diff --git a/CLAUDE.md b/CLAUDE.md index b72aa8abf..dc17f8795 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -101,14 +101,36 @@ Release is what ships and exercises the backstops the asserts hide): ```bash docker run --rm -v "$PWD":/src:ro ubuntu:24.04 bash -c ' apt-get update -qq && apt-get install -y -qq build-essential cmake libssl-dev git ca-certificates - cp -r /src /work && cd /work for cfg in Debug Release; do - cmake -B /b-$cfg -S . -DCMAKE_BUILD_TYPE=$cfg -DMAFIANET_BUILD_TESTS=ON + cmake -B /b-$cfg -S /src -DCMAKE_BUILD_TYPE=$cfg -DMAFIANET_BUILD_TESTS=ON cmake --build /b-$cfg --parallel "$(nproc)" ctest --test-dir /b-$cfg --output-on-failure --timeout 600 || exit 1 done' ``` +Build straight from the read-only mount with `-S /src`; **do not** `cp -r /src /work` +first. A local `build/` directory is a few hundred MB, the copy pulls it in, and once +the container's writable layer fills up it truncates files *silently* -- the symptom +is gcc reporting "unterminated #if" in a source file that compiles fine everywhere +else. CMake never writes into the source tree, so there is nothing to copy for. + +To check for leaks and memory errors, add a sanitizer to the same invocation and run +the binaries directly (LeakSanitizer is Linux-only; macOS ASAN has no leak +detection): + +```bash +docker run --rm -v "$PWD":/src:ro ubuntu:24.04 bash -c ' + apt-get update -qq && apt-get install -y -qq build-essential cmake libssl-dev git ca-certificates + cmake -B /b -S /src -DCMAKE_BUILD_TYPE=Debug -DMAFIANET_BUILD_TESTS=ON \ + -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \ + -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \ + -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" \ + -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address" + cmake --build /b --parallel "$(nproc)" + ASAN_OPTIONS=detect_leaks=1 /b/Tests/UnitTests + ASAN_OPTIONS=detect_leaks=1 /b/Tests/IntegrationTests --gtest_filter=".*"' +``` + `Tests/Integration/MmsgBatchLiveTests.cpp` is the suite that actually fills batches (the rest of the suite sends ~1 datagram per tick and never crosses `MMSG_BATCH_MAX`). Keep its message counts above `MMSG_BATCH_MAX` or that coverage silently disappears. diff --git a/DependentExtensions/Autopatcher/AutopatcherServer.h b/DependentExtensions/Autopatcher/AutopatcherServer.h index 233b21676..972a59ca9 100644 --- a/DependentExtensions/Autopatcher/AutopatcherServer.h +++ b/DependentExtensions/Autopatcher/AutopatcherServer.h @@ -35,6 +35,7 @@ #include "mafianet/plugin_interface2.h" #include "mafianet/packet_priority.h" #include "mafianet/thread_pool.h" +#include "mafianet/ds_queue.h" #include "mafianet/bit_stream.h" #include "mafianet/string.h" #include "mafianet/file_list.h" diff --git a/DependentExtensions/Lobby2/Lobby2Server.h b/DependentExtensions/Lobby2/Lobby2Server.h index cf2ec63aa..5a93579d8 100644 --- a/DependentExtensions/Lobby2/Lobby2Server.h +++ b/DependentExtensions/Lobby2/Lobby2Server.h @@ -21,6 +21,7 @@ #include "Lobby2Plugin.h" #include "mafianet/ds_ordered_list.h" #include "mafianet/thread_pool.h" +#include "mafianet/ds_queue.h" #include "Lobby2Presence.h" //class PostgreSQLInterface; diff --git a/Source/include/mafianet/ds_table.h b/Source/include/mafianet/ds_table.h index 595628d79..73f8938cf 100644 --- a/Source/include/mafianet/ds_table.h +++ b/Source/include/mafianet/ds_table.h @@ -25,6 +25,7 @@ #include "mafianet/memory_override.h" #include "mafianet/export.h" #include "mafianet/string.h" +#include #define _TABLE_MAX_COLUMN_NAME_LENGTH 64 @@ -330,11 +331,11 @@ namespace DataStructures Table& operator = ( const Table& input ); protected: - Table::Row* AddRowColumns(unsigned rowId, Row *row, DataStructures::List columnIndices); + Table::Row* AddRowColumns(unsigned rowId, Row *row, std::vector columnIndices); void DeleteRow(Row *row); - void QueryRow(DataStructures::List &inclusionFilterColumnIndices, DataStructures::List &columnIndicesToReturn, unsigned key, Table::Row* row, FilterQuery *inclusionFilters, Table *result); + void QueryRow(std::vector &inclusionFilterColumnIndices, std::vector &columnIndicesToReturn, unsigned key, Table::Row* row, FilterQuery *inclusionFilters, Table *result); std::map rows; diff --git a/Source/include/mafianet/message_filter.h b/Source/include/mafianet/message_filter.h index e43501c8d..11166ec98 100644 --- a/Source/include/mafianet/message_filter.h +++ b/Source/include/mafianet/message_filter.h @@ -27,6 +27,8 @@ #include "mafianet/plugin_interface2.h" #include "mafianet/ds_ordered_list.h" #include "mafianet/ds_hash.h" +#include +#include #include "mafianet/export.h" /// MessageIdentifier (ID_*) values shoudln't go higher than this. Change it if you do. @@ -188,9 +190,28 @@ class RAK_DLL_EXPORT MessageFilter : public PluginInterface2 FilterSet* GetFilterSetByID(int filterSetID); void OnInvalidMessage(FilterSet *filterSet, AddressOrGUID systemAddress, unsigned char messageID); - DataStructures::OrderedList filterList; + // Kept sorted ascending by FilterSet::filterSetID and searched with std::lower_bound, + // matching the OrderedList it replaces. GetFilterSetIDByIndex publishes this index. + std::vector filterList; + // Change to guid - DataStructures::Hash systemList; + // Same hash and same equality as the DataStructures::Hash this replaces, so behaviour is + // unchanged -- including a pre-existing quirk worth knowing about: AddressOrGUID::operator== + // matches when EITHER the guid or the address matches, while ToInteger hashes on the guid + // when one is set and the address otherwise. Two keys that compare equal can therefore hash + // differently and miss each other. That is inherited deliberately; changing key equality + // would change which systems a filter set matches. + struct AddressOrGUIDKeyHash + { + size_t operator()(const AddressOrGUID &aog) const + { + return (size_t) AddressOrGUID::ToInteger(aog); + } + }; + std::unordered_map systemList; + + // Position of filterSetID in filterList, or of where it would be inserted. + unsigned int IndexForFilterSetID(int filterSetID, bool *found) const; int autoAddNewConnectionsToFilter; MafiaNet::Time whenLastTimeoutCheck; diff --git a/Source/include/mafianet/packetized_tcp.h b/Source/include/mafianet/packetized_tcp.h index a67996dc4..062b3cc7b 100644 --- a/Source/include/mafianet/packetized_tcp.h +++ b/Source/include/mafianet/packetized_tcp.h @@ -26,7 +26,9 @@ #include "mafianet/tcp_interface.h" #include "mafianet/ds_byte_queue.h" -#include "mafianet/ds_map.h" + +#include +#include namespace MafiaNet { @@ -71,17 +73,23 @@ class RAK_DLL_EXPORT PacketizedTCP : public TCPInterface protected: void ClearAllConnections(void); + /// Frees every reassembled message still waiting for Receive, and empties the queue. + void ClearWaitingPackets(void); void RemoveFromConnectionList(const SystemAddress &sa); void AddToConnectionList(const SystemAddress &sa); void PushNotificationsToQueues(void); Packet *ReturnOutgoingPacket(void); // A single TCP recieve may generate multiple split packets. They are stored in the waitingPackets list until Receive is called - DataStructures::Queue waitingPackets; - DataStructures::Map connections; + std::deque waitingPackets; + + // Per-connection reassembly buffers, keyed by address. Ordered by + // SystemAddress::operator<, as DataStructures::Map was; nothing depends on the + // order, only on lookup by address. + std::map connections; // Mirrors single producer / consumer, but processes them in Receive() before returning to user - DataStructures::Queue _newIncomingConnections, _lostConnections, _failedConnectionAttempts, _completedConnectionAttempts; + std::deque _newIncomingConnections, _lostConnections, _failedConnectionAttempts, _completedConnectionAttempts; }; } // namespace MafiaNet diff --git a/Source/include/mafianet/peer.h b/Source/include/mafianet/peer.h index fe31cb18f..8f1920c36 100644 --- a/Source/include/mafianet/peer.h +++ b/Source/include/mafianet/peer.h @@ -42,6 +42,9 @@ #include "mafianet/lockless_types.h" #include "mafianet/ds_queue.h" +#include +#include + namespace MafiaNet { /// Forward declarations class HuffmanEncodingTree; @@ -970,11 +973,11 @@ class RAK_DLL_EXPORT RakPeer : public RakPeerInterface, public RNS2EventHandler #endif //DataStructures::List* > automaticVariableSynchronizationList; - DataStructures::List banList; + std::vector banList; // Threadsafe, and not thread safe DataStructures::List pluginListTS, pluginListNTS; - DataStructures::Queue requestedConnectionQueue; + std::deque requestedConnectionQueue; SimpleMutex requestedConnectionQueueMutex; // void RunMutexedUpdateCycle(void); @@ -1012,9 +1015,9 @@ class RAK_DLL_EXPORT RakPeer : public RakPeerInterface, public RNS2EventHandler // DataStructures::ThreadsafeAllocatingQueue bufferedPackets; - DataStructures::Queue bufferedPacketsFreePool; + std::deque bufferedPacketsFreePool; MafiaNet::SimpleMutex bufferedPacketsFreePoolMutex; - DataStructures::Queue bufferedPacketsQueue; + std::deque bufferedPacketsQueue; MafiaNet::SimpleMutex bufferedPacketsQueueMutex; virtual void DeallocRNS2RecvStruct(RNS2RecvStruct *s, const char *file, unsigned int line); @@ -1027,7 +1030,7 @@ class RAK_DLL_EXPORT RakPeer : public RakPeerInterface, public RNS2EventHandler { SocketQueryOutput() {} ~SocketQueryOutput() {} - DataStructures::List sockets; + std::vector sockets; }; DataStructures::ThreadsafeAllocatingQueue socketQueryOutput; @@ -1060,7 +1063,7 @@ class RAK_DLL_EXPORT RakPeer : public RakPeerInterface, public RNS2EventHandler bool trackFrequencyTable; // Smart pointer so I can return the object to the user - DataStructures::List socketList; + std::vector socketList; void DerefAllSockets(void); unsigned int GetRakNetSocketFromUserConnectionSocketIndex(unsigned int userIndex) const; // Used for RPC replies @@ -1095,7 +1098,7 @@ class RAK_DLL_EXPORT RakPeer : public RakPeerInterface, public RNS2EventHandler bool (*incomingDatagramEventHandler)(RNS2RecvStruct *); // Systems in this list will not go through the secure connection process, even when secure connections are turned on. Wildcards are accepted. - DataStructures::List securityExceptionList; + std::vector securityExceptionList; SystemAddress ipList[ MAXIMUM_NUMBER_OF_INTERNAL_IDS ]; @@ -1112,7 +1115,7 @@ class RAK_DLL_EXPORT RakPeer : public RakPeerInterface, public RNS2EventHandler DataStructures::MemoryPool packetAllocationPool; SimpleMutex packetReturnMutex; - DataStructures::Queue packetReturnQueue; + std::deque packetReturnQueue; Packet *AllocPacket(unsigned dataSize, const char *file, unsigned int line); Packet *AllocPacket(unsigned dataSize, unsigned char *data, const char *file, unsigned int line); diff --git a/Source/include/mafianet/reliability_layer.h b/Source/include/mafianet/reliability_layer.h index b5314feb5..daf743c11 100644 --- a/Source/include/mafianet/reliability_layer.h +++ b/Source/include/mafianet/reliability_layer.h @@ -49,6 +49,8 @@ #define INCLUDE_TIMESTAMP_WITH_DATAGRAMS 1 #else #include "mafianet/cc_sliding_window.h" +#include +#include #define INCLUDE_TIMESTAMP_WITH_DATAGRAMS 0 #endif @@ -408,7 +410,7 @@ class ReliabilityLayer// // Used ONLY for MafiaNet::Reliability::ReliableOrdered // MafiaNet::Reliability::ReliableSequenced just returns the newest one // DataStructures::List*> orderingList; - DataStructures::Queue outputQueue; + std::deque outputQueue; int splitMessageProgressInterval; CCTimeType unreliableTimeout; @@ -430,7 +432,9 @@ class ReliabilityLayer// // Queue length is programmatically restricted to DATAGRAM_MESSAGE_ID_ARRAY_LENGTH // This is essentially an O(1) lookup to get a DatagramHistoryNode given an index // datagramHistory holds a linked list of MessageNumberNode. Each MessageNumberNode refers to one element in resendList which can be cleared on an ack. - DataStructures::Queue datagramHistory; + // A sliding window: index 0 is datagram number datagramHistoryPopCount, and lookups index + // it by (datagramNumber - datagramHistoryPopCount) in wrapping uint24_t arithmetic. + std::deque datagramHistory; DataStructures::MemoryPool datagramHistoryMessagePool; struct UnreliableWithAckReceiptNode @@ -443,7 +447,7 @@ class ReliabilityLayer// uint32_t sendReceiptSerial; MafiaNet::TimeUS nextActionTime; }; - DataStructures::List unreliableWithAckReceiptHistory; + std::vector unreliableWithAckReceiptHistory; void RemoveFromDatagramHistory(DatagramSequenceNumberType index); MessageNumberNode* GetMessageNumberNodeByDatagramIndex(DatagramSequenceNumberType index, CCTimeType *timeSent); @@ -487,7 +491,10 @@ class ReliabilityLayer// // double bytesInSendBuffer[MafiaNet::NUMBER_OF_PRIORITIES]; - DataStructures::OrderedList splitPacketChannelList; + // Kept sorted by split-packet id and searched with std::lower_bound through + // IndexForSplitPacketId, matching the OrderedList it replaces. Several split messages can + // be in flight at once, so the keyed lookup decides which message a fragment belongs to. + std::vector splitPacketChannelList; MessageNumberType sendReliableMessageNumberIndex; MessageNumberType internalOrderIndex; @@ -549,7 +556,10 @@ class ReliabilityLayer// /// If we get a packet number where (receivedPacketsBaseIndex-packetNumber) is less than half the range of receivedPacketsBaseIndex then it is a duplicate /// Otherwise, it is a duplicate packet (and ignore it). // DataStructures::Queue hasReceivedPacketQueue; - DataStructures::Queue hasReceivedPacketQueue; + // Sliding window of holes, indexed from the head. std::deque frees blocks as it drains, so + // the predecessor's manual AllocationSize()/Compress() compaction is gone -- that existed + // only because its ring buffer never shrank on its own. + std::deque hasReceivedPacketQueue; DatagramSequenceNumberType receivedPacketsBaseIndex; bool resetReceivedPackets; @@ -586,7 +596,7 @@ class ReliabilityLayer// // SystemAddress systemAddress; unsigned int extraSocketOptions; }; - DataStructures::Queue delayList; + std::deque delayList; // Internet simulator double packetloss; @@ -623,15 +633,23 @@ class ReliabilityLayer// void AddToListTail(InternalPacket *internalPacket, bool modifyUnacknowledgedBytes); void PopListHead(bool modifyUnacknowledgedBytes); bool IsResendQueueEmpty(void) const; - void SortSplitPacketList(DataStructures::List &data, unsigned int leftEdge, unsigned int rightEdge) const; + void SortSplitPacketList(std::vector &data, unsigned int leftEdge, unsigned int rightEdge) const; + + // Position of a split-packet id in splitPacketChannelList, or where it would be inserted. + unsigned int IndexForSplitPacketId(SplitPacketIdType id, bool *found) const; + // Sorted insert; returns the index, or (unsigned)-1 if that id is already present, matching + // the OrderedList::Insert(assertOnDuplicate=true) it replaces. The id is passed in rather + // than read back out of the channel: at the call site the channel's split packet list has + // not been populated yet, so its own id is not readable there. + unsigned int InsertSplitPacketChannel(SplitPacketIdType id, SplitPacketChannel *channel); void SendACKs(RakNetSocket2 *s, SystemAddress &systemAddress, CCTimeType time, RakNetRandom *rnr, BitStream &updateBitStream); - DataStructures::List packetsToSendThisUpdate; - DataStructures::List packetsToDeallocThisUpdate; + std::vector packetsToSendThisUpdate; + std::vector packetsToDeallocThisUpdate; // boundary is in packetsToSendThisUpdate, inclusive - DataStructures::List packetsToSendThisUpdateDatagramBoundaries; - DataStructures::List datagramsToSendThisUpdateIsPair; - DataStructures::List datagramSizesInBytes; + std::vector packetsToSendThisUpdateDatagramBoundaries; + std::vector datagramsToSendThisUpdateIsPair; + std::vector datagramSizesInBytes; BitSize_t datagramSizeSoFar; BitSize_t allDatagramSizesSoFar; double totalUserDataBytesAcked; diff --git a/Source/include/mafianet/replica_manager3.h b/Source/include/mafianet/replica_manager3.h index 3254f4e4f..8d6ea7f12 100644 --- a/Source/include/mafianet/replica_manager3.h +++ b/Source/include/mafianet/replica_manager3.h @@ -32,6 +32,8 @@ #include "mafianet/network_id_object.h" #include "mafianet/ds_ordered_list.h" #include "mafianet/ds_queue.h" +#include +#include #include "mafianet/simple_mutex.h" #include "mafianet/virtual_world.h" @@ -376,8 +378,10 @@ class RAK_DLL_EXPORT ReplicaManager3 : public PluginInterface2 // For O(1) lookup. One slot per WorldId value: an id read off the wire indexes it before it is checked. RM3World *worldsArray[256]; - // For fast traversal - DataStructures::List worldsList; + // For fast traversal. RemoveWorld swaps the last element into the hole rather than + // shifting -- that is documented on GetWorldIdAtIndex and publicly observable, so it is + // reproduced explicitly rather than with erase(). + std::vector worldsList; private: // #med - reconsider visibility here --- should be properly encapsulated so to not allow access to worldsList by derived classes (which could bypass the mutex) // mutex to ensure thread safe access to worldsList member @@ -644,6 +648,15 @@ class RAK_DLL_EXPORT Connection_RM3 static int Replica3LSRComp( Replica3 * const &replica3, LastSerializationResult * const &data ); + // Position of replica3 in constructedReplicaList, or of where it would be inserted; + // *found reports which. Replaces OrderedList::GetIndexFromKey. + unsigned int IndexForReplica(Replica3 *replica3, bool *found) const; + + // Sorted insert into constructedReplicaList. Returns the index, or (unsigned)-1 if the + // replica is already present -- the same contract as the OrderedList::Insert it replaces, + // which refused duplicates and asserted on them. + unsigned int InsertConstructedReplica(LastSerializationResult *lsr); + // Internal void ClearDownloadGroup(RakPeerInterface *rakPeerInterface); protected: @@ -717,27 +730,33 @@ class RAK_DLL_EXPORT Connection_RM3 // The list of objects that our local system and this remote system both have // Either we sent this object to them, or they sent this object to us // A given Replica can be either in queryToConstructReplicaList or constructedReplicaList but not both at the same time - DataStructures::OrderedList constructedReplicaList; + // Kept sorted ascending by Replica3::referenceIndex and searched with std::lower_bound, + // matching the OrderedList it replaces. Lookup runs on the serialize path for every + // replica every tick, so the ordering is kept rather than traded for a linear scan. + std::vector constructedReplicaList; // Objects that we have, but this system does not, and we will query each tick to see if it should be sent to them // If we do send it to them, the replica is moved to constructedReplicaList // A given Replica can be either in queryToConstructReplicaList or constructedReplicaList but not both at the same time - DataStructures::List queryToConstructReplicaList; + std::vector queryToConstructReplicaList; // Objects that this system has constructed are added at the same time to queryToSerializeReplicaList // This list is used to serialize all objects that this system has to this connection - DataStructures::List queryToSerializeReplicaList; + std::vector queryToSerializeReplicaList; // Objects that are constructed on this system are also queried if they should be destroyed to this system - DataStructures::List queryToDestructReplicaList; + std::vector queryToDestructReplicaList; // Working lists + // Handed straight to the public virtuals QueryReplicaList and SendConstruction, which take + // DataStructures::List&. Left as-is deliberately: converting here would copy both lists + // every tick on the serialize path. Stage 3 moves them with those signatures. DataStructures::List constructedReplicasCulled, destroyedReplicasCulled; // This is used if QueryGroupDownloadMessages() returns true when ID_REPLICA_MANAGER_DOWNLOAD_STARTED arrives // Packets will be gathered and not returned until ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE arrives bool groupConstructionAndSerialize; - DataStructures::Queue downloadGroup; + std::deque downloadGroup; // Stores if we got download complete for this connection bool gotDownloadComplete; diff --git a/Source/include/mafianet/rpc4_plugin.h b/Source/include/mafianet/rpc4_plugin.h index 33c4c01f6..cff1e88e5 100644 --- a/Source/include/mafianet/rpc4_plugin.h +++ b/Source/include/mafianet/rpc4_plugin.h @@ -31,6 +31,8 @@ #include "mafianet/string.h" #include "mafianet/network_id_object.h" #include "mafianet/ds_hash.h" +#include +#include #include "mafianet/ds_ordered_list.h" /// \defgroup RPC_PLUGIN_GROUP RPC @@ -238,9 +240,24 @@ class NetworkIDManager; void *context; }; - DataStructures::Hash registeredNonblockingFunctions; - DataStructures::Hash registeredBlockingFunctions; - DataStructures::OrderedList localCallbacks; + // RakString has no std::hash specialisation; reuse the hash the replaced + // DataStructures::Hash instantiations were given. + struct RakStringKeyHash + { + size_t operator()(const MafiaNet::RakString &s) const + { + return (size_t) MafiaNet::RakString::ToInteger(s); + } + }; + std::unordered_map registeredNonblockingFunctions; + std::unordered_map registeredBlockingFunctions; + // Kept sorted ascending by LocalCallback::messageId and searched with std::lower_bound, + // matching the OrderedList it replaces. Nothing publishes this index, but the dispatch + // path looks up by message id on every received packet, so the ordering is kept. + std::vector localCallbacks; + + // Position of messageId, or of where it would be inserted; *found reports which. + unsigned int IndexForMessageId(MessageID messageId, bool *found) const; MafiaNet::BitStream blockingReturnValue; bool gotBlockingReturnValue; diff --git a/Source/include/mafianet/signaled_event.h b/Source/include/mafianet/signaled_event.h index bd0d8efa6..3e349bcdd 100644 --- a/Source/include/mafianet/signaled_event.h +++ b/Source/include/mafianet/signaled_event.h @@ -50,6 +50,14 @@ class RAK_DLL_EXPORT SignaledEvent void WaitOnEvent(int timeoutMs); protected: + /// Guards SetEvent and WaitOnEvent against running before InitEvent, or after + /// CloseEvent. On POSIX the condition variable and mutex below are only + /// initialised by InitEvent, and pthread_cond_broadcast on an uninitialised + /// pthread_cond_t blocks forever on glibc rather than failing, so an + /// unguarded SetEvent hung the caller. Read and written under isSignaledMutex + /// on POSIX; on Windows the INVALID_HANDLE_VALUE check serves the same purpose. + bool isInitialized; + #ifdef _WIN32 HANDLE eventList; diff --git a/Source/include/mafianet/statistics_history.h b/Source/include/mafianet/statistics_history.h index 6fe964275..372f0505a 100644 --- a/Source/include/mafianet/statistics_history.h +++ b/Source/include/mafianet/statistics_history.h @@ -27,6 +27,8 @@ #include "mafianet/memory_override.h" #include "mafianet/native_types.h" #include "mafianet/ds_list.h" +#include +#include #include "mafianet/types.h" #include "mafianet/ds_ordered_list.h" #include "mafianet/string.h" @@ -190,15 +192,34 @@ class RAK_DLL_EXPORT StatisticsHistory static int TrackedObjectComp( const uint64_t &key, TrackedObject* const &data ); protected: + // RakString has no std::hash specialisation; reuse the hash the replaced + // DataStructures::Hash instantiation was given. + struct RakStringKeyHash + { + size_t operator()(const MafiaNet::RakString &s) const + { + return (size_t) MafiaNet::RakString::ToInteger(s); + } + }; + struct TrackedObject { TrackedObject(); ~TrackedObject(); TrackedObjectData trackedObjectData; - DataStructures::Hash dataQueues; + // Keyed by the caller's key string. Unordered: the only consumers either sort the result + // (GetHistorySorted) or treat it as a set (GetUniqueKeyList), so bucket order never mattered. + std::unordered_map dataQueues; }; - DataStructures::OrderedList objects; + // Kept sorted ascending by trackedObjectData.objectId. The sort order is a public contract: + // GetObjectIndex, GetObjectAtIndex and RemoveObjectAtIndex all expose this index, so a + // replacement must stay both sorted and O(1) indexable -- hence a vector with lower_bound + // rather than a std::map. Insert through IndexForObjectId to preserve it. + std::vector objects; + + // Position of objectId, or of where it would be inserted; *found reports which. + unsigned int IndexForObjectId(uint64_t objectId, bool *found) const; Time timeToTrack; }; diff --git a/Source/include/mafianet/tcp_interface.h b/Source/include/mafianet/tcp_interface.h index 34bda39e2..803792224 100644 --- a/Source/include/mafianet/tcp_interface.h +++ b/Source/include/mafianet/tcp_interface.h @@ -37,6 +37,8 @@ #include "mafianet/ds_threadsafe_allocating_queue.h" #include "mafianet/lockless_types.h" #include "mafianet/plugin_interface2.h" +#include +#include #if OPEN_SSL_CLIENT_SUPPORT==1 #include @@ -147,12 +149,15 @@ class RAK_DLL_EXPORT TCPInterface bool CreateListenSocket(unsigned short port, unsigned short maxIncomingConnections, unsigned short socketFamily, const char *hostAddress); // Plugins - DataStructures::List messageHandlerList; + // DetachPlugin swaps the last handler into the hole; see the implementation. + std::vector messageHandlerList; MafiaNet::LocklessUint32_t isStarted, threadRunning; __TCPSOCKET__ listenSocket; - DataStructures::Queue headPush, tailPush; + // FIFO within each queue, and ReceiveInt drains headPush before tailPush. Both are + // touched only by the calling thread. + std::deque headPush, tailPush; RemoteClient* remoteClients; unsigned short remoteClientsLength; @@ -180,11 +185,12 @@ class RAK_DLL_EXPORT TCPInterface DataStructures::ThreadsafeAllocatingQueue newIncomingConnections, lostConnections, requestedCloseConnections; DataStructures::ThreadsafeAllocatingQueue newRemoteClients; SimpleMutex completedConnectionAttemptMutex, failedConnectionAttemptMutex; - DataStructures::Queue completedConnectionAttempts, failedConnectionAttempts; + std::deque completedConnectionAttempts, failedConnectionAttempts; int threadPriority; - DataStructures::List<__TCPSOCKET__> blockingSocketList; + // Order is not published; the blocking-connect helper swap-removes, as before. + std::vector<__TCPSOCKET__> blockingSocketList; SimpleMutex blockingSocketListMutex; @@ -211,7 +217,7 @@ class RAK_DLL_EXPORT TCPInterface SSL_CTX* ctx; SSL_METHOD *meth; DataStructures::ThreadsafeAllocatingQueue startSSL; - DataStructures::List activeSSLConnections; + std::vector activeSSLConnections; SimpleMutex sharedSslMutex; #endif }; diff --git a/Source/include/mafianet/team_balancer.h b/Source/include/mafianet/team_balancer.h index 44c2969b3..efba9c028 100644 --- a/Source/include/mafianet/team_balancer.h +++ b/Source/include/mafianet/team_balancer.h @@ -31,6 +31,7 @@ #include "mafianet/native_types.h" #include "mafianet/ds_list.h" #include "mafianet/string.h" +#include namespace MafiaNet { @@ -170,17 +171,22 @@ class RAK_DLL_EXPORT TeamBalancer : public PluginInterface2 bool forceTeamsToBeEven; bool lockTeams; // So if we lose the connection while processing, we request the same info of the new host - DataStructures::List myTeamMembers; - - DataStructures::List teamLimits; - DataStructures::List teamMemberCounts; - DataStructures::List teamMembers; + // DeleteMember swaps the last entry into the hole. No public method exposes this index and + // every lookup is by NetworkID, so the swap is unobservable through the API; it is kept + // because it is the cheaper operation, not because anything depends on it. + std::vector myTeamMembers; + + std::vector teamLimits; + std::vector teamMemberCounts; + // Host-side. RemoveTeamMember swaps the last entry in, and this order decides which member + // is moved when teams are rebalanced, so the swap is reproduced exactly. + std::vector teamMembers; unsigned int GetMemberIndex(NetworkID memberId, RakNetGUID guid) const; unsigned int AddTeamMember(const TeamMember &tm); // Returns index of new member void RemoveTeamMember(unsigned int index); void EvenTeams(void); - unsigned int GetMemberIndexToSwitchTeams(const DataStructures::List &sourceTeamNumbers, TeamId targetTeamNumber); - void GetOverpopulatedTeams(DataStructures::List &overpopulatedTeams, int maxTeamSize); + unsigned int GetMemberIndexToSwitchTeams(const std::vector &sourceTeamNumbers, TeamId targetTeamNumber); + void GetOverpopulatedTeams(std::vector &overpopulatedTeams, int maxTeamSize); void SwitchMemberTeam(unsigned int teamMemberIndex, TeamId destinationTeam); void NotifyTeamAssigment(unsigned int teamMemberIndex); bool WeAreHost(void) const; diff --git a/Source/include/mafianet/team_manager.h b/Source/include/mafianet/team_manager.h index e86437fe0..b9c823c4e 100644 --- a/Source/include/mafianet/team_manager.h +++ b/Source/include/mafianet/team_manager.h @@ -36,6 +36,8 @@ #include "mafianet/ds_list.h" #include "mafianet/types.h" #include "mafianet/ds_hash.h" +#include +#include #include "mafianet/ds_ordered_list.h" namespace MafiaNet @@ -272,16 +274,16 @@ class RAK_DLL_EXPORT TM_TeamMember NetworkID networkId; TM_World* world; // Teams we are a member of. We can be on more than one team, but not on the same team more than once - DataStructures::List teams; + std::vector teams; // If teams is empty, which subcategory of noTeam we are on NoTeamId noTeamSubcategory; // Teams we have requested to join. Mutually exclusive with teams we are already on. Cannot request the same team more than once. - DataStructures::List teamsRequested; + std::vector teamsRequested; // If teamsRequested is not empty, we want to join a specific team // If teamsRequested is empty, then joinTeamType is either JOIN_NO_TEAM or JOIN_ANY_AVAILABLE_TEAM JoinTeamType joinTeamType; // Set by StoreLastTeams() - DataStructures::List lastTeams; + std::vector lastTeams; MafiaNet::Time whenJoinAnyRequested; unsigned int joinAnyRequestIndex; void *owner; @@ -400,7 +402,7 @@ class RAK_DLL_EXPORT TM_Team NetworkID ID; TM_World* world; // Which members are on this team. The same member cannot be on the same team more than once - DataStructures::List teamMembers; + std::vector teamMembers; // Permissions on who can join this team JoinPermissions joinPermissions; // Whether or not to consider this team when balancing teams @@ -566,7 +568,10 @@ class TM_World void FillRequestedSlots(void); unsigned int GetAvailableTeamIndexWithFewestMembers(TeamMemberLimit secondaryLimit, JoinPermissions joinPermissions); - void GetSortedJoinRequests(DataStructures::OrderedList &joinRequests); + // Sorted ascending by JoinRequestHelperComp. requestIndex breaks ties and is a + // monotonically increasing per-request counter, so no two requests compare equal and a + // sort is equivalent to the ordered inserts this replaced. + void GetSortedJoinRequests(std::vector &joinRequests); // Send a message to all participants @@ -580,19 +585,22 @@ class TM_World // 2. Else return 0 TM_Team* JoinAnyTeam(TM_TeamMember *teamMember, int *resultCode); - int JoinSpecificTeam(TM_TeamMember *teamMember, TM_Team *team, bool isTeamSwitch, TM_Team *teamToLeave, DataStructures::List &teamsWeAreLeaving); + int JoinSpecificTeam(TM_TeamMember *teamMember, TM_Team *team, bool isTeamSwitch, TM_Team *teamToLeave, std::vector &teamsWeAreLeaving); TeamMemberLimit GetBalancedTeamLimit(void) const; - // For fast lookup. Shares pointers with list teams - DataStructures::Hash teamsHash; + // For fast lookup. Shares pointers with list teams. NetworkID is an integer type, so the + // default std::hash applies and the ToUint32 projection the old table needed is not used. + std::unordered_map teamsHash; // For fast lookup. Shares pointers with list teamMembers - DataStructures::Hash teamMembersHash; + std::unordered_map teamMembersHash; TeamManager *teamManager; - DataStructures::List participants; - DataStructures::List teams; - DataStructures::List teamMembers; + std::vector participants; + // Order-preserving: GetTeamByIndex and GetTeamIndex publish this index and + // DereferenceTeam shifts the remainder down rather than swapping. + std::vector teams; + std::vector teamMembers; bool balanceTeamsIsActive; RakNetGUID hostGuid; WorldId worldId; @@ -739,13 +747,14 @@ class RAK_DLL_EXPORT TeamManager : public PluginInterface2 /// \param[in] A packet where packet->data[0]==ID_TEAM_BALANCER_TEAM_ASSIGNED /// \return true on success, false on read error void DecodeTeamAssigned(MafiaNet::BitStream *bsIn, TM_World **world, TM_TeamMember **teamMember, NoTeamId &noTeamSubcategory, - JoinTeamType &joinTeamType, DataStructures::List &newTeam, - DataStructures::List &teamsLeft, DataStructures::List &teamsJoined); + JoinTeamType &joinTeamType, std::vector &newTeam, + std::vector &teamsLeft, std::vector &teamsJoined); // O(1) lookup for a given world. If I need more worlds, change this to a hash or ordered list TM_World *worldsArray[255]; - // All allocated worlds for linear traversal - DataStructures::List worldsList; + // All allocated worlds for linear traversal. Unlike the team lists above, RemoveWorld + // swaps the last world into the hole, which GetWorldAtIndex exposes. + std::vector worldsList; bool autoAddParticipants; TMTopology topology; diff --git a/Source/include/mafianet/thread_pool.h b/Source/include/mafianet/thread_pool.h index 448b82bba..aa613965b 100644 --- a/Source/include/mafianet/thread_pool.h +++ b/Source/include/mafianet/thread_pool.h @@ -17,12 +17,14 @@ #define __THREAD_POOL_H #include "mafianet/memory_override.h" -#include "mafianet/ds_queue.h" +#include "mafianet/assert.h" #include "mafianet/simple_mutex.h" #include "mafianet/export.h" #include "mafianet/thread.h" #include "mafianet/signaled_event.h" +#include + class ThreadDataInterface { public: @@ -158,9 +160,9 @@ struct RAK_DLL_EXPORT ThreadPool // inputFunctionQueue & inputQueue are paired arrays so if you delete from one at a particular index you must delete from the other // at the same index - DataStructures::Queue inputFunctionQueue; - DataStructures::Queue inputQueue; - DataStructures::Queue outputQueue; + std::deque inputFunctionQueue; + std::deque inputQueue; + std::deque outputQueue; ThreadDataInterface *threadDataInterface; void *tdiContext; @@ -268,10 +270,12 @@ void* WorkerThread( void* arguments ) // Read input data userCallback=0; threadPool->inputQueueMutex.Lock(); - if (threadPool->inputFunctionQueue.Size()) + if (threadPool->inputFunctionQueue.empty()==false) { - userCallback=threadPool->inputFunctionQueue.Pop(); - inputData=threadPool->inputQueue.Pop(); + userCallback=threadPool->inputFunctionQueue.front(); + threadPool->inputFunctionQueue.pop_front(); + inputData=threadPool->inputQueue.front(); + threadPool->inputQueue.pop_front(); } threadPool->inputQueueMutex.Unlock(); @@ -281,7 +285,7 @@ void* WorkerThread( void* arguments ) if (returnOutput) { threadPool->outputQueueMutex.Lock(); - threadPool->outputQueue.Push(callbackOutput, _FILE_AND_LINE_ ); + threadPool->outputQueue.push_back(callbackOutput); threadPool->outputQueueMutex.Unlock(); } } @@ -423,8 +427,8 @@ template void ThreadPool::AddInput(OutputType (*workerThreadCallback)(InputType, bool *returnOutput, void* perThreadData), InputType inputData) { inputQueueMutex.Lock(); - inputQueue.Push(inputData, _FILE_AND_LINE_ ); - inputFunctionQueue.Push(workerThreadCallback, _FILE_AND_LINE_ ); + inputQueue.push_back(inputData); + inputFunctionQueue.push_back(workerThreadCallback); inputQueueMutex.Unlock(); quitAndIncomingDataEvents.SetEvent(); @@ -433,34 +437,34 @@ template void ThreadPool::AddOutput(OutputType outputData) { outputQueueMutex.Lock(); - outputQueue.Push(outputData, _FILE_AND_LINE_ ); + outputQueue.push_back(outputData); outputQueueMutex.Unlock(); } template bool ThreadPool::HasOutputFast(void) { - return outputQueue.IsEmpty()==false; + return outputQueue.empty()==false; } template bool ThreadPool::HasOutput(void) { bool res; outputQueueMutex.Lock(); - res=outputQueue.IsEmpty()==false; + res=outputQueue.empty()==false; outputQueueMutex.Unlock(); return res; } template bool ThreadPool::HasInputFast(void) { - return inputQueue.IsEmpty()==false; + return inputQueue.empty()==false; } template bool ThreadPool::HasInput(void) { bool res; inputQueueMutex.Lock(); - res=inputQueue.IsEmpty()==false; + res=inputQueue.empty()==false; inputQueueMutex.Unlock(); return res; } @@ -470,7 +474,9 @@ OutputType ThreadPool::GetOutput(void) // Real output check OutputType output; outputQueueMutex.Lock(); - output=outputQueue.Pop(); + RakAssert(outputQueue.empty()==false); + output=outputQueue.front(); + outputQueue.pop_front(); outputQueueMutex.Unlock(); return output; } @@ -482,19 +488,25 @@ void ThreadPool::Clear(void) { runThreadsMutex.Unlock(); inputQueueMutex.Lock(); - inputFunctionQueue.Clear(_FILE_AND_LINE_); - inputQueue.Clear(_FILE_AND_LINE_); + inputFunctionQueue.clear(); + inputQueue.clear(); inputQueueMutex.Unlock(); outputQueueMutex.Lock(); - outputQueue.Clear(_FILE_AND_LINE_); + outputQueue.clear(); outputQueueMutex.Unlock(); } else { - inputFunctionQueue.Clear(_FILE_AND_LINE_); - inputQueue.Clear(_FILE_AND_LINE_); - outputQueue.Clear(_FILE_AND_LINE_); + // This branch used to return with runThreadsMutex still held, so any later + // lock of it -- StartThreads, StopThreads, Pause, WasStarted, or ~ThreadPool + // calling StopThreads -- deadlocked on a non-recursive SimpleMutex. Nothing + // in the library calls Clear(), which is why it went unnoticed, but it is a + // public header. + inputFunctionQueue.clear(); + inputQueue.clear(); + outputQueue.clear(); + runThreadsMutex.Unlock(); } } template @@ -510,18 +522,25 @@ void ThreadPool::UnlockInput(void) template unsigned ThreadPool::InputSize(void) { - return inputQueue.Size(); + return (unsigned) inputQueue.size(); } template InputType ThreadPool::GetInputAtIndex(unsigned index) { + RakAssert(index < inputQueue.size()); return inputQueue[index]; } template void ThreadPool::RemoveInputAtIndex(unsigned index) { - inputQueue.RemoveAtIndex(index); - inputFunctionQueue.RemoveAtIndex(index); + RakAssert(index < inputQueue.size()); + if (index >= inputQueue.size()) + return; + + // Paired arrays: the same index has to go from both, or every later input is + // run with the wrong callback. + inputQueue.erase(inputQueue.begin() + index); + inputFunctionQueue.erase(inputFunctionQueue.begin() + index); } template void ThreadPool::LockOutput(void) @@ -536,29 +555,34 @@ void ThreadPool::UnlockOutput(void) template unsigned ThreadPool::OutputSize(void) { - return outputQueue.Size(); + return (unsigned) outputQueue.size(); } template OutputType ThreadPool::GetOutputAtIndex(unsigned index) { + RakAssert(index < outputQueue.size()); return outputQueue[index]; } template void ThreadPool::RemoveOutputAtIndex(unsigned index) { - outputQueue.RemoveAtIndex(index); + RakAssert(index < outputQueue.size()); + if (index >= outputQueue.size()) + return; + + outputQueue.erase(outputQueue.begin() + index); } template void ThreadPool::ClearInput(void) { - inputQueue.Clear(_FILE_AND_LINE_); - inputFunctionQueue.Clear(_FILE_AND_LINE_); + inputQueue.clear(); + inputFunctionQueue.clear(); } template void ThreadPool::ClearOutput(void) { - outputQueue.Clear(_FILE_AND_LINE_); + outputQueue.clear(); } template bool ThreadPool::IsWorking(void) diff --git a/Source/src/ds_table.cpp b/Source/src/ds_table.cpp index 5a6e3c368..f1c51863d 100644 --- a/Source/src/ds_table.cpp +++ b/Source/src/ds_table.cpp @@ -429,11 +429,11 @@ Table::Row* Table::AddRow(unsigned rowId, DataStructures::List &initialCe } return newRow; } -Table::Row* Table::AddRowColumns(unsigned rowId, Row *row, DataStructures::List columnIndices) +Table::Row* Table::AddRowColumns(unsigned rowId, Row *row, std::vector columnIndices) { Row *newRow = MafiaNet::OP_NEW( _FILE_AND_LINE_ ); unsigned columnIndex; - for (columnIndex=0; columnIndex < columnIndices.Size(); columnIndex++) + for (columnIndex=0; columnIndex < columnIndices.size(); columnIndex++) { if (row->cells[columnIndices[columnIndex]]->isEmpty==false) { @@ -631,7 +631,7 @@ Table::Row* Table::GetRowByIndex(unsigned rowIndex, unsigned *key) const void Table::QueryTable(unsigned *columnIndicesSubset, unsigned numColumnSubset, FilterQuery *inclusionFilters, unsigned numInclusionFilters, unsigned *rowIds, unsigned numRowIDs, Table *result) { unsigned i; - DataStructures::List columnIndicesToReturn; + std::vector columnIndicesToReturn; // Clear the result table. result->Clear(); @@ -641,25 +641,25 @@ void Table::QueryTable(unsigned *columnIndicesSubset, unsigned numColumnSubset, for (i=0; i < numColumnSubset; i++) { if (columnIndicesSubset[i]AddColumn(columns[columnIndicesToReturn[i]].columnName,columns[columnIndicesToReturn[i]].columnType); } // Get the column indices of the filter queries. - DataStructures::List inclusionFilterColumnIndices; + std::vector inclusionFilterColumnIndices; if (inclusionFilters && numInclusionFilters>0) { for (i=0; i < numInclusionFilters; i++) @@ -667,9 +667,9 @@ void Table::QueryTable(unsigned *columnIndicesSubset, unsigned numColumnSubset, if (inclusionFilters[i].columnName[0]) inclusionFilters[i].columnIndex=ColumnIndex(inclusionFilters[i].columnName); if (inclusionFilters[i].columnIndex &inclusionFilterColumnIndices, DataStructures::List &columnIndicesToReturn, unsigned key, Table::Row* row, FilterQuery *inclusionFilters, Table *result) +void Table::QueryRow(std::vector &inclusionFilterColumnIndices, std::vector &columnIndicesToReturn, unsigned key, Table::Row* row, FilterQuery *inclusionFilters, Table *result) { bool pass=false; unsigned columnIndex; unsigned j; // If no inclusion filters, just add the row - if (inclusionFilterColumnIndices.Size()==0) + if (inclusionFilterColumnIndices.size()==0) { result->AddRowColumns(key, row, columnIndicesToReturn); } else { // Go through all inclusion filters. Only add this row if all filters pass. - for (j=0; jcells[columnIndex]->isEmpty==false ) @@ -852,7 +852,7 @@ void Table::QueryRow(DataStructures::List &inclusionFilterColumnIndice static Table::SortQuery *_sortQueries; static unsigned _numSortQueries; -static DataStructures::List *_columnIndices; +static std::vector *_columnIndices; static DataStructures::List *_columns; int RowSort(Table::Row* const &first, Table::Row* const &second) // first is the one inserting, second is the one already there. { @@ -913,7 +913,7 @@ void Table::SortTable(Table::SortQuery *sortQueries, unsigned numSortQueries, Ta { unsigned i; unsigned outLength; - DataStructures::List columnIndices; + std::vector columnIndices; _sortQueries=sortQueries; _numSortQueries=numSortQueries; _columnIndices=&columnIndices; @@ -924,11 +924,11 @@ void Table::SortTable(Table::SortQuery *sortQueries, unsigned numSortQueries, Ta { if (sortQueries[i].columnIndex::const_iterator it; diff --git a/Source/src/message_filter.cpp b/Source/src/message_filter.cpp index 38dcec14a..32f01b15c 100644 --- a/Source/src/message_filter.cpp +++ b/Source/src/message_filter.cpp @@ -17,6 +17,7 @@ #if _RAKNET_SUPPORT_MessageFilter==1 #include "mafianet/message_filter.h" +#include #include "mafianet/assert.h" #include "mafianet/get_time.h" #include "mafianet/message_identifiers.h" @@ -51,6 +52,19 @@ MessageFilter::~MessageFilter() { Clear(); } +unsigned int MessageFilter::IndexForFilterSetID(int filterSetID, bool *found) const +{ + // filterList is sorted ascending by filterSetID; GetFilterSetIDByIndex publishes the index. + std::vector::const_iterator it = std::lower_bound( + filterList.begin(), filterList.end(), filterSetID, + [](FilterSet * const &candidate, int key) + { + return candidate->filterSetID < key; + }); + unsigned int i = (unsigned int) (it - filterList.begin()); + *found = (it != filterList.end() && (*it)->filterSetID == filterSetID); + return i; +} void MessageFilter::SetAutoAddNewConnectionsToFilter(int filterSetID) { autoAddNewConnectionsToFilter=filterSetID; @@ -123,11 +137,12 @@ int MessageFilter::GetSystemFilterSet(AddressOrGUID systemAddress) // else // return systemList[index].filter->filterSetID; - DataStructures::HashIndex index = systemList.GetIndexOf(systemAddress); - if (index.IsInvalid()) + std::unordered_map::const_iterator it = + systemList.find(systemAddress); + if (it == systemList.end()) return -1; else - return systemList.ItemAtIndex(index).filter->filterSetID; + return it->second.filter->filterSetID; } void MessageFilter::SetSystemFilterSet(AddressOrGUID addressOrGUID, int filterSetID) { @@ -136,8 +151,9 @@ void MessageFilter::SetSystemFilterSet(AddressOrGUID addressOrGUID, int filterSe // bool objectExists; // unsigned index = systemList.GetIndexFromKey(addressOrGUID, &objectExists); // if (objectExists==false) - DataStructures::HashIndex index = systemList.GetIndexOf(addressOrGUID); - if (index.IsInvalid()) + std::unordered_map::iterator index = + systemList.find(addressOrGUID); + if (index == systemList.end()) { if (filterSetID<0) return; @@ -147,19 +163,19 @@ void MessageFilter::SetSystemFilterSet(AddressOrGUID addressOrGUID, int filterSe // filteredSystem.addressOrGUID=addressOrGUID; filteredSystem.timeEnteredThisSet= MafiaNet::GetTimeMS(); // systemList.Insert(addressOrGUID, filteredSystem, true, _FILE_AND_LINE_); - systemList.Push(addressOrGUID,filteredSystem,_FILE_AND_LINE_); + systemList.insert(std::make_pair(addressOrGUID, filteredSystem)); } else { if (filterSetID>=0) { FilterSet *filterSet = GetFilterSetByID(filterSetID); - systemList.ItemAtIndex(index).timeEnteredThisSet= MafiaNet::GetTimeMS(); - systemList.ItemAtIndex(index).filter=filterSet; + index->second.timeEnteredThisSet= MafiaNet::GetTimeMS(); + index->second.filter=filterSet; } else { - systemList.RemoveAtIndex(index, _FILE_AND_LINE_); + systemList.erase(index); } } } @@ -167,24 +183,21 @@ unsigned MessageFilter::GetSystemCount(int filterSetID) const { if (filterSetID==-1) { - return systemList.Size(); + return systemList.size(); } else { - unsigned i; unsigned count=0; - DataStructures::List< FilteredSystem > itemList; - DataStructures::List< AddressOrGUID > keyList; - systemList.GetAsList(itemList, keyList, _FILE_AND_LINE_); - for (i=0; i < itemList.Size(); i++) - if (itemList[i].filter->filterSetID==filterSetID) + for (std::unordered_map::const_iterator + it = systemList.begin(); it != systemList.end(); ++it) + if (it->second.filter->filterSetID==filterSetID) ++count; return count; } } unsigned MessageFilter::GetFilterSetCount(void) const { - return filterList.Size(); + return filterList.size(); } int MessageFilter::GetFilterSetIDByIndex(unsigned index) { @@ -195,28 +208,26 @@ void MessageFilter::DeleteFilterSet(int filterSetID) FilterSet *filterSet; bool objectExists; unsigned i,index; - index = filterList.GetIndexFromKey(filterSetID, &objectExists); + index = IndexForFilterSetID(filterSetID, &objectExists); if (objectExists) { filterSet=filterList[index]; DeallocateFilterSet(filterSet); - filterList.RemoveAtIndex(index); + filterList.erase(filterList.begin() + index); - DataStructures::List< FilteredSystem > itemList; - DataStructures::List< AddressOrGUID > keyList; - systemList.GetAsList(itemList, keyList, _FILE_AND_LINE_); - for (i=0; i < itemList.Size(); i++) + for (std::unordered_map::iterator + it = systemList.begin(); it != systemList.end(); ) { - if (itemList[i].filter==filterSet) - { - systemList.Remove(keyList[i], _FILE_AND_LINE_); - } + if (it->second.filter==filterSet) + it = systemList.erase(it); + else + ++it; } /* // Don't reference this pointer any longer i=0; - while (i < systemList.Size()) + while (i < systemList.size()) { if (systemList[i].filter==filterSet) systemList.RemoveAtIndex(i); @@ -229,10 +240,10 @@ void MessageFilter::DeleteFilterSet(int filterSetID) void MessageFilter::Clear(void) { unsigned i; - systemList.Clear(_FILE_AND_LINE_); - for (i=0; i < filterList.Size(); i++) + systemList.clear(); + for (i=0; i < filterList.size(); i++) DeallocateFilterSet(filterList[i]); - filterList.Clear(false, _FILE_AND_LINE_); + filterList.clear(); } void MessageFilter::DeallocateFilterSet(FilterSet* filterSet) { @@ -243,7 +254,7 @@ FilterSet* MessageFilter::GetFilterSetByID(int filterSetID) RakAssert(filterSetID>=0); bool objectExists; unsigned index; - index = filterList.GetIndexFromKey(filterSetID, &objectExists); + index = IndexForFilterSetID(filterSetID, &objectExists); if (objectExists) return filterList[index]; else @@ -260,7 +271,7 @@ FilterSet* MessageFilter::GetFilterSetByID(int filterSetID) newFilterSet->invalidMessageCallback=0; newFilterSet->timeoutCallback=0; newFilterSet->timeoutUserData=0; - filterList.Insert(filterSetID, newFilterSet, true, _FILE_AND_LINE_); + filterList.insert(filterList.begin() + index, newFilterSet); return newFilterSet; } } @@ -290,12 +301,21 @@ void MessageFilter::Update(void) MafiaNet::Time curTime = MafiaNet::GetTime(); if (GreaterThan(curTime - 1000, whenLastTimeoutCheck)) { - DataStructures::List< FilteredSystem > itemList; - DataStructures::List< AddressOrGUID > keyList; - systemList.GetAsList(itemList, keyList, _FILE_AND_LINE_); + // A snapshot, because the callbacks below can close connections and so reach back into + // systemList; the DataStructures::List copy this replaces had the same effect. + std::vector itemList; + std::vector keyList; + itemList.reserve(systemList.size()); + keyList.reserve(systemList.size()); + for (std::unordered_map::const_iterator + it = systemList.begin(); it != systemList.end(); ++it) + { + itemList.push_back(it->second); + keyList.push_back(it->first); + } unsigned int index; - for (index=0; index < itemList.Size(); index++) + for (index=0; index < itemList.size(); index++) { if (itemList[index].filter && itemList[index].filter->maxMemberTimeMS>0 && @@ -317,7 +337,7 @@ void MessageFilter::Update(void) tcpInterface->CloseConnection(keyList[index].systemAddress); #endif - systemList.Remove(keyList[index], _FILE_AND_LINE_); + systemList.erase(keyList[index]); } } @@ -335,7 +355,7 @@ void MessageFilter::OnNewConnection(const SystemAddress &systemAddress, RakNetGU aog.systemAddress=systemAddress; // New system, automatically assign to filter set if appropriate - if (autoAddNewConnectionsToFilter>=0 && systemList.HasData(aog)==false) + if (autoAddNewConnectionsToFilter>=0 && systemList.find(aog)==systemList.end()) SetSystemFilterSet(aog, autoAddNewConnectionsToFilter); } void MessageFilter::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) @@ -348,11 +368,10 @@ void MessageFilter::OnClosedConnection(const SystemAddress &systemAddress, RakNe aog.systemAddress=systemAddress; // Lost system, remove from the list - systemList.Remove(aog, _FILE_AND_LINE_); + systemList.erase(aog); } PluginReceiveResult MessageFilter::OnReceive(Packet *packet) { - DataStructures::HashIndex index; unsigned char messageId; switch (packet->data[0]) @@ -385,12 +404,13 @@ void MessageFilter::OnClosedConnection(const SystemAddress &systemAddress, RakNe messageId=packet->data[0]; // If this system is filtered, check if this message is allowed. If not allowed, return RR_STOP_PROCESSING_AND_DEALLOCATE // index = systemList.GetIndexFromKey(packet->addressOrGUID, &objectExists); - index = systemList.GetIndexOf(packet); - if (index.IsInvalid()) + std::unordered_map::const_iterator sysIt = + systemList.find(packet); + if (sysIt == systemList.end()) break; - if (systemList.ItemAtIndex(index).filter->allowedIDs[messageId]==false) + if (sysIt->second.filter->allowedIDs[messageId]==false) { - OnInvalidMessage(systemList.ItemAtIndex(index).filter, packet, packet->data[0]); + OnInvalidMessage(sysIt->second.filter, packet, packet->data[0]); return RR_STOP_PROCESSING_AND_DEALLOCATE; } if (packet->data[0]==ID_RPC_PLUGIN) @@ -399,9 +419,9 @@ void MessageFilter::OnClosedConnection(const SystemAddress &systemAddress, RakNe bsIn.IgnoreBytes(2); MafiaNet::RakString functionName; bsIn.ReadCompressed(functionName); - if (systemList.ItemAtIndex(index).filter->allowedRPC4.HasData(functionName)==false) + if (sysIt->second.filter->allowedRPC4.HasData(functionName)==false) { - OnInvalidMessage(systemList.ItemAtIndex(index).filter, packet, packet->data[0]); + OnInvalidMessage(sysIt->second.filter, packet, packet->data[0]); return RR_STOP_PROCESSING_AND_DEALLOCATE; } } diff --git a/Source/src/packetized_tcp.cpp b/Source/src/packetized_tcp.cpp index a1ba1bcf7..b0fc128f9 100644 --- a/Source/src/packetized_tcp.cpp +++ b/Source/src/packetized_tcp.cpp @@ -35,15 +35,19 @@ PacketizedTCP::PacketizedTCP() } PacketizedTCP::~PacketizedTCP() { + // Anything still in waitingPackets was leaked: only Stop() freed it, and + // nothing calls Stop() on the way out. + ClearWaitingPackets(); ClearAllConnections(); } void PacketizedTCP::Stop(void) { - unsigned int i; TCPInterface::Stop(); - for (i=0; i < waitingPackets.Size(); i++) - DeallocatePacket(waitingPackets[i]); + // The pointers used to be left in the queue after being freed, so a second + // Stop() double-freed them and a Receive() after Stop() handed one back to the + // caller. + ClearWaitingPackets(); ClearAllConnections(); } @@ -110,27 +114,27 @@ void PacketizedTCP::PushNotificationsToQueues(void) sa = TCPInterface::HasNewIncomingConnection(); if (sa!=UNASSIGNED_SYSTEM_ADDRESS) { - _newIncomingConnections.Push(sa, _FILE_AND_LINE_ ); + _newIncomingConnections.push_back(sa); AddToConnectionList(sa); } sa = TCPInterface::HasFailedConnectionAttempt(); if (sa!=UNASSIGNED_SYSTEM_ADDRESS) { - _failedConnectionAttempts.Push(sa, _FILE_AND_LINE_ ); + _failedConnectionAttempts.push_back(sa); } sa = TCPInterface::HasLostConnection(); if (sa!=UNASSIGNED_SYSTEM_ADDRESS) { - _lostConnections.Push(sa, _FILE_AND_LINE_ ); + _lostConnections.push_back(sa); RemoveFromConnectionList(sa); } sa = TCPInterface::HasCompletedConnectionAttempt(); if (sa!=UNASSIGNED_SYSTEM_ADDRESS) { - _completedConnectionAttempts.Push(sa, _FILE_AND_LINE_ ); + _completedConnectionAttempts.push_back(sa); AddToConnectionList(sa); } } @@ -139,7 +143,7 @@ Packet* PacketizedTCP::Receive( void ) PushNotificationsToQueues(); unsigned int i; - for (i=0; i < messageHandlerList.Size(); i++) + for (i=0; i < messageHandlerList.size(); i++) messageHandlerList[i]->Update(); Packet *outgoingPacket=ReturnOutgoingPacket(); @@ -148,15 +152,12 @@ Packet* PacketizedTCP::Receive( void ) Packet *incomingPacket; incomingPacket = TCPInterface::ReceiveInt(); - unsigned int index; while (incomingPacket) { - if (connections.Has(incomingPacket->systemAddress)) - index = connections.GetIndexAtKey(incomingPacket->systemAddress); - else - index=(unsigned int) -1; - if ((unsigned int)index==(unsigned int)-1) + std::map::iterator connection = + connections.find(incomingPacket->systemAddress); + if (connection == connections.end()) { DeallocatePacket(incomingPacket); incomingPacket = TCPInterface::ReceiveInt(); @@ -168,9 +169,8 @@ Packet* PacketizedTCP::Receive( void ) { // Came from network SystemAddress systemAddressFromPacket; - if (index < connections.Size()) { - DataStructures::ByteQueue *bq = connections[index]; + DataStructures::ByteQueue *bq = connection->second; // Buffer data bq->WriteBytes((const char*) incomingPacket->data,incomingPacket->length, _FILE_AND_LINE_); systemAddressFromPacket=incomingPacket->systemAddress; @@ -201,7 +201,7 @@ Packet* PacketizedTCP::Receive( void ) } bq->ReadBytes((char*) outgoingPacket->data,dataLength,false); - waitingPackets.Push(outgoingPacket, _FILE_AND_LINE_ ); + waitingPackets.push_back(outgoingPacket); // Peek the header to see if a full message is waiting if (bq->ReadBytes((char*) &dataLength,sizeof(PTCPHeader),true)) @@ -227,7 +227,10 @@ Packet* PacketizedTCP::Receive( void ) sizeof(unsigned int)*2 + sizeof(unsigned int) + 65536; - outgoingPacket->bitSize=BYTES_TO_BITS(incomingPacket->length); + // This took its bit size from the *incoming* packet, which has + // nothing to do with the progress packet's own length, so + // bitSize and length disagreed on every ID_DOWNLOAD_PROGRESS. + outgoingPacket->bitSize=BYTES_TO_BITS(outgoingPacket->length); outgoingPacket->guid=UNASSIGNED_RAKNET_GUID; outgoingPacket->systemAddress=incomingPacket->systemAddress; outgoingPacket->deleteData=false; @@ -250,7 +253,7 @@ Packet* PacketizedTCP::Receive( void ) bq->ReadBytes((char*) outgoingPacket->data+sizeof(MessageID)+sizeof(unsigned int)*3,oneChunkSize,true); bq->DecrementReadOffset(sizeof(PTCPHeader)); - waitingPackets.Push(outgoingPacket, _FILE_AND_LINE_ ); + waitingPackets.push_back(outgoingPacket); } } @@ -260,7 +263,7 @@ Packet* PacketizedTCP::Receive( void ) incomingPacket=0; } else - waitingPackets.Push(incomingPacket, _FILE_AND_LINE_ ); + waitingPackets.push_back(incomingPacket); incomingPacket = TCPInterface::ReceiveInt(); } @@ -271,11 +274,12 @@ Packet *PacketizedTCP::ReturnOutgoingPacket(void) { Packet *outgoingPacket=0; unsigned int i; - while (outgoingPacket==0 && waitingPackets.IsEmpty()==false) + while (outgoingPacket==0 && waitingPackets.empty()==false) { - outgoingPacket=waitingPackets.Pop(); + outgoingPacket=waitingPackets.front(); + waitingPackets.pop_front(); PluginReceiveResult pluginResult; - for (i=0; i < messageHandlerList.Size(); i++) + for (i=0; i < messageHandlerList.size(); i++) { pluginResult=messageHandlerList[i]->OnReceive(outgoingPacket); if (pluginResult==RR_STOP_PROCESSING_AND_DEALLOCATE) @@ -303,59 +307,89 @@ void PacketizedTCP::RemoveFromConnectionList(const SystemAddress &sa) { if (sa==UNASSIGNED_SYSTEM_ADDRESS) return; - if (connections.Has(sa)) + std::map::iterator it = connections.find(sa); + if (it != connections.end()) { - unsigned int index = connections.GetIndexAtKey(sa); - if (index!=(unsigned int)-1) - { - MafiaNet::OP_DELETE(connections[index],_FILE_AND_LINE_); - connections.RemoveAtIndex(index); - } + MafiaNet::OP_DELETE(it->second,_FILE_AND_LINE_); + connections.erase(it); } } void PacketizedTCP::AddToConnectionList(const SystemAddress &sa) { if (sa==UNASSIGNED_SYSTEM_ADDRESS) return; - connections.SetNew(sa, MafiaNet::OP_NEW(_FILE_AND_LINE_)); + // DataStructures::Map::SetNew asserted on a duplicate key under _DEBUG and then + // replaced the value, which leaked the old buffer. Keep the assert, and if it + // does happen, free what was there instead of dropping it on the floor. + std::map::iterator existing = connections.find(sa); + RakAssert(existing == connections.end()); + if (existing != connections.end()) + { + MafiaNet::OP_DELETE(existing->second,_FILE_AND_LINE_); + existing->second = MafiaNet::OP_NEW(_FILE_AND_LINE_); + return; + } + connections[sa] = MafiaNet::OP_NEW(_FILE_AND_LINE_); +} +void PacketizedTCP::ClearWaitingPackets(void) +{ + for (size_t i=0; i < waitingPackets.size(); i++) + DeallocatePacket(waitingPackets[i]); + waitingPackets.clear(); } void PacketizedTCP::ClearAllConnections(void) { - unsigned int i; - for (i=0; i < connections.Size(); i++) - MafiaNet::OP_DELETE(connections[i],_FILE_AND_LINE_); - connections.Clear(); + for (std::map::iterator it = connections.begin(); + it != connections.end(); ++it) + MafiaNet::OP_DELETE(it->second,_FILE_AND_LINE_); + connections.clear(); } SystemAddress PacketizedTCP::HasCompletedConnectionAttempt(void) { PushNotificationsToQueues(); - if (_completedConnectionAttempts.IsEmpty()==false) - return _completedConnectionAttempts.Pop(); + if (_completedConnectionAttempts.empty()==false) + { + const SystemAddress sa = _completedConnectionAttempts.front(); + _completedConnectionAttempts.pop_front(); + return sa; + } return UNASSIGNED_SYSTEM_ADDRESS; } SystemAddress PacketizedTCP::HasFailedConnectionAttempt(void) { PushNotificationsToQueues(); - if (_failedConnectionAttempts.IsEmpty()==false) - return _failedConnectionAttempts.Pop(); + if (_failedConnectionAttempts.empty()==false) + { + const SystemAddress sa = _failedConnectionAttempts.front(); + _failedConnectionAttempts.pop_front(); + return sa; + } return UNASSIGNED_SYSTEM_ADDRESS; } SystemAddress PacketizedTCP::HasNewIncomingConnection(void) { PushNotificationsToQueues(); - if (_newIncomingConnections.IsEmpty()==false) - return _newIncomingConnections.Pop(); + if (_newIncomingConnections.empty()==false) + { + const SystemAddress sa = _newIncomingConnections.front(); + _newIncomingConnections.pop_front(); + return sa; + } return UNASSIGNED_SYSTEM_ADDRESS; } SystemAddress PacketizedTCP::HasLostConnection(void) { PushNotificationsToQueues(); - if (_lostConnections.IsEmpty()==false) - return _lostConnections.Pop(); + if (_lostConnections.empty()==false) + { + const SystemAddress sa = _lostConnections.front(); + _lostConnections.pop_front(); + return sa; + } return UNASSIGNED_SYSTEM_ADDRESS; } diff --git a/Source/src/peer.cpp b/Source/src/peer.cpp index 68456a88a..919dfca16 100644 --- a/Source/src/peer.cpp +++ b/Source/src/peer.cpp @@ -519,7 +519,7 @@ StartupResult RakPeer::Startup( unsigned int maxConnections, SocketDescriptor *s RakAssert("TODO" && 0); } - socketList.Push(r2, _FILE_AND_LINE_ ); + socketList.push_back(r2); } @@ -787,32 +787,32 @@ void RakPeer::DisableSecurity( void ) void RakPeer::AddToSecurityExceptionList(const char *ip) { securityExceptionMutex.Lock(); - securityExceptionList.Insert(RakString(ip), _FILE_AND_LINE_); + securityExceptionList.push_back(RakString(ip)); securityExceptionMutex.Unlock(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- void RakPeer::RemoveFromSecurityExceptionList(const char *ip) { - if (securityExceptionList.Size()==0) + if (securityExceptionList.size()==0) return; if (ip==0) { securityExceptionMutex.Lock(); - securityExceptionList.Clear(false, _FILE_AND_LINE_); + securityExceptionList.clear(); securityExceptionMutex.Unlock(); } else { unsigned i=0; securityExceptionMutex.Lock(); - while (i < securityExceptionList.Size()) + while (i < securityExceptionList.size()) { if (securityExceptionList[i].IPAddressMatch(ip)) { - securityExceptionList[i]=securityExceptionList[securityExceptionList.Size()-1]; - securityExceptionList.RemoveAtIndex(securityExceptionList.Size()-1); + securityExceptionList[i]=securityExceptionList.back(); + securityExceptionList.pop_back(); } else i++; @@ -823,12 +823,12 @@ void RakPeer::RemoveFromSecurityExceptionList(const char *ip) // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- bool RakPeer::IsInSecurityExceptionList(const char *ip) { - if (securityExceptionList.Size()==0) + if (securityExceptionList.size()==0) return false; unsigned i=0; securityExceptionMutex.Lock(); - for (; i < securityExceptionList.Size(); i++) + for (; i < securityExceptionList.size(); i++) { if (securityExceptionList[i].IPAddressMatch(ip)) { @@ -940,7 +940,7 @@ void RakPeer::GetIncomingPassword( char* passwordData, int *passwordDataLength ConnectionAttemptResult RakPeer::Connect( const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength, PublicKey *publicKey, unsigned connectionSocketIndex, unsigned sendConnectionAttemptCount, unsigned timeBetweenSendConnectionAttemptsMS, MafiaNet::TimeMS timeoutTime ) { // If endThreads is true here you didn't call Startup() first. - if ( host == 0 || endThreads || connectionSocketIndex>=socketList.Size() ) + if ( host == 0 || endThreads || connectionSocketIndex>=socketList.size() ) return INVALID_PARAMETER; RakAssert(remotePort!=0); @@ -1049,7 +1049,7 @@ void RakPeer::Shutdown( unsigned int blockDuration, unsigned char orderingChanne // MafiaNet::TimeMS timeout; #if RAKPEER_USER_THREADED!=1 - for (i=0; i < socketList.Size(); i++) + for (i=0; i < socketList.size(); i++) { if (socketList[i]->IsBerkleySocket()) { @@ -1059,7 +1059,7 @@ void RakPeer::Shutdown( unsigned int blockDuration, unsigned char orderingChanne /* // Get recvfrom to unblock - for (i=0; i < socketList.Size(); i++) + for (i=0; i < socketList.size(); i++) { if (SocketLayer::SendTo(socketList[i], (const char*) &i,1,socketList[i]->GetBoundAddress(), _FILE_AND_LINE_)!=0) break; @@ -1078,7 +1078,7 @@ void RakPeer::Shutdown( unsigned int blockDuration, unsigned char orderingChanne while ( isRecvFromLoopThreadActive.GetValue()>0 && MafiaNet::GetTimeMS()GetBoundAddress(), _FILE_AND_LINE_); } @@ -1087,7 +1087,7 @@ void RakPeer::Shutdown( unsigned int blockDuration, unsigned char orderingChanne } */ - for (i=0; i < socketList.Size(); i++) + for (i=0; i < socketList.size(); i++) { if (socketList[i]->IsBerkleySocket()) { @@ -1122,9 +1122,9 @@ void RakPeer::Shutdown( unsigned int blockDuration, unsigned char orderingChanne // Free any packets the user didn't deallocate packetReturnMutex.Lock(); - for (i=0; i < packetReturnQueue.Size(); i++) + for (i=0; i < packetReturnQueue.size(); i++) DeallocatePacket(packetReturnQueue[i]); - packetReturnQueue.Clear(_FILE_AND_LINE_); + packetReturnQueue.clear(); packetReturnMutex.Unlock(); packetAllocationPoolMutex.Lock(); packetAllocationPool.Clear(_FILE_AND_LINE_); @@ -1464,7 +1464,7 @@ Packet* RakPeer::Receive( void ) /* #if RAKPEER_SINGLE_THREADED==1 RakPeer::RecvFromStruct *recvFromStruct; - for (i=0; i < socketList.Size(); i++) + for (i=0; i < socketList.size(); i++) { for(;;) { @@ -1507,10 +1507,13 @@ Packet* RakPeer::Receive( void ) do { packetReturnMutex.Lock(); - if (packetReturnQueue.IsEmpty()) + if (packetReturnQueue.empty()) packet=0; else - packet = packetReturnQueue.Pop(); + { + packet = packetReturnQueue.front(); + packetReturnQueue.pop_front(); + } packetReturnMutex.Unlock(); if (packet==0) return 0; @@ -1644,7 +1647,7 @@ void RakPeer::CloseConnection( const AddressOrGUID target, bool sendDisconnectio // the BCS_CLOSE_CONNECTION path below. With no socket at all there is nothing to // close, so bail out. RakNetSocket2 *closeSocket = (remoteSystemListIndex != -1) ? remoteSystemList[remoteSystemListIndex].rakNetSocket : nullptr; - if (closeSocket == nullptr && socketList.Size() > 0) + if (closeSocket == nullptr && socketList.size() > 0) closeSocket = socketList[0]; if (closeSocket == nullptr) return; @@ -1676,7 +1679,7 @@ void RakPeer::CancelConnectionAttempt( const SystemAddress target ) // Cancel pending connection attempt, if there is one i=0; requestedConnectionQueueMutex.Lock(); - while (i < requestedConnectionQueue.Size()) + while (i < requestedConnectionQueue.size()) { if (requestedConnectionQueue[i]->systemAddress==target) { @@ -1685,7 +1688,7 @@ void RakPeer::CancelConnectionAttempt( const SystemAddress target ) MafiaNet::OP_DELETE(requestedConnectionQueue[i]->client_handshake, _FILE_AND_LINE_ ); #endif MafiaNet::OP_DELETE(requestedConnectionQueue[i], _FILE_AND_LINE_ ); - requestedConnectionQueue.RemoveAtIndex(i); + requestedConnectionQueue.erase(requestedConnectionQueue.begin() + i); break; } else @@ -1703,7 +1706,7 @@ ConnectionState RakPeer::GetConnectionState(const AddressOrGUID systemIdentifier { unsigned int i=0; requestedConnectionQueueMutex.Lock(); - for (; i < requestedConnectionQueue.Size(); i++) + for (; i < requestedConnectionQueue.size(); i++) { if (requestedConnectionQueue[i]->systemAddress==systemIdentifier.systemAddress) { @@ -1856,7 +1859,7 @@ void RakPeer::AddToBanList( const char *IP, MafiaNet::TimeMS milliseconds ) banListMutex.Lock(); - for ( ; index < banList.Size(); index++ ) + for ( ; index < banList.size(); index++ ) { if ( strcmp( IP, banList[ index ]->IP ) == 0 ) { @@ -1880,7 +1883,7 @@ void RakPeer::AddToBanList( const char *IP, MafiaNet::TimeMS milliseconds ) banStruct->timeout=time+milliseconds; strcpy_s( banStruct->IP, 16, IP ); banListMutex.Lock(); - banList.Insert( banStruct, _FILE_AND_LINE_ ); + banList.push_back(banStruct); banListMutex.Unlock(); } @@ -1905,13 +1908,13 @@ void RakPeer::RemoveFromBanList( const char *IP ) banListMutex.Lock(); - for ( ; index < banList.Size(); index++ ) + for ( ; index < banList.size(); index++ ) { if ( strcmp( IP, banList[ index ]->IP ) == 0 ) { temp = banList[ index ]; - banList[ index ] = banList[ banList.Size() - 1 ]; - banList.RemoveAtIndex( banList.Size() - 1 ); + banList[ index ] = banList.back(); + banList.pop_back(); break; } } @@ -1936,13 +1939,13 @@ void RakPeer::ClearBanList( void ) index = 0; banListMutex.Lock(); - for ( ; index < banList.Size(); index++ ) + for ( ; index < banList.size(); index++ ) { rakFree_Ex(banList[ index ]->IP, _FILE_AND_LINE_ ); MafiaNet::OP_DELETE(banList[ index ], _FILE_AND_LINE_); } - banList.Clear(false, _FILE_AND_LINE_); + banList.clear(); banListMutex.Unlock(); } @@ -1974,21 +1977,21 @@ bool RakPeer::IsBanned( const char *IP ) banListIndex = 0; - if ( banList.Size() == 0 ) + if ( banList.size() == 0 ) return false; // Skip the mutex if possible time = MafiaNet::GetTimeMS(); banListMutex.Lock(); - while ( banListIndex < banList.Size() ) + while ( banListIndex < banList.size() ) { if (banList[ banListIndex ]->timeout>0 && banList[ banListIndex ]->timeoutIP, _FILE_AND_LINE_ ); MafiaNet::OP_DELETE(temp, _FILE_AND_LINE_); } @@ -2075,7 +2078,7 @@ bool RakPeer::Ping( const char* host, unsigned short remotePort, bool onlyReplyO return false; // If this assert hits then Startup wasn't called or the call failed. - RakAssert(connectionSocketIndex < socketList.Size()); + RakAssert(connectionSocketIndex < socketList.size()); // if ( IsActive() == false ) // return; @@ -2939,9 +2942,9 @@ void RakPeer::PushBackPacket( Packet *packet, bool pushAtHead) packetReturnMutex.Lock(); if (pushAtHead) - packetReturnQueue.PushAtHead(packet,0,_FILE_AND_LINE_); + packetReturnQueue.push_front(packet); else - packetReturnQueue.Push(packet,_FILE_AND_LINE_); + packetReturnQueue.push_back(packet); packetReturnMutex.Unlock(); } @@ -2987,8 +2990,11 @@ RakNetSocket2* RakPeer::GetSocket( const SystemAddress target ) sqo = socketQueryOutput.Pop(); if (sqo) { - output=sqo->sockets; - sqo->sockets.Clear(false, _FILE_AND_LINE_); + // output is a fresh local filled at most once, and sockets is cleared at the + // top of GetSockets, so neither needs clearing here. + for (size_t socketIndex = 0; socketIndex < sqo->sockets.size(); socketIndex++) + output.Push(sqo->sockets[socketIndex], _FILE_AND_LINE_); + sqo->sockets.clear(); socketQueryOutput.Deallocate(sqo, _FILE_AND_LINE_); if (output.Size()) return output[0]; @@ -3024,8 +3030,9 @@ void RakPeer::GetSockets( DataStructures::List &sockets ) sqo = socketQueryOutput.Pop(); if (sqo) { - sockets=sqo->sockets; - sqo->sockets.Clear(false, _FILE_AND_LINE_); + for (size_t socketIndex = 0; socketIndex < sqo->sockets.size(); socketIndex++) + sockets.Push(sqo->sockets[socketIndex], _FILE_AND_LINE_); + sqo->sockets.clear(); socketQueryOutput.Deallocate(sqo, _FILE_AND_LINE_); return; } @@ -3113,7 +3120,7 @@ bool RakPeer::SendOutOfBand(const char *host, unsigned short remotePort, const c return false; // If this assert hits then Startup wasn't called or the call failed. - RakAssert(connectionSocketIndex < socketList.Size()); + RakAssert(connectionSocketIndex < socketList.size()); // This is a security measure. Don't send data longer than this value RakAssert(dataLength <= (MAX_OFFLINE_DATA_LENGTH + sizeof(unsigned char)+sizeof(MafiaNet::Time)+RakNetGUID::size()+sizeof(OFFLINE_MESSAGE_DATA_ID))); @@ -3241,7 +3248,7 @@ unsigned int RakPeer::GetReceiveBufferSize(void) { unsigned int size; packetReturnMutex.Lock(); - size=packetReturnQueue.Size(); + size=packetReturnQueue.size(); packetReturnMutex.Unlock(); return size; } @@ -3413,7 +3420,7 @@ ConnectionAttemptResult RakPeer::SendConnectionRequest( const char* host, unsign // Return false if already pending, else push on queue unsigned int i=0; requestedConnectionQueueMutex.Lock(); - for (; i < requestedConnectionQueue.Size(); i++) + for (; i < requestedConnectionQueue.size(); i++) { if (requestedConnectionQueue[i]->systemAddress==systemAddress) { @@ -3424,7 +3431,7 @@ ConnectionAttemptResult RakPeer::SendConnectionRequest( const char* host, unsign return CONNECTION_ATTEMPT_ALREADY_IN_PROGRESS; } } - requestedConnectionQueue.Push(rcs, _FILE_AND_LINE_ ); + requestedConnectionQueue.push_back(rcs); requestedConnectionQueueMutex.Unlock(); return CONNECTION_ATTEMPT_STARTED; @@ -3467,7 +3474,7 @@ ConnectionAttemptResult RakPeer::SendConnectionRequest( const char* host, unsign // Return false if already pending, else push on queue unsigned int i=0; requestedConnectionQueueMutex.Lock(); - for (; i < requestedConnectionQueue.Size(); i++) + for (; i < requestedConnectionQueue.size(); i++) { if (requestedConnectionQueue[i]->systemAddress==systemAddress) { @@ -3478,7 +3485,7 @@ ConnectionAttemptResult RakPeer::SendConnectionRequest( const char* host, unsign return CONNECTION_ATTEMPT_ALREADY_IN_PROGRESS; } } - requestedConnectionQueue.Push(rcs, _FILE_AND_LINE_ ); + requestedConnectionQueue.push_back(rcs); requestedConnectionQueueMutex.Unlock(); return CONNECTION_ATTEMPT_STARTED; @@ -4339,17 +4346,23 @@ MafiaNet::TimeMS RakPeer::GetSessionTimeoutFor(RemoteSystemStruct *remoteSystem) // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- void RakPeer::DeallocRNS2RecvStruct(RNS2RecvStruct *s, const char *file, unsigned int line) { + // file/line were only ever the allocation-tracking arguments DataStructures::Queue + // took; std::deque does not. + (void) file; + (void) line; + bufferedPacketsFreePoolMutex.Lock(); - bufferedPacketsFreePool.Push(s, file, line); + bufferedPacketsFreePool.push_back(s); bufferedPacketsFreePoolMutex.Unlock(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- RNS2RecvStruct *RakPeer::AllocRNS2RecvStruct(const char *file, unsigned int line) { bufferedPacketsFreePoolMutex.Lock(); - if (bufferedPacketsFreePool.Size()>0) + if (bufferedPacketsFreePool.empty()==false) { - RNS2RecvStruct *s = bufferedPacketsFreePool.Pop(); + RNS2RecvStruct *s = bufferedPacketsFreePool.front(); + bufferedPacketsFreePool.pop_front(); bufferedPacketsFreePoolMutex.Unlock(); return s; } @@ -4363,13 +4376,19 @@ RNS2RecvStruct *RakPeer::AllocRNS2RecvStruct(const char *file, unsigned int line void RakPeer::ClearBufferedPackets(void) { bufferedPacketsFreePoolMutex.Lock(); - while (bufferedPacketsFreePool.Size()>0) - MafiaNet::OP_DELETE(bufferedPacketsFreePool.Pop(), _FILE_AND_LINE_); + while (bufferedPacketsFreePool.empty()==false) + { + MafiaNet::OP_DELETE(bufferedPacketsFreePool.front(), _FILE_AND_LINE_); + bufferedPacketsFreePool.pop_front(); + } bufferedPacketsFreePoolMutex.Unlock(); bufferedPacketsQueueMutex.Lock(); - while (bufferedPacketsQueue.Size()>0) - MafiaNet::OP_DELETE(bufferedPacketsQueue.Pop(), _FILE_AND_LINE_); + while (bufferedPacketsQueue.empty()==false) + { + MafiaNet::OP_DELETE(bufferedPacketsQueue.front(), _FILE_AND_LINE_); + bufferedPacketsQueue.pop_front(); + } bufferedPacketsQueueMutex.Unlock(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -4380,16 +4399,17 @@ void RakPeer::SetupBufferedPackets(void) void RakPeer::PushBufferedPacket(RNS2RecvStruct * p) { bufferedPacketsQueueMutex.Lock(); - bufferedPacketsQueue.Push(p, _FILE_AND_LINE_); + bufferedPacketsQueue.push_back(p); bufferedPacketsQueueMutex.Unlock(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- RNS2RecvStruct *RakPeer::PopBufferedPacket(void) { bufferedPacketsQueueMutex.Lock(); - if (bufferedPacketsQueue.Size()>0) + if (bufferedPacketsQueue.empty()==false) { - RNS2RecvStruct *s = bufferedPacketsQueue.Pop(); + RNS2RecvStruct *s = bufferedPacketsQueue.front(); + bufferedPacketsQueue.pop_front(); bufferedPacketsQueueMutex.Unlock(); return s; } @@ -4759,16 +4779,19 @@ void RakPeer::ClearSocketQueryOutput(void) // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- void RakPeer::ClearRequestedConnectionList(void) { - DataStructures::Queue freeQueue; + std::deque freeQueue; requestedConnectionQueueMutex.Lock(); - while (requestedConnectionQueue.Size()) - freeQueue.Push(requestedConnectionQueue.Pop(), _FILE_AND_LINE_ ); + while (requestedConnectionQueue.empty()==false) + { + freeQueue.push_back(requestedConnectionQueue.front()); + requestedConnectionQueue.pop_front(); + } requestedConnectionQueueMutex.Unlock(); - unsigned i; - for (i=0; i < freeQueue.Size(); i++) + size_t i; + for (i=0; i < freeQueue.size(); i++) { #if LIBCAT_SECURITY==1 - CAT_AUDIT_PRINTF("AUDIT: In ClearRequestedConnectionList(), Deleting freeQueue index %i client_handshake %x\n", i, freeQueue[i]->client_handshake); + CAT_AUDIT_PRINTF("AUDIT: In ClearRequestedConnectionList(), Deleting freeQueue index %i client_handshake %x\n", (int) i, freeQueue[i]->client_handshake); MafiaNet::OP_DELETE(freeQueue[i]->client_handshake,_FILE_AND_LINE_); #endif MafiaNet::OP_DELETE(freeQueue[i], _FILE_AND_LINE_ ); @@ -4777,7 +4800,7 @@ void RakPeer::ClearRequestedConnectionList(void) inline void RakPeer::AddPacketToProducer(MafiaNet::Packet *p) { packetReturnMutex.Lock(); - packetReturnQueue.Push(p,_FILE_AND_LINE_); + packetReturnQueue.push_back(p); packetReturnMutex.Unlock(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -5098,7 +5121,7 @@ bool ProcessOfflineNetworkPacket( SystemAddress systemAddress, const char *data, bsOut.Write(cookie); rakPeer->requestedConnectionQueueMutex.Lock(); - for (i=0; i < rakPeer->requestedConnectionQueue.Size(); i++) + for (i=0; i < rakPeer->requestedConnectionQueue.size(); i++) { RakPeer::RequestedConnectionStruct *rcs; rcs=rakPeer->requestedConnectionQueue[i]; @@ -5239,7 +5262,7 @@ bool ProcessOfflineNetworkPacket( SystemAddress systemAddress, const char *data, RakPeer::RequestedConnectionStruct *rcs; bool unlock=true; rakPeer->requestedConnectionQueueMutex.Lock(); - for (i=0; i < rakPeer->requestedConnectionQueue.Size(); i++) + for (i=0; i < rakPeer->requestedConnectionQueue.size(); i++) { rcs=rakPeer->requestedConnectionQueue[i]; @@ -5377,11 +5400,11 @@ bool ProcessOfflineNetworkPacket( SystemAddress systemAddress, const char *data, } rakPeer->requestedConnectionQueueMutex.Lock(); - for (unsigned int k=0; k < rakPeer->requestedConnectionQueue.Size(); k++) + for (unsigned int k=0; k < rakPeer->requestedConnectionQueue.size(); k++) { if (rakPeer->requestedConnectionQueue[k]->systemAddress==systemAddress) { - rakPeer->requestedConnectionQueue.RemoveAtIndex(k); + rakPeer->requestedConnectionQueue.erase(rakPeer->requestedConnectionQueue.begin() + k); break; } } @@ -5425,13 +5448,13 @@ bool ProcessOfflineNetworkPacket( SystemAddress systemAddress, const char *data, RakPeer::RequestedConnectionStruct *rcs; bool connectionAttemptCancelled=false; rakPeer->requestedConnectionQueueMutex.Lock(); - for (i=0; i < rakPeer->requestedConnectionQueue.Size(); i++) + for (i=0; i < rakPeer->requestedConnectionQueue.size(); i++) { rcs=rakPeer->requestedConnectionQueue[i]; if (rcs->actionToTake==RakPeer::RequestedConnectionStruct::CONNECT && rcs->systemAddress==systemAddress) { connectionAttemptCancelled=true; - rakPeer->requestedConnectionQueue.RemoveAtIndex(i); + rakPeer->requestedConnectionQueue.erase(rakPeer->requestedConnectionQueue.begin() + i); #if LIBCAT_SECURITY==1 CAT_AUDIT_PRINTF("AUDIT: Connection attempt canceled so deleting rcs->client_handshake object %x\n", rcs->client_handshake); @@ -5823,17 +5846,17 @@ unsigned int RakPeer::GenerateSeedFromGuid(void) void RakPeer::DerefAllSockets(void) { unsigned int i; - for (i=0; i < socketList.Size(); i++) + for (i=0; i < socketList.size(); i++) { MafiaNet::OP_DELETE(socketList[i], _FILE_AND_LINE_); } - socketList.Clear(false, _FILE_AND_LINE_); + socketList.clear(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- unsigned int RakPeer::GetRakNetSocketFromUserConnectionSocketIndex(unsigned int userIndex) const { unsigned int i; - for (i=0; i < socketList.Size(); i++) + for (i=0; i < socketList.size(); i++) { if (socketList[i]->GetUserConnectionSocketIndex()==userIndex) return i; @@ -5933,12 +5956,12 @@ bool RakPeer::RunUpdateCycle(BitStream &updateBitStream ) while ((recvFromStruct=PopBufferedPacket())!=0) { /* - for (socketListIndex=0; socketListIndex < socketList.Size(); socketListIndex++) + for (socketListIndex=0; socketListIndex < socketList.size(); socketListIndex++) { if ((RakNetSocket*) socketList[socketListIndex]==recvFromStruct->s) break; } - if (socketListIndex!=socketList.Size()) + if (socketListIndex!=socketList.size()) */ ProcessNetworkPacket(recvFromStruct->systemAddress, recvFromStruct->data, recvFromStruct->bytesRead, this, recvFromStruct->socket, recvFromStruct->timeRead, updateBitStream); DeallocRNS2RecvStruct(recvFromStruct, _FILE_AND_LINE_); @@ -6031,7 +6054,7 @@ bool RakPeer::RunUpdateCycle(BitStream &updateBitStream ) // RakAssert is a no-op in release (NDEBUG), so guard explicitly and fall back // to the primary socket to avoid dereferencing null in CloseConnectionInternal2. RakNetSocket2 *closeSocket = bcs->socket; - if (closeSocket == nullptr && socketList.Size() > 0) + if (closeSocket == nullptr && socketList.size() > 0) closeSocket = socketList[0]; if (closeSocket != nullptr) CloseConnectionInternal2(bcs->systemIdentifier, false, true, bcs->orderingChannel, bcs->priority, *closeSocket); @@ -6060,10 +6083,10 @@ bool RakPeer::RunUpdateCycle(BitStream &updateBitStream ) remoteSystem=GetRemoteSystem( bcs->systemIdentifier, true, true ); sqo = socketQueryOutput.Allocate( _FILE_AND_LINE_ ); - sqo->sockets.Clear(false, _FILE_AND_LINE_); + sqo->sockets.clear(); if (remoteSystem) { - sqo->sockets.Push(remoteSystem->rakNetSocket, _FILE_AND_LINE_ ); + sqo->sockets.push_back(remoteSystem->rakNetSocket); } else { @@ -6081,7 +6104,7 @@ bool RakPeer::RunUpdateCycle(BitStream &updateBitStream ) bufferedCommands.Deallocate(bcs, _FILE_AND_LINE_); } - if (requestedConnectionQueue.IsEmpty()==false) + if (requestedConnectionQueue.empty()==false) { if (timeNS==0) { @@ -6092,7 +6115,7 @@ bool RakPeer::RunUpdateCycle(BitStream &updateBitStream ) bool condition1, condition2; unsigned requestedConnectionQueueIndex=0; requestedConnectionQueueMutex.Lock(); - while (requestedConnectionQueueIndex < requestedConnectionQueue.Size()) + while (requestedConnectionQueueIndex < requestedConnectionQueue.size()) { RequestedConnectionStruct *rcs; rcs = requestedConnectionQueue[requestedConnectionQueueIndex]; @@ -6127,11 +6150,11 @@ bool RakPeer::RunUpdateCycle(BitStream &updateBitStream ) MafiaNet::OP_DELETE(rcs,_FILE_AND_LINE_); requestedConnectionQueueMutex.Lock(); - for (unsigned int k=0; k < requestedConnectionQueue.Size(); k++) + for (unsigned int k=0; k < requestedConnectionQueue.size(); k++) { if (requestedConnectionQueue[k]==rcs) { - requestedConnectionQueue.RemoveAtIndex(k); + requestedConnectionQueue.erase(requestedConnectionQueue.begin() + k); break; } } @@ -6971,7 +6994,7 @@ RAK_THREAD_DECLARATION(MafiaNet::UpdateNetworkLoop) { WSAEVENT eventArray[256]; unsigned int i, eventArrayIndex; - for (i=0,eventArrayIndex=0; i < rakPeer->socketList.Size(); i++) + for (i=0,eventArrayIndex=0; i < rakPeer->socketList.size(); i++) { if (rakPeer->socketList[i]->recvEvent!=INVALID_HANDLE_VALUE) { diff --git a/Source/src/reliability_layer.cpp b/Source/src/reliability_layer.cpp index 4e8dbd582..28fada9e7 100644 --- a/Source/src/reliability_layer.cpp +++ b/Source/src/reliability_layer.cpp @@ -545,7 +545,7 @@ void ReliabilityLayer::FreeThreadSafeMemory( void ) ClearPacketsAndDatagrams(); - for (i=0; i < splitPacketChannelList.Size(); i++) + for (i=0; i < splitPacketChannelList.size(); i++) { for (j=0; j < splitPacketChannelList[i]->splitPacketList.GetAllocSize(); j++) { @@ -564,16 +564,17 @@ void ReliabilityLayer::FreeThreadSafeMemory( void ) #endif MafiaNet::OP_DELETE(splitPacketChannelList[i], __FILE__, __LINE__); } - splitPacketChannelList.Clear(false, _FILE_AND_LINE_); + splitPacketChannelList.clear(); - while ( outputQueue.Size() > 0 ) + while ( outputQueue.size() > 0 ) { - internalPacket = outputQueue.Pop(); + internalPacket = outputQueue.front(); + outputQueue.pop_front(); FreeInternalPacketData(internalPacket, _FILE_AND_LINE_ ); ReleaseToInternalPacketPool( internalPacket ); } - outputQueue.ClearAndForceAllocation( 32, _FILE_AND_LINE_ ); + outputQueue.clear(); /* for ( i = 0; i < orderingList.Size(); i++ ) @@ -649,21 +650,21 @@ void ReliabilityLayer::FreeThreadSafeMemory( void ) outgoingPacketBuffer.Clear(); #ifdef _DEBUG - for (i = 0; i < delayList.Size(); i++ ) + for (i = 0; i < delayList.size(); i++ ) MafiaNet::OP_DELETE(delayList[ i ], __FILE__, __LINE__); - delayList.Clear(__FILE__, __LINE__); + delayList.clear(); #endif - unreliableWithAckReceiptHistory.Clear(false, _FILE_AND_LINE_); + unreliableWithAckReceiptHistory.clear(); - packetsToSendThisUpdate.Clear(false, _FILE_AND_LINE_); - packetsToSendThisUpdate.Preallocate(512, _FILE_AND_LINE_); - packetsToDeallocThisUpdate.Clear(false, _FILE_AND_LINE_); - packetsToDeallocThisUpdate.Preallocate(512, _FILE_AND_LINE_); - packetsToSendThisUpdateDatagramBoundaries.Clear(false, _FILE_AND_LINE_); - packetsToSendThisUpdateDatagramBoundaries.Preallocate(128, _FILE_AND_LINE_); - datagramSizesInBytes.Clear(false, _FILE_AND_LINE_); - datagramSizesInBytes.Preallocate(128, _FILE_AND_LINE_); + packetsToSendThisUpdate.clear(); + packetsToSendThisUpdate.reserve(512); + packetsToDeallocThisUpdate.clear(); + packetsToDeallocThisUpdate.reserve(512); + packetsToSendThisUpdateDatagramBoundaries.clear(); + packetsToSendThisUpdateDatagramBoundaries.reserve(128); + datagramSizesInBytes.clear(); + datagramSizesInBytes.reserve(128); internalPacketPool.Clear(_FILE_AND_LINE_); @@ -682,10 +683,10 @@ void ReliabilityLayer::FreeThreadSafeMemory( void ) datagramMessageIDPool.Clear(_FILE_AND_LINE_); */ - while (datagramHistory.Size()) + while (datagramHistory.size()) { RemoveFromDatagramHistory(datagramHistoryPopCount); - datagramHistory.Pop(); + datagramHistory.pop_front(); datagramHistoryPopCount++; } datagramHistoryMessagePool.Clear(_FILE_AND_LINE_); @@ -801,24 +802,24 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( } unsigned int k = 0; - while (k < unreliableWithAckReceiptHistory.Size()) { + while (k < unreliableWithAckReceiptHistory.size()) { if (incomingAcks.IsWithinRange(unreliableWithAckReceiptHistory[k].datagramNumber)) { InternalPacket *ackReceipt = AllocateFromInternalPacketPool(); AllocInternalPacketData(ackReceipt, 5, false, _FILE_AND_LINE_); ackReceipt->dataBitLength = BYTES_TO_BITS(5); ackReceipt->data[0] = (MessageID)ID_SND_RECEIPT_ACKED; memcpy(ackReceipt->data + sizeof(MessageID), &unreliableWithAckReceiptHistory[k].sendReceiptSerial, sizeof(uint32_t)); - outputQueue.Push(ackReceipt, _FILE_AND_LINE_); + outputQueue.push_back(ackReceipt); // Remove, swap with last - unreliableWithAckReceiptHistory.RemoveAtIndex(k); + unreliableWithAckReceiptHistory.erase(unreliableWithAckReceiptHistory.begin() + k); } else { k++; } } // early out, if we've got no outstanding datagramHistory entries - if (datagramHistory.IsEmpty()) { + if (datagramHistory.empty()) { receivePacketCount++; return true; } @@ -838,7 +839,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( for (datagramNumber = incomingAcks.ranges[i].minIndex; datagramNumber <= incomingAcks.ranges[i].maxIndex; datagramNumber++) { const DatagramSequenceNumberType offsetIntoList = datagramNumber - datagramHistoryPopCount; - if (offsetIntoList >= datagramHistory.Size()) { + if (offsetIntoList >= datagramHistory.size()) { // reached the end of the datagramHistory list - hence, we are done receivePacketCount++; return true; @@ -879,7 +880,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( } } else if (dhf.isNAK) { // early out, if we've got no outstanding datagramHistory entries - if (datagramHistory.IsEmpty()) { + if (datagramHistory.empty()) { receivePacketCount++; return true; } @@ -914,7 +915,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( // printf("%p NAK %i\n", this, dhf.datagramNumber.val); const DatagramSequenceNumberType offsetIntoList = messageNumber - datagramHistoryPopCount; - if (offsetIntoList >= datagramHistory.Size()) { + if (offsetIntoList >= datagramHistory.size()) { // reached the end of the datagramHistory list - hence, we are done receivePacketCount++; return true; @@ -987,7 +988,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( // resetReceivedPackets is set from a non-threadsafe function. // We do the actual reset in this function so the data is not modified by multiple threads if (resetReceivedPackets) { - hasReceivedPacketQueue.ClearAndForceAllocation(DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE, _FILE_AND_LINE_); + hasReceivedPacketQueue.clear(); receivedPacketsBaseIndex=0; resetReceivedPackets=false; } @@ -1026,8 +1027,8 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( if (holeCount == (DatagramSequenceNumberType)0) { // Got what we were expecting - if (hasReceivedPacketQueue.Size()) { - hasReceivedPacketQueue.Pop(); + if (hasReceivedPacketQueue.size()) { + hasReceivedPacketQueue.pop_front(); } ++receivedPacketsBaseIndex; } else if (holeCount > typeRange/(DatagramSequenceNumberType) 2) { @@ -1042,7 +1043,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( ReleaseToInternalPacketPool(internalPacket); goto CONTINUE_SOCKET_DATA_PARSE_LOOP; - } else if ((unsigned int)holeCount < hasReceivedPacketQueue.Size()) { + } else if ((unsigned int)holeCount < hasReceivedPacketQueue.size()) { // Got a higher count out of order packet that was missing in the sequence or we already got if (hasReceivedPacketQueue[holeCount] != false) { // non-zero means this is a hole #ifdef LOG_TRIVIAL_NOTIFICATIONS @@ -1097,26 +1098,25 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( // Fixed by late assigning message IDs on the sender // Add 0 times to the queue until (reliableMessageNumber - baseIndex) < queue size. - while ((unsigned int)(holeCount) > hasReceivedPacketQueue.Size()) { - hasReceivedPacketQueue.Push(true, _FILE_AND_LINE_); // time+(CCTimeType)60 * (CCTimeType)1000 * (CCTimeType)1000); // Didn't get this packet - set the time to give up waiting + while ((unsigned int)(holeCount) > hasReceivedPacketQueue.size()) { + hasReceivedPacketQueue.push_back(true); // time+(CCTimeType)60 * (CCTimeType)1000 * (CCTimeType)1000); // Didn't get this packet - set the time to give up waiting } - hasReceivedPacketQueue.Push(false, _FILE_AND_LINE_ ); // Got the packet + hasReceivedPacketQueue.push_back(false); // Got the packet #ifdef _DEBUG // If this assert hits then DatagramSequenceNumberType has overflowed - RakAssert(hasReceivedPacketQueue.Size() < (unsigned int)((DatagramSequenceNumberType)(const uint32_t)(-1))); + RakAssert(hasReceivedPacketQueue.size() < (unsigned int)((DatagramSequenceNumberType)(const uint32_t)(-1))); #endif } - while (hasReceivedPacketQueue.Size() > 0 && !hasReceivedPacketQueue.Peek()) { - hasReceivedPacketQueue.Pop(); + while (hasReceivedPacketQueue.size() > 0 && !hasReceivedPacketQueue.front()) { + hasReceivedPacketQueue.pop_front(); ++receivedPacketsBaseIndex; } } - // If the allocated buffer is > DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE and it is 3x greater than the number of elements actually being used - if (hasReceivedPacketQueue.AllocationSize() > (unsigned int)DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE && hasReceivedPacketQueue.AllocationSize() > hasReceivedPacketQueue.Size() * 3) { - hasReceivedPacketQueue.Compress(_FILE_AND_LINE_); - } + // The predecessor compacted here when its ring buffer had grown past 3x the live + // element count, because that buffer never shrank on its own. std::deque releases + // blocks as it drains, so there is nothing to compact and no equivalent call. /* @@ -1158,7 +1158,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( waitingForSequencedPacketReadIndex[ internalPacket->orderingChannel ] = internalPacket->orderingIndex + (OrderingIndexType)1; // If there is a rebuilt packet, add it to the output queue - outputQueue.Push(internalPacket, _FILE_AND_LINE_); + outputQueue.push_back(internalPacket); internalPacket = 0; } @@ -1169,7 +1169,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( // Not a split packet. Add the packet to the output queue bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_PROCESSED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); - outputQueue.Push( internalPacket, _FILE_AND_LINE_ ); + outputQueue.push_back(internalPacket); internalPacket = 0; } } else { @@ -1231,7 +1231,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( unsigned char orderingChannelCopy = internalPacket->orderingChannel; // Push the packet for the user to read - outputQueue.Push( internalPacket, _FILE_AND_LINE_ ); + outputQueue.push_back(internalPacket); internalPacket = 0; // Don't reference this any longer since other threads access it // Wait for the resendNext ordered packet in sequence @@ -1253,7 +1253,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( { if ( orderingListAtOrderingStream->Peek()->orderingIndex == waitingForOrderedPacketReadIndex[ orderingChannelCopy ] ) { - outputQueue.Push( orderingListAtOrderingStream->Pop(), _FILE_AND_LINE_ ); + outputQueue.push_back(orderingListAtOrderingStream->Pop()); waitingForOrderedPacketReadIndex[ orderingChannelCopy ]++; indexFound=true; } @@ -1405,7 +1405,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( { // Push to output buffer immediately bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_PROCESSED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); - outputQueue.Push( internalPacket, _FILE_AND_LINE_ ); + outputQueue.push_back(internalPacket); #ifdef PRINT_TO_FILE_RELIABLE_ORDERED_TEST if (packetId==ID_USER_PACKET_ENUM+1 && fp) @@ -1471,7 +1471,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( #endif bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_PROCESSED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); - outputQueue.Push( internalPacket, _FILE_AND_LINE_ ); + outputQueue.push_back(internalPacket); if (internalPacket->reliability == MafiaNet::Reliability::ReliableOrdered) { @@ -1542,7 +1542,7 @@ bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_PROCESSED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); // Nothing special about this packet. Add it to the output queue - outputQueue.Push( internalPacket, _FILE_AND_LINE_ ); + outputQueue.push_back(internalPacket); internalPacket = 0; } @@ -1569,12 +1569,13 @@ BitSize_t ReliabilityLayer::Receive( unsigned char **data ) { InternalPacket * internalPacket; - if ( outputQueue.Size() > 0 ) + if ( outputQueue.size() > 0 ) { // #ifdef _DEBUG // RakAssert(bitStream->GetNumberOfBitsUsed()==0); // #endif - internalPacket = outputQueue.Pop(); + internalPacket = outputQueue.front(); + outputQueue.pop_front(); BitSize_t bitLength; *data = internalPacket->data; @@ -1780,11 +1781,12 @@ void ReliabilityLayer::UpdateInternal( RakNetSocket2 *s, SystemAddress &systemAd #endif #ifdef _DEBUG - while (delayList.Size()) + while (delayList.size()) { - if (delayList.Peek()->sendTime <= timeMs) + if (delayList.front()->sendTime <= timeMs) { - DataAndTime *dat = delayList.Pop(); + DataAndTime *dat = delayList.front(); + delayList.pop_front(); // SocketLayer::SendTo( dat->s, dat->data, dat->length, systemAddress, __FILE__, __LINE__ ); RNS2_SendParameters bsp; @@ -1932,10 +1934,10 @@ void ReliabilityLayer::UpdateInternal( RakNetSocket2 *s, SystemAddress &systemAd lastBpsClear=time; } - if (unreliableWithAckReceiptHistory.Size()>0) + if (unreliableWithAckReceiptHistory.size()>0) { i=0; - while (i < unreliableWithAckReceiptHistory.Size()) + while (i < unreliableWithAckReceiptHistory.size()) { //if (unreliableWithAckReceiptHistory[i].nextActionTime < time) if (time - unreliableWithAckReceiptHistory[i].nextActionTime < (((CCTimeType)-1)/2) ) @@ -1945,10 +1947,10 @@ void ReliabilityLayer::UpdateInternal( RakNetSocket2 *s, SystemAddress &systemAd ackReceipt->dataBitLength=BYTES_TO_BITS(5); ackReceipt->data[0]=(MessageID)ID_SND_RECEIPT_LOSS; memcpy(ackReceipt->data+sizeof(MessageID), &unreliableWithAckReceiptHistory[i].sendReceiptSerial, sizeof(uint32_t)); - outputQueue.Push(ackReceipt, _FILE_AND_LINE_ ); + outputQueue.push_back(ackReceipt); // Remove, swap with last - unreliableWithAckReceiptHistory.RemoveAtIndex(i); + unreliableWithAckReceiptHistory.erase(unreliableWithAckReceiptHistory.begin() + i); } else i++; @@ -2036,9 +2038,9 @@ void ReliabilityLayer::UpdateInternal( RakNetSocket2 *s, SystemAddress &systemAd for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) { #if CC_TIME_TYPE_BYTES==4 - messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, packetsToSendThisUpdateDatagramBoundaries.Size()+congestionManager.GetNextDatagramSequenceNumber(), systemAddress, (MafiaNet::TimeMS) time, true); + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, packetsToSendThisUpdateDatagramBoundaries.size()+congestionManager.GetNextDatagramSequenceNumber(), systemAddress, (MafiaNet::TimeMS) time, true); #else - messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, packetsToSendThisUpdateDatagramBoundaries.Size()+congestionManager.GetNextDatagramSequenceNumber(), systemAddress, (MafiaNet::TimeMS)(time/(CCTimeType)1000), true); + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, packetsToSendThisUpdateDatagramBoundaries.size()+congestionManager.GetNextDatagramSequenceNumber(), systemAddress, (MafiaNet::TimeMS)(time/(CCTimeType)1000), true); #endif } @@ -2078,7 +2080,7 @@ void ReliabilityLayer::UpdateInternal( RakNetSocket2 *s, SystemAddress &systemAd ((int)BITS_TO_BYTES(allDatagramSizesSoFar)reliability == MafiaNet::Reliability::UnreliableWithAckReceipt) { - unreliableWithAckReceiptHistory.Push(UnreliableWithAckReceiptNode( - congestionManager.GetNextDatagramSequenceNumber() + packetsToSendThisUpdateDatagramBoundaries.Size(), + unreliableWithAckReceiptHistory.push_back(UnreliableWithAckReceiptNode( + // Cast back to unsigned int: uint24_t has both operator uint32_t() and an + // implicit uint24_t(const uint32_t&), so adding a size_t is ambiguous where + // adding the unsigned that List::Size() returned was not. + congestionManager.GetNextDatagramSequenceNumber() + (unsigned int) packetsToSendThisUpdateDatagramBoundaries.size(), internalPacket->sendReceiptSerial, congestionManager.GetRTOForRetransmission(internalPacket->timesSent+1)+time - ), _FILE_AND_LINE_); + )); } // If isReliable is false, the packet and its contents will be added to a list to be freed in ClearPacketsAndDatagrams @@ -2206,9 +2211,9 @@ void ReliabilityLayer::UpdateInternal( RakNetSocket2 *s, SystemAddress &systemAd for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) { #if CC_TIME_TYPE_BYTES==4 - messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, packetsToSendThisUpdateDatagramBoundaries.Size()+congestionManager.GetNextDatagramSequenceNumber(), systemAddress, (MafiaNet::TimeMS)time, true); + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, packetsToSendThisUpdateDatagramBoundaries.size()+congestionManager.GetNextDatagramSequenceNumber(), systemAddress, (MafiaNet::TimeMS)time, true); #else - messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, packetsToSendThisUpdateDatagramBoundaries.Size()+congestionManager.GetNextDatagramSequenceNumber(), systemAddress, (MafiaNet::TimeMS)(time/(CCTimeType)1000), true); + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, packetsToSendThisUpdateDatagramBoundaries.size()+congestionManager.GetNextDatagramSequenceNumber(), systemAddress, (MafiaNet::TimeMS)(time/(CCTimeType)1000), true); #endif } pushedAnything=true; @@ -2236,7 +2241,7 @@ void ReliabilityLayer::UpdateInternal( RakNetSocket2 *s, SystemAddress &systemAd // sendmmsg. Loop-local: flushed at the single loop exit below. RNS2SendBatch sendBatch(s, systemAddress); #endif - for (unsigned int datagramIndex=0; datagramIndex < packetsToSendThisUpdateDatagramBoundaries.Size(); datagramIndex++) + for (unsigned int datagramIndex=0; datagramIndex < packetsToSendThisUpdateDatagramBoundaries.size(); datagramIndex++) { if (datagramIndex>0) dhf.isContinuousSend=true; @@ -2380,7 +2385,7 @@ void ReliabilityLayer::SendBitStream( RakNetSocket2 *s, SystemAddress &systemAdd dat->length=length; dat->sendTime = 0; dat->extraSocketOptions=extraSocketOptions; - delayList.PushAtHead(dat, 0, _FILE_AND_LINE_); + delayList.push_front(dat); #else MafiaNet::TimeMS delay = minExtraPing; if (extraPingVariance>0) @@ -2392,17 +2397,19 @@ void ReliabilityLayer::SendBitStream( RakNetSocket2 *s, SystemAddress &systemAdd dat->s=s; dat->length=length; dat->sendTime = MafiaNet::GetTimeMS() + delay; - for (unsigned int i=0; i < delayList.Size(); i++) + for (unsigned int i=0; i < delayList.size(); i++) { if (dat->sendTime < delayList[i]->sendTime) { - delayList.PushAtHead(dat, i, __FILE__, __LINE__); + // Insert before the first entry due later than this one, keeping delayList + // ordered by sendTime, as PushAtHead(dat, i) did. + delayList.insert(delayList.begin() + i, dat); dat=0; break; } } if (dat!=0) - delayList.Push(dat,__FILE__,__LINE__); + delayList.push_back(dat); return; } #endif @@ -2474,7 +2481,7 @@ bool ReliabilityLayer::IsOutgoingDataWaiting(void) return //acknowlegements.Size() > 0 || - //resendTree.IsEmpty()==false;// || outputQueue.Size() > 0 || orderingList.Size() > 0 || splitPacketChannelList.Size() > 0; + //resendTree.IsEmpty()==false;// || outputQueue.size() > 0 || orderingList.Size() > 0 || splitPacketChannelList.size() > 0; statistics.messagesInResendBuffer!=0; } bool ReliabilityLayer::AreAcksWaiting(void) @@ -2617,7 +2624,7 @@ unsigned ReliabilityLayer::RemovePacketFromResendListAndDeleteOlderReliableSeque ackReceipt->dataBitLength=BYTES_TO_BITS(5); ackReceipt->data[0]=(MessageID)ID_SND_RECEIPT_ACKED; memcpy(ackReceipt->data+sizeof(MessageID), &internalPacket->sendReceiptSerial, sizeof(internalPacket->sendReceiptSerial)); - outputQueue.Push(ackReceipt, _FILE_AND_LINE_ ); + outputQueue.push_back(ackReceipt); } bool isReliable; @@ -3176,9 +3183,9 @@ void ReliabilityLayer::ReSplitOversizedMessages(void) unsigned int i; InternalPacket *p; - DataStructures::List staleSplitIds; - DataStructures::List rebuiltMessages; - DataStructures::List resendVictims; + std::vector staleSplitIds; + std::vector rebuiltMessages; + std::vector resendVictims; // Rebuild a full-length copy of the message pkt belongs to, before any of // its packets are freed. Returns 0 (leaving the queues untouched) when the @@ -3238,7 +3245,7 @@ void ReliabilityLayer::ReSplitOversizedMessages(void) }; auto isStaleId = [&staleSplitIds](SplitPacketIdType id) -> bool { - for (unsigned int k = 0; k < staleSplitIds.Size(); k++) + for (unsigned int k = 0; k < staleSplitIds.size(); k++) if (staleSplitIds[k] == id) return true; return false; @@ -3254,8 +3261,8 @@ void ReliabilityLayer::ReSplitOversizedMessages(void) InternalPacket *rebuilt = rebuildMessage(pkt); if (rebuilt == 0) return; - staleSplitIds.Push(pkt->splitPacketId, _FILE_AND_LINE_); - rebuiltMessages.Push(rebuilt, _FILE_AND_LINE_); + staleSplitIds.push_back(pkt->splitPacketId); + rebuiltMessages.push_back(rebuilt); }; // Drop a not-yet-sent packet in place. The outgoing buffer is a heap, so @@ -3284,8 +3291,8 @@ void ReliabilityLayer::ReSplitOversizedMessages(void) InternalPacket *rebuilt = rebuildMessage(p); if (rebuilt) { - rebuiltMessages.Push(rebuilt, _FILE_AND_LINE_); - resendVictims.Push(p, _FILE_AND_LINE_); + rebuiltMessages.push_back(rebuilt); + resendVictims.push_back(p); } } } @@ -3312,7 +3319,7 @@ void ReliabilityLayer::ReSplitOversizedMessages(void) InternalPacket *rebuilt = rebuildMessage(p); if (rebuilt == 0) continue; - rebuiltMessages.Push(rebuilt, _FILE_AND_LINE_); + rebuiltMessages.push_back(rebuilt); } // An unreliable message the path already black-holed is not owed // delivery; dropping it here is what the network was doing anyway. @@ -3323,7 +3330,7 @@ void ReliabilityLayer::ReSplitOversizedMessages(void) // Pass 2: sweep every packet of the stale messages out of both queues -- // including small tail fragments that still fit, since the whole message // is re-sent under its new id. - if (staleSplitIds.Size() > 0) + if (staleSplitIds.size() > 0) { if (resendLinkedListHead) { @@ -3331,7 +3338,7 @@ void ReliabilityLayer::ReSplitOversizedMessages(void) do { if (p->splitPacketCount > 0 && isStaleId(p->splitPacketId)) - resendVictims.Push(p, _FILE_AND_LINE_); + resendVictims.push_back(p); p = p->resendNext; } while (p != resendLinkedListHead); } @@ -3344,7 +3351,7 @@ void ReliabilityLayer::ReSplitOversizedMessages(void) } // Unlink the resend-list victims the way an ack would, minus the receipt. - for (i = 0; i < resendVictims.Size(); i++) + for (i = 0; i < resendVictims.size(); i++) { p = resendVictims[i]; if (resendBuffer[p->reliableMessageNumber & (uint32_t) RESEND_BUFFER_ARRAY_MASK] == p) @@ -3357,24 +3364,58 @@ void ReliabilityLayer::ReSplitOversizedMessages(void) } // Queue the rebuilt messages, split at the new, smaller size. - for (i = 0; i < rebuiltMessages.Size(); i++) + for (i = 0; i < rebuiltMessages.size(); i++) SplitPacket(rebuiltMessages[i]); } //------------------------------------------------------------------------------------------------------- // Insert a packet into the split packet list //------------------------------------------------------------------------------------------------------- +unsigned int ReliabilityLayer::IndexForSplitPacketId(SplitPacketIdType id, bool *found) const +{ + // splitPacketChannelList is sorted by split-packet id. The key is read out of the channel by + // SplitPacketChannelComp, whose extraction differs under PREALLOCATE_LARGE_MESSAGES, so the + // comparator is reused rather than reimplemented: it returns >0 when the candidate sorts + // below the key. + std::vector::const_iterator it = std::lower_bound( + splitPacketChannelList.begin(), splitPacketChannelList.end(), id, + [](SplitPacketChannel * const &candidate, SplitPacketIdType key) + { + return MafiaNet::SplitPacketChannelComp(key, candidate) > 0; + }); + unsigned int i = (unsigned int) (it - splitPacketChannelList.begin()); + *found = (it != splitPacketChannelList.end() && MafiaNet::SplitPacketChannelComp(id, *it) == 0); + return i; +} +//------------------------------------------------------------------------------------------------------- +unsigned int ReliabilityLayer::InsertSplitPacketChannel(SplitPacketIdType id, SplitPacketChannel *channel) +{ + // The OrderedList::Insert this replaces was called with assertOnDuplicate=true: it refused a + // duplicate id and returned (unsigned)-1 without inserting. The id is a parameter because the + // caller inserts the channel before filling in its split packet list, so the channel cannot + // yet report its own id. + bool found; + unsigned int i = IndexForSplitPacketId(id, &found); + if (found) + { + RakAssert(0 && "Split packet id already in splitPacketChannelList"); + return (unsigned int) -1; + } + splitPacketChannelList.insert(splitPacketChannelList.begin() + i, channel); + return i; +} +//------------------------------------------------------------------------------------------------------- void ReliabilityLayer::InsertIntoSplitPacketList( InternalPacket * internalPacket, CCTimeType time ) { bool objectExists; unsigned index; // Find in splitPacketChannelList if a SplitPacketChannel with this splitPacketId was already allocated. If not, allocate and insert the channel into the list. - index=splitPacketChannelList.GetIndexFromKey(internalPacket->splitPacketId, &objectExists); + index=IndexForSplitPacketId(internalPacket->splitPacketId, &objectExists); if (objectExists==false) { SplitPacketChannel *newChannel = MafiaNet::OP_NEW( __FILE__, __LINE__ ); #if PREALLOCATE_LARGE_MESSAGES==1 - index=splitPacketChannelList.Insert(internalPacket->splitPacketId, newChannel, true, __FILE__,__LINE__); + index=InsertSplitPacketChannel(internalPacket->splitPacketId, newChannel); newChannel->returnedPacket=CreateInternalPacketCopy( internalPacket, 0, 0, time ); newChannel->gotFirstPacket=false; newChannel->splitPacketsArrived=0; @@ -3382,7 +3423,7 @@ void ReliabilityLayer::InsertIntoSplitPacketList( InternalPacket * internalPacke RakAssert(newChannel->returnedPacket->data); #else newChannel->firstPacket=0; - index=splitPacketChannelList.Insert(internalPacket->splitPacketId, newChannel, true, __FILE__,__LINE__); + index=InsertSplitPacketChannel(internalPacket->splitPacketId, newChannel); // Preallocate to the final size, to avoid runtime copies newChannel->splitPacketList.Preallocate(internalPacket, __FILE__,__LINE__); @@ -3504,7 +3545,7 @@ void ReliabilityLayer::InsertIntoSplitPacketList( InternalPacket * internalPacke memcpy(progressIndicator->data+sizeof(MessageID)+sizeof(unsigned int)*2, &temp, sizeof(unsigned int)); memcpy(progressIndicator->data+sizeof(MessageID)+sizeof(unsigned int)*3, splitPacketChannelList[index]->firstPacket->data, (size_t) BITS_TO_BYTES(splitPacketChannelList[index]->firstPacket->dataBitLength)); - outputQueue.Push(progressIndicator, __FILE__, __LINE__ ); + outputQueue.push_back(progressIndicator); } #endif @@ -3566,7 +3607,7 @@ InternalPacket * ReliabilityLayer::BuildPacketFromSplitPacketList( SplitPacketId InternalPacket * internalPacket; // Find in splitPacketChannelList the SplitPacketChannel with this splitPacketId - i=splitPacketChannelList.GetIndexFromKey(inSplitPacketId, &objectExists); + i=IndexForSplitPacketId(inSplitPacketId, &objectExists); splitPacketChannel=splitPacketChannelList[i]; #if PREALLOCATE_LARGE_MESSAGES==1 @@ -3578,7 +3619,7 @@ InternalPacket * ReliabilityLayer::BuildPacketFromSplitPacketList( SplitPacketId // Ack immediately, because for large files this can take a long time SendACKs(s, systemAddress, time, rnr, updateBitStream); internalPacket=BuildPacketFromSplitPacketList(splitPacketChannel,time); - splitPacketChannelList.RemoveAtIndex(i); + splitPacketChannelList.erase(splitPacketChannelList.begin() + i); return internalPacket; } else @@ -3593,7 +3634,7 @@ void ReliabilityLayer::DeleteOldUnreliableSplitPackets( CCTimeType time ) { unsigned i,j; i=0; -while (i < splitPacketChannelList.Size()) +while (i < splitPacketChannelList.size()) { #if CC_TIME_TYPE_BYTES==4 if (time > splitPacketChannelList[i]->lastUpdateTime + timeoutTime && @@ -3608,7 +3649,7 @@ MafiaNet::OP_DELETE_ARRAY(splitPacketChannelList[i]->splitPacketList[j]->data, _ ReleaseToInternalPacketPool(splitPacketChannelList[i]->splitPacketList[j]); } MafiaNet::OP_DELETE(splitPacketChannelList[i], _FILE_AND_LINE_); -splitPacketChannelList.RemoveAtIndex(i); +splitPacketChannelList.erase(splitPacketChannelList.begin() + i); } else i++; @@ -3785,11 +3826,11 @@ CCTimeType ReliabilityLayer::GetAckPing(void) const //------------------------------------------------------------------------------------------------------- void ReliabilityLayer::ResetPacketsAndDatagrams(void) { - packetsToSendThisUpdate.Clear(true, _FILE_AND_LINE_); - packetsToDeallocThisUpdate.Clear(true, _FILE_AND_LINE_); - packetsToSendThisUpdateDatagramBoundaries.Clear(true, _FILE_AND_LINE_); - datagramsToSendThisUpdateIsPair.Clear(true, _FILE_AND_LINE_); - datagramSizesInBytes.Clear(true, _FILE_AND_LINE_); + packetsToSendThisUpdate.clear(); + packetsToDeallocThisUpdate.clear(); + packetsToSendThisUpdateDatagramBoundaries.clear(); + datagramsToSendThisUpdateIsPair.clear(); + datagramSizesInBytes.clear(); datagramSizeSoFar=0; } //------------------------------------------------------------------------------------------------------- @@ -3799,8 +3840,8 @@ void ReliabilityLayer::PushPacket(CCTimeType time, InternalPacket *internalPacke datagramSizeSoFar+=bitsForThisPacket; RakAssert(BITS_TO_BYTES(datagramSizeSoFar)headerLength==GetMessageHeaderLengthBits(internalPacket)); // This code tells me how much time elapses between when you send, and when the message actually goes out @@ -3820,10 +3861,10 @@ void ReliabilityLayer::PushDatagram(void) { if (datagramSizeSoFar>0) { - packetsToSendThisUpdateDatagramBoundaries.Push(packetsToSendThisUpdate.Size(), _FILE_AND_LINE_ ); - datagramsToSendThisUpdateIsPair.Push(false, _FILE_AND_LINE_ ); + packetsToSendThisUpdateDatagramBoundaries.push_back(packetsToSendThisUpdate.size()); + datagramsToSendThisUpdateIsPair.push_back(false); RakAssert(BITS_TO_BYTES(datagramSizeSoFar)=2) + if (datagramsToSendThisUpdateIsPair.size()>=2) { - datagramsToSendThisUpdateIsPair[datagramsToSendThisUpdateIsPair.Size()-2]=true; - datagramsToSendThisUpdateIsPair[datagramsToSendThisUpdateIsPair.Size()-1]=true; + datagramsToSendThisUpdateIsPair[datagramsToSendThisUpdateIsPair.size()-2]=true; + datagramsToSendThisUpdateIsPair[datagramsToSendThisUpdateIsPair.size()-1]=true; return true; } return false; @@ -3853,7 +3894,7 @@ bool ReliabilityLayer::TagMostRecentPushAsSecondOfPacketPair(void) void ReliabilityLayer::ClearPacketsAndDatagrams(void) { unsigned int i; - for (i=0; i < packetsToDeallocThisUpdate.Size(); i++) + for (i=0; i < packetsToDeallocThisUpdate.size(); i++) { // packetsToDeallocThisUpdate holds a boolean indicating if packetsToSendThisUpdate at this index should be freed if (packetsToDeallocThisUpdate[i]) @@ -3864,7 +3905,7 @@ void ReliabilityLayer::ClearPacketsAndDatagrams(void) ReleaseToInternalPacketPool( packetsToSendThisUpdate[i] ); } } - packetsToDeallocThisUpdate.Clear(true, _FILE_AND_LINE_); + packetsToDeallocThisUpdate.clear(); } //------------------------------------------------------------------------------------------------------- void ReliabilityLayer::MoveToListHead(InternalPacket *internalPacket) @@ -4101,14 +4142,14 @@ bool ReliabilityLayer::ResendBufferOverflow(void) const //------------------------------------------------------------------------------------------------------- ReliabilityLayer::MessageNumberNode* ReliabilityLayer::GetMessageNumberNodeByDatagramIndex(DatagramSequenceNumberType index, CCTimeType *timeSent) { - if (datagramHistory.IsEmpty()) + if (datagramHistory.empty()) return nullptr; if (congestionManager.LessThan(index, datagramHistoryPopCount)) return nullptr; DatagramSequenceNumberType offsetIntoList = index - datagramHistoryPopCount; - if (offsetIntoList >= datagramHistory.Size()) + if (offsetIntoList >= datagramHistory.size()) return nullptr; *timeSent=datagramHistory[offsetIntoList].timeSent; @@ -4132,33 +4173,33 @@ void ReliabilityLayer::RemoveFromDatagramHistory(DatagramSequenceNumberType inde void ReliabilityLayer::AddFirstToDatagramHistory(DatagramSequenceNumberType datagramNumber, CCTimeType timeSent) { (void) datagramNumber; - if (datagramHistory.Size()>DATAGRAM_MESSAGE_ID_ARRAY_LENGTH) + if (datagramHistory.size()>DATAGRAM_MESSAGE_ID_ARRAY_LENGTH) { RemoveFromDatagramHistory(datagramHistoryPopCount); - datagramHistory.Pop(); + datagramHistory.pop_front(); datagramHistoryPopCount++; } - datagramHistory.Push(DatagramHistoryNode(0, timeSent), _FILE_AND_LINE_); - // printf("%p Pushed empty DatagramHistoryNode to datagram history at index %i\n", this, datagramHistory.Size()-1); + datagramHistory.push_back(DatagramHistoryNode(0, timeSent)); + // printf("%p Pushed empty DatagramHistoryNode to datagram history at index %i\n", this, datagramHistory.size()-1); } //------------------------------------------------------------------------------------------------------- ReliabilityLayer::MessageNumberNode* ReliabilityLayer::AddFirstToDatagramHistory(DatagramSequenceNumberType datagramNumber, DatagramSequenceNumberType messageNumber, CCTimeType timeSent) { (void) datagramNumber; -// RakAssert(datagramHistoryPopCount+(unsigned int) datagramHistory.Size()==datagramNumber); - if (datagramHistory.Size()>DATAGRAM_MESSAGE_ID_ARRAY_LENGTH) +// RakAssert(datagramHistoryPopCount+(unsigned int) datagramHistory.size()==datagramNumber); + if (datagramHistory.size()>DATAGRAM_MESSAGE_ID_ARRAY_LENGTH) { RemoveFromDatagramHistory(datagramHistoryPopCount); - datagramHistory.Pop(); + datagramHistory.pop_front(); datagramHistoryPopCount++; } MessageNumberNode *mnm = datagramHistoryMessagePool.Allocate(_FILE_AND_LINE_); mnm->next=0; mnm->messageNumber=messageNumber; - datagramHistory.Push(DatagramHistoryNode(mnm, timeSent), _FILE_AND_LINE_); - // printf("%p Pushed message %i to DatagramHistoryNode to datagram history at index %i\n", this, messageNumber.val, datagramHistory.Size()-1); + datagramHistory.push_back(DatagramHistoryNode(mnm, timeSent)); + // printf("%p Pushed message %i to DatagramHistoryNode to datagram history at index %i\n", this, messageNumber.val, datagramHistory.size()-1); return mnm; } //------------------------------------------------------------------------------------------------------- diff --git a/Source/src/replica_manager3.cpp b/Source/src/replica_manager3.cpp index fe72deaa3..db985d347 100644 --- a/Source/src/replica_manager3.cpp +++ b/Source/src/replica_manager3.cpp @@ -17,6 +17,7 @@ #if _RAKNET_SUPPORT_ReplicaManager3==1 #include "mafianet/replica_manager3.h" +#include #include "mafianet/virtual_world_replica3.h" #include "mafianet/get_time.h" #include "mafianet/message_identifiers.h" @@ -41,6 +42,35 @@ bool PRO::operator!=( const PRO& right ) const // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned int Connection_RM3::IndexForReplica(Replica3 *replica3, bool *found) const +{ + // constructedReplicaList is sorted ascending by Replica3::referenceIndex, the same key + // Replica3LSRComp orders on. + std::vector::const_iterator it = std::lower_bound( + constructedReplicaList.begin(), constructedReplicaList.end(), replica3, + [](LastSerializationResult * const &candidate, Replica3 *key) + { + return candidate->replica->referenceIndex < key->referenceIndex; + }); + unsigned int idx = (unsigned int) (it - constructedReplicaList.begin()); + *found = (it != constructedReplicaList.end() && + (*it)->replica->referenceIndex == replica3->referenceIndex); + return idx; +} +unsigned int Connection_RM3::InsertConstructedReplica(LastSerializationResult *lsr) +{ + bool found; + unsigned int idx = IndexForReplica(lsr->replica, &found); + if (found) + { + // The OrderedList::Insert this replaces was called with assertOnDuplicate=true and + // returned (unsigned)-1 without inserting. + RakAssert(0 && "Replica already in constructedReplicaList"); + return (unsigned int) -1; + } + constructedReplicaList.insert(constructedReplicaList.begin() + idx, lsr); + return idx; +} int Connection_RM3::Replica3LSRComp( Replica3 * const &replica3, LastSerializationResult * const &data ) { /* @@ -106,7 +136,7 @@ ReplicaManager3::~ReplicaManager3() if (autoDestroyConnections) { m_WorldListMutex.Lock(); - for (unsigned int i=0; i < worldsList.Size(); i++) + for (unsigned int i=0; i < worldsList.size(); i++) { RakAssert(worldsList[i]->connectionList.Size()==0); } @@ -244,7 +274,7 @@ MafiaNet::Connection_RM3 * ReplicaManager3::PopConnection(unsigned int index, Wo } else if (action==RM3AOPC_DO_NOTHING) { - for (unsigned int index3 = 0; index3 < connection->queryToSerializeReplicaList.Size(); index3++) + for (unsigned int index3 = 0; index3 < connection->queryToSerializeReplicaList.size(); index3++) { LastSerializationResult *lsr = connection->queryToSerializeReplicaList[index3]; lsr->whenLastSerialized=0; @@ -605,7 +635,7 @@ bool ReplicaManager3::GetAllConnectionDownloadsCompleted(WorldId worldId) const void ReplicaManager3::Clear(bool deleteWorlds) { m_WorldListMutex.Lock(); - for (unsigned int i=0; i < worldsList.Size(); i++) + for (unsigned int i=0; i < worldsList.size(); i++) { worldsList[i]->Clear(this); if (deleteWorlds) @@ -615,7 +645,7 @@ void ReplicaManager3::Clear(bool deleteWorlds) } } if (deleteWorlds) - worldsList.Clear(false, _FILE_AND_LINE_); + worldsList.clear(); m_WorldListMutex.Unlock(); } @@ -668,7 +698,7 @@ void ReplicaManager3::AddWorld(WorldId worldId) newWorld->worldId=worldId; worldsArray[worldId]=newWorld; m_WorldListMutex.Lock(); - worldsList.Push(newWorld,_FILE_AND_LINE_); + worldsList.push_back(newWorld); m_WorldListMutex.Unlock(); } @@ -677,12 +707,15 @@ void ReplicaManager3::AddWorld(WorldId worldId) void ReplicaManager3::RemoveWorld(WorldId worldId) { RakAssert(worldsArray[worldId]!=0 && "World not in use"); - for (unsigned int i=0; i < worldsList.Size(); i++) + for (unsigned int i=0; i < worldsList.size(); i++) { if (worldsList[i]==worldsArray[worldId]) { MafiaNet::OP_DELETE(worldsList[i],_FILE_AND_LINE_); - worldsList.RemoveAtIndexFast(i); + // Swap-remove, not erase: GetWorldIdAtIndex documents that worlds are not kept in the + // order they were added, and an erase here would renumber every later world. + worldsList[i] = worldsList.back(); + worldsList.pop_back(); break; } } @@ -694,7 +727,7 @@ void ReplicaManager3::RemoveWorld(WorldId worldId) WorldId ReplicaManager3::GetWorldIdAtIndex(unsigned int index) { - RakAssert(index < worldsList.Size()); + RakAssert(index < worldsList.size()); return worldsList[index]->worldId; } @@ -702,7 +735,7 @@ WorldId ReplicaManager3::GetWorldIdAtIndex(unsigned int index) unsigned int ReplicaManager3::GetWorldCount(void) const { - return worldsList.Size(); + return worldsList.size(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -818,7 +851,7 @@ void Connection_RM3::AutoConstructByQuery(ReplicaManager3 *replicaManager3, Worl if (curConstructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || curConstructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) { - while (index < queryToConstructReplicaList.Size()) + while (index < queryToConstructReplicaList.size()) { lsr=queryToConstructReplicaList[index]; constructionState=lsr->replica->QueryConstruction(this, replicaManager3); @@ -897,7 +930,7 @@ void Connection_RM3::AutoConstructByQuery(ReplicaManager3 *replicaManager3, Worl { RM3DestructionState destructionState; index=0; - while (index < queryToDestructReplicaList.Size()) + while (index < queryToDestructReplicaList.size()) { lsr=queryToDestructReplicaList[index]; destructionState=lsr->replica->QueryDestruction(this, replicaManager3); @@ -933,17 +966,17 @@ void Connection_RM3::AutoConstructByQuery(ReplicaManager3 *replicaManager3, Worl { exists=false; bool objectExists; - idx1=constructedReplicaList.GetIndexFromKey(destroyedReplicasCulled[idx2], &objectExists); + idx1=IndexForReplica(destroyedReplicasCulled[idx2], &objectExists); if (objectExists) { - constructedReplicaList.RemoveAtIndex(idx1); + constructedReplicaList.erase(constructedReplicaList.begin() + idx1); unsigned int j; - for (j=0; j < queryToSerializeReplicaList.Size(); j++) + for (j=0; j < queryToSerializeReplicaList.size(); j++) { if (queryToSerializeReplicaList[j]->replica==destroyedReplicasCulled[idx2] ) { - queryToSerializeReplicaList.RemoveAtIndex(j); + queryToSerializeReplicaList.erase(queryToSerializeReplicaList.begin() + j); break; } } @@ -962,7 +995,7 @@ void ReplicaManager3::Update(void) MafiaNet::Time time = MafiaNet::GetTime(); m_WorldListMutex.Lock(); - for (index3=0; index3 < worldsList.Size(); index3++) + for (index3=0; index3 < worldsList.size(); index3++) { world = worldsList[index3]; worldId = world->worldId; @@ -977,7 +1010,7 @@ void ReplicaManager3::Update(void) if (time - lastAutoSerializeOccurance >= autoSerializeInterval) { - for (index3=0; index3 < worldsList.Size(); index3++) + for (index3=0; index3 < worldsList.size(); index3++) { world = worldsList[index3]; worldId = world->worldId; @@ -1011,7 +1044,7 @@ void ReplicaManager3::Update(void) { // Update replica->lsr so we can lookup in the next block // lsr is per connection / per replica - while (index2 < connection->queryToSerializeReplicaList.Size()) + while (index2 < connection->queryToSerializeReplicaList.size()) { connection->queryToSerializeReplicaList[index2]->replica->lsr=connection->queryToSerializeReplicaList[index2]; index2++; @@ -1034,7 +1067,7 @@ void ReplicaManager3::Update(void) } else { - while (index2 < connection->queryToSerializeReplicaList.Size()) + while (index2 < connection->queryToSerializeReplicaList.size()) { lsr=connection->queryToSerializeReplicaList[index2]; @@ -1098,7 +1131,7 @@ void ReplicaManager3::OnRakPeerShutdown(void) RM3World *world; unsigned int index3; m_WorldListMutex.Lock(); - for (index3=0; index3 < worldsList.Size(); index3++) + for (index3=0; index3 < worldsList.size(); index3++) { world = worldsList[index3]; @@ -1138,7 +1171,7 @@ PluginReceiveResult ReplicaManager3::OnConstruction(Packet *packet, unsigned cha } if (connection->groupConstructionAndSerialize) { - connection->downloadGroup.Push(packet, __FILE__, __LINE__); + connection->downloadGroup.push_back(packet); return RR_STOP_PROCESSING; } @@ -1339,7 +1372,7 @@ PluginReceiveResult ReplicaManager3::OnSerialize(Packet *packet, unsigned char * return RR_CONTINUE_PROCESSING; if (connection->groupConstructionAndSerialize) { - connection->downloadGroup.Push(packet, __FILE__, __LINE__); + connection->downloadGroup.push_back(packet); return RR_STOP_PROCESSING; } @@ -1389,8 +1422,8 @@ PluginReceiveResult ReplicaManager3::OnDownloadStarted(Packet *packet, unsigned { // These messages will be held by the plugin and returned when the download is complete connection->groupConstructionAndSerialize=true; - RakAssert(connection->downloadGroup.Size()==0); - connection->downloadGroup.Push(packet, __FILE__, __LINE__); + RakAssert(connection->downloadGroup.size()==0); + connection->downloadGroup.push_back(packet); return RR_STOP_PROCESSING; } @@ -1409,16 +1442,16 @@ PluginReceiveResult ReplicaManager3::OnDownloadComplete(Packet *packet, unsigned if (connection==0) return RR_CONTINUE_PROCESSING; - if (connection->groupConstructionAndSerialize==true && connection->downloadGroup.Size()>0) + if (connection->groupConstructionAndSerialize==true && connection->downloadGroup.size()>0) { // Push back buffered packets in front of this one unsigned int i; - for (i=0; i < connection->downloadGroup.Size(); i++) + for (i=0; i < connection->downloadGroup.size(); i++) rakPeerInterface->PushBackPacket(connection->downloadGroup[i],false); // Push this one to be last too. It will be processed again, but the second time // groupConstructionAndSerialize will be false and downloadGroup will be empty, so it will go past this block - connection->downloadGroup.Clear(__FILE__,__LINE__); + connection->downloadGroup.clear(); rakPeerInterface->PushBackPacket(packet,false); return RR_STOP_PROCESSING; @@ -1553,9 +1586,9 @@ Connection_RM3::Connection_RM3(const SystemAddress &_systemAddress, RakNetGUID _ Connection_RM3::~Connection_RM3() { unsigned int i; - for (i=0; i < constructedReplicaList.Size(); i++) + for (i=0; i < constructedReplicaList.size(); i++) MafiaNet::OP_DELETE(constructedReplicaList[i], _FILE_AND_LINE_); - for (i=0; i < queryToConstructReplicaList.Size(); i++) + for (i=0; i < queryToConstructReplicaList.size(); i++) MafiaNet::OP_DELETE(queryToConstructReplicaList[i], _FILE_AND_LINE_); } @@ -1564,7 +1597,7 @@ Connection_RM3::~Connection_RM3() void Connection_RM3::GetConstructedReplicas(DataStructures::List &objectsTheyDoHave) { objectsTheyDoHave.Clear(true,_FILE_AND_LINE_); - for (unsigned int idx=0; idx < constructedReplicaList.Size(); idx++) + for (unsigned int idx=0; idx < constructedReplicaList.size(); idx++) { objectsTheyDoHave.Push(constructedReplicaList[idx]->replica, _FILE_AND_LINE_ ); } @@ -1575,7 +1608,7 @@ void Connection_RM3::GetConstructedReplicas(DataStructures::List &obj bool Connection_RM3::HasReplicaConstructed(MafiaNet::Replica3 *replica) { bool objectExists; - constructedReplicaList.GetIndexFromKey(replica, &objectExists); + IndexForReplica(replica, &objectExists); return objectExists; } @@ -1597,9 +1630,9 @@ void Connection_RM3::SendSerializeHeader(MafiaNet::Replica3 *replica, MafiaNet:: void Connection_RM3::ClearDownloadGroup(RakPeerInterface *rakPeerInterface) { unsigned int i; - for (i=0; i < downloadGroup.Size(); i++) + for (i=0; i < downloadGroup.size(); i++) rakPeerInterface->DeallocatePacket(downloadGroup[i]); - downloadGroup.Clear(__FILE__,__LINE__); + downloadGroup.clear(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- SendSerializeIfChangedResult Connection_RM3::SendSerialize(MafiaNet::Replica3 *replica, bool indicesToSend[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS], MafiaNet::BitStream serializationData[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS], MafiaNet::Time timestamp, PRO sendParameters[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS], RakPeerInterface *rakPeer, unsigned char worldId, MafiaNet::Time curTime) @@ -1829,7 +1862,7 @@ void Connection_RM3::OnLocalReference(Replica3* replica3, ReplicaManager3 *repli (void) curConstructionMode; #ifdef _DEBUG - for (unsigned int i=0; i < queryToConstructReplicaList.Size(); i++) + for (unsigned int i=0; i < queryToConstructReplicaList.size(); i++) { if (queryToConstructReplicaList[i]->replica==replica3) { @@ -1837,7 +1870,9 @@ void Connection_RM3::OnLocalReference(Replica3* replica3, ReplicaManager3 *repli } } - if (constructedReplicaList.HasData(replica3)==true) + bool alreadyConstructed; + IndexForReplica(replica3, &alreadyConstructed); + if (alreadyConstructed==true) { RakAssert("replica added to queryToConstructReplicaList when already in constructedReplicaList" && 0); } @@ -1845,7 +1880,7 @@ void Connection_RM3::OnLocalReference(Replica3* replica3, ReplicaManager3 *repli LastSerializationResult* lsr= MafiaNet::OP_NEW(_FILE_AND_LINE_); lsr->replica=replica3; - queryToConstructReplicaList.Push(lsr,_FILE_AND_LINE_); + queryToConstructReplicaList.push_back(lsr); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1861,39 +1896,39 @@ void Connection_RM3::OnDereference(Replica3* replica3, ReplicaManager3 *replicaM unsigned int idx; bool objectExists; - idx=constructedReplicaList.GetIndexFromKey(replica3, &objectExists); + idx=IndexForReplica(replica3, &objectExists); if (objectExists) { lsr=constructedReplicaList[idx]; - constructedReplicaList.RemoveAtIndex(idx); + constructedReplicaList.erase(constructedReplicaList.begin() + idx); } - for (idx=0; idx < queryToConstructReplicaList.Size(); idx++) + for (idx=0; idx < queryToConstructReplicaList.size(); idx++) { if (queryToConstructReplicaList[idx]->replica==replica3) { lsr=queryToConstructReplicaList[idx]; - queryToConstructReplicaList.RemoveAtIndex(idx); + queryToConstructReplicaList.erase(queryToConstructReplicaList.begin() + idx); break; } } - for (idx=0; idx < queryToSerializeReplicaList.Size(); idx++) + for (idx=0; idx < queryToSerializeReplicaList.size(); idx++) { if (queryToSerializeReplicaList[idx]->replica==replica3) { lsr=queryToSerializeReplicaList[idx]; - queryToSerializeReplicaList.RemoveAtIndex(idx); + queryToSerializeReplicaList.erase(queryToSerializeReplicaList.begin() + idx); break; } } - for (idx=0; idx < queryToDestructReplicaList.Size(); idx++) + for (idx=0; idx < queryToDestructReplicaList.size(); idx++) { if (queryToDestructReplicaList[idx]->replica==replica3) { lsr=queryToDestructReplicaList[idx]; - queryToDestructReplicaList.RemoveAtIndex(idx); + queryToDestructReplicaList.erase(queryToDestructReplicaList.begin() + idx); break; } } @@ -1920,22 +1955,22 @@ void Connection_RM3::OnDownloadFromThisSystem(Replica3* replica3, ReplicaManager if (curConstructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || curConstructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) { unsigned int j; - for (j=0; j < queryToConstructReplicaList.Size(); j++) + for (j=0; j < queryToConstructReplicaList.size(); j++) { if (queryToConstructReplicaList[j]->replica==replica3 ) { - queryToConstructReplicaList.RemoveAtIndex(j); + queryToConstructReplicaList.erase(queryToConstructReplicaList.begin() + j); break; } } - queryToDestructReplicaList.Push(lsr,_FILE_AND_LINE_); + queryToDestructReplicaList.push_back(lsr); } - if (constructedReplicaList.Insert(lsr->replica, lsr, true, _FILE_AND_LINE_) != (unsigned) -1) + if (InsertConstructedReplica(lsr) != (unsigned) -1) { //assert(queryToSerializeReplicaList.GetIndexOf(replica3)==(unsigned int)-1); - queryToSerializeReplicaList.Push(lsr,_FILE_AND_LINE_); + queryToSerializeReplicaList.push_back(lsr); } ValidateLists(replicaManager); @@ -1949,7 +1984,7 @@ void Connection_RM3::OnDownloadFromOtherSystem(Replica3* replica3, ReplicaManage if (curConstructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || curConstructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) { unsigned int j; - for (j=0; j < queryToConstructReplicaList.Size(); j++) + for (j=0; j < queryToConstructReplicaList.size(); j++) { if (queryToConstructReplicaList[j]->replica==replica3 ) { @@ -1971,7 +2006,7 @@ void Connection_RM3::OnNeverConstruct(unsigned int queryToConstructIdx, ReplicaM ValidateLists(replicaManager); LastSerializationResult* lsr = queryToConstructReplicaList[queryToConstructIdx]; - queryToConstructReplicaList.RemoveAtIndex(queryToConstructIdx); + queryToConstructReplicaList.erase(queryToConstructReplicaList.begin() + queryToConstructIdx); MafiaNet::OP_DELETE(lsr,_FILE_AND_LINE_); ValidateLists(replicaManager); } @@ -1986,13 +2021,13 @@ void Connection_RM3::OnConstructToThisConnection(unsigned int queryToConstructId ValidateLists(replicaManager); LastSerializationResult* lsr = queryToConstructReplicaList[queryToConstructIdx]; - queryToConstructReplicaList.RemoveAtIndex(queryToConstructIdx); + queryToConstructReplicaList.erase(queryToConstructReplicaList.begin() + queryToConstructIdx); //assert(constructedReplicaList.GetIndexOf(lsr->replica)==(unsigned int)-1); - constructedReplicaList.Insert(lsr->replica,lsr,true,_FILE_AND_LINE_); + InsertConstructedReplica(lsr); //assert(queryToDestructReplicaList.GetIndexOf(lsr->replica)==(unsigned int)-1); - queryToDestructReplicaList.Push(lsr,_FILE_AND_LINE_); + queryToDestructReplicaList.push_back(lsr); //assert(queryToSerializeReplicaList.GetIndexOf(lsr->replica)==(unsigned int)-1); - queryToSerializeReplicaList.Push(lsr,_FILE_AND_LINE_); + queryToSerializeReplicaList.push_back(lsr); ValidateLists(replicaManager); } @@ -2006,8 +2041,8 @@ void Connection_RM3::OnConstructToThisConnection(Replica3 *replica, ReplicaManag LastSerializationResult* lsr= MafiaNet::OP_NEW(_FILE_AND_LINE_); lsr->replica=replica; - constructedReplicaList.Insert(replica,lsr,true,_FILE_AND_LINE_); - queryToSerializeReplicaList.Push(lsr,_FILE_AND_LINE_); + InsertConstructedReplica(lsr); + queryToSerializeReplicaList.push_back(lsr); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2017,11 +2052,11 @@ void Connection_RM3::OnNeverSerialize(LastSerializationResult *lsr, ReplicaManag ValidateLists(replicaManager); unsigned int j; - for (j=0; j < queryToSerializeReplicaList.Size(); j++) + for (j=0; j < queryToSerializeReplicaList.size(); j++) { if (queryToSerializeReplicaList[j]==lsr ) { - queryToSerializeReplicaList.RemoveAtIndex(j); + queryToSerializeReplicaList.erase(queryToSerializeReplicaList.begin() + j); break; } } @@ -2039,13 +2074,13 @@ void Connection_RM3::OnReplicaAlreadyExists(unsigned int queryToConstructIdx, Re ValidateLists(replicaManager); LastSerializationResult* lsr = queryToConstructReplicaList[queryToConstructIdx]; - queryToConstructReplicaList.RemoveAtIndex(queryToConstructIdx); + queryToConstructReplicaList.erase(queryToConstructReplicaList.begin() + queryToConstructIdx); //assert(constructedReplicaList.GetIndexOf(lsr->replica)==(unsigned int)-1); - constructedReplicaList.Insert(lsr->replica,lsr,true,_FILE_AND_LINE_); + InsertConstructedReplica(lsr); //assert(queryToDestructReplicaList.GetIndexOf(lsr->replica)==(unsigned int)-1); - queryToDestructReplicaList.Push(lsr,_FILE_AND_LINE_); + queryToDestructReplicaList.push_back(lsr); //assert(queryToSerializeReplicaList.GetIndexOf(lsr->replica)==(unsigned int)-1); - queryToSerializeReplicaList.Push(lsr,_FILE_AND_LINE_); + queryToSerializeReplicaList.push_back(lsr); ValidateLists(replicaManager); } @@ -2059,7 +2094,7 @@ void Connection_RM3::OnDownloadExisting(Replica3* replica3, ReplicaManager3 *rep if (curConstructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || curConstructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) { unsigned int idx; - for (idx=0; idx < queryToConstructReplicaList.Size(); idx++) + for (idx=0; idx < queryToConstructReplicaList.size(); idx++) { if (queryToConstructReplicaList[idx]->replica==replica3) { @@ -2083,26 +2118,26 @@ void Connection_RM3::OnSendDestructionFromQuery(unsigned int queryToDestructIdx, ValidateLists(replicaManager); LastSerializationResult* lsr = queryToDestructReplicaList[queryToDestructIdx]; - queryToDestructReplicaList.RemoveAtIndex(queryToDestructIdx); + queryToDestructReplicaList.erase(queryToDestructReplicaList.begin() + queryToDestructIdx); unsigned int j; - for (j=0; j < queryToSerializeReplicaList.Size(); j++) + for (j=0; j < queryToSerializeReplicaList.size(); j++) { if (queryToSerializeReplicaList[j]->replica==lsr->replica ) { - queryToSerializeReplicaList.RemoveAtIndex(j); + queryToSerializeReplicaList.erase(queryToSerializeReplicaList.begin() + j); break; } } - for (j=0; j < constructedReplicaList.Size(); j++) + for (j=0; j < constructedReplicaList.size(); j++) { if (constructedReplicaList[j]->replica==lsr->replica ) { - constructedReplicaList.RemoveAtIndex(j); + constructedReplicaList.erase(constructedReplicaList.begin() + j); break; } } //assert(queryToConstructReplicaList.GetIndexOf(lsr->replica)==(unsigned int)-1); - queryToConstructReplicaList.Push(lsr,_FILE_AND_LINE_); + queryToConstructReplicaList.push_back(lsr); ValidateLists(replicaManager); } @@ -2111,7 +2146,7 @@ void Connection_RM3::OnSendDestructionFromQuery(unsigned int queryToDestructIdx, void Connection_RM3::OnDoNotQueryDestruction(unsigned int queryToDestructIdx, ReplicaManager3 *replicaManager) { ValidateLists(replicaManager); - queryToDestructReplicaList.RemoveAtIndex(queryToDestructIdx); + queryToDestructReplicaList.erase(queryToDestructReplicaList.begin() + queryToDestructIdx); ValidateLists(replicaManager); } @@ -2125,7 +2160,7 @@ void Connection_RM3::ValidateLists(ReplicaManager3 *replicaManager) const // Each object should exist only once in either constructedReplicaList or queryToConstructReplicaList // replicaPointer from LastSerializationResult should be same among all lists unsigned int idx, idx2; - for (idx=0; idx < constructedReplicaList.Size(); idx++) + for (idx=0; idx < constructedReplicaList.size(); idx++) { idx2=queryToConstructReplicaList.GetIndexOf(constructedReplicaList[idx]->replica); if (idx2!=(unsigned int)-1) @@ -2137,7 +2172,7 @@ void Connection_RM3::ValidateLists(ReplicaManager3 *replicaManager) const } } - for (idx=0; idx < queryToConstructReplicaList.Size(); idx++) + for (idx=0; idx < queryToConstructReplicaList.size(); idx++) { idx2=constructedReplicaList.GetIndexOf(queryToConstructReplicaList[idx]->replica); if (idx2!=(unsigned int)-1) @@ -2150,7 +2185,7 @@ void Connection_RM3::ValidateLists(ReplicaManager3 *replicaManager) const } LastSerializationResult *lsr, *lsr2; - for (idx=0; idx < constructedReplicaList.Size(); idx++) + for (idx=0; idx < constructedReplicaList.size(); idx++) { lsr=constructedReplicaList[idx]; @@ -2180,7 +2215,7 @@ void Connection_RM3::ValidateLists(ReplicaManager3 *replicaManager) const } } } - for (idx=0; idx < queryToConstructReplicaList.Size(); idx++) + for (idx=0; idx < queryToConstructReplicaList.size(); idx++) { lsr=queryToConstructReplicaList[idx]; @@ -2212,7 +2247,7 @@ void Connection_RM3::ValidateLists(ReplicaManager3 *replicaManager) const } // Verify pointer integrity - for (idx=0; idx < constructedReplicaList.Size(); idx++) + for (idx=0; idx < constructedReplicaList.size(); idx++) { if (constructedReplicaList[idx]->replica->replicaManager!=replicaManager) { @@ -2224,7 +2259,7 @@ void Connection_RM3::ValidateLists(ReplicaManager3 *replicaManager) const } // Verify pointer integrity - for (idx=0; idx < queryToConstructReplicaList.Size(); idx++) + for (idx=0; idx < queryToConstructReplicaList.size(); idx++) { if (queryToConstructReplicaList[idx]->replica->replicaManager!=replicaManager) { diff --git a/Source/src/rpc4_plugin.cpp b/Source/src/rpc4_plugin.cpp index d521f8b37..66f8429ac 100644 --- a/Source/src/rpc4_plugin.cpp +++ b/Source/src/rpc4_plugin.cpp @@ -17,6 +17,8 @@ #if _RAKNET_SUPPORT_RPC4Plugin==1 #include "mafianet/rpc4_plugin.h" +#include +#include #include "mafianet/message_identifiers.h" #include "mafianet/peer_interface.h" #include "mafianet/packetized_tcp.h" @@ -147,7 +149,7 @@ RPC4::RPC4() RPC4::~RPC4() { unsigned int i; - for (i=0; i < localCallbacks.Size(); i++) + for (i=0; i < localCallbacks.size(); i++) { MafiaNet::OP_DELETE(localCallbacks[i],_FILE_AND_LINE_); } @@ -164,14 +166,13 @@ RPC4::~RPC4() } bool RPC4::RegisterFunction(const char* uniqueID, void ( *functionPointer ) (MafiaNet::BitStream *userData, Packet *packet, void *context ), void *context) { - DataStructures::HashIndex skhi = registeredNonblockingFunctions.GetIndexOf(uniqueID); - if (skhi.IsInvalid()==false) + if (registeredNonblockingFunctions.find(uniqueID) != registeredNonblockingFunctions.end()) return false; RegisteredNonblockingFunction rnf; rnf.functionPointer=functionPointer; rnf.context=context; - registeredNonblockingFunctions.Push(uniqueID,rnf,_FILE_AND_LINE_); + registeredNonblockingFunctions.insert(std::make_pair(MafiaNet::RakString(uniqueID), rnf)); return true; } void RPC4::RegisterSlot(const char *sharedIdentifier, void ( *functionPointer ) (MafiaNet::BitStream *userData, Packet *packet, void *context ), void *context, int callPriority) @@ -192,16 +193,28 @@ void RPC4::RegisterSlot(const char *sharedIdentifier, void ( *functionPointer ) } bool RPC4::RegisterBlockingFunction(const char* uniqueID, void ( *functionPointer ) (MafiaNet::BitStream *userData, MafiaNet::BitStream *returnData, Packet *packet, void *context ), void *context) { - DataStructures::HashIndex skhi = registeredBlockingFunctions.GetIndexOf(uniqueID); - if (skhi.IsInvalid()==false) + if (registeredBlockingFunctions.find(uniqueID) != registeredBlockingFunctions.end()) return false; RegisteredBlockingFunction rbf; rbf.functionPointer=functionPointer; rbf.context=context; - registeredBlockingFunctions.Push(uniqueID,rbf,_FILE_AND_LINE_); + registeredBlockingFunctions.insert(std::make_pair(MafiaNet::RakString(uniqueID), rbf)); return true; } +unsigned int RPC4::IndexForMessageId(MessageID messageId, bool *found) const +{ + // localCallbacks is sorted ascending by messageId; see the declaration. + std::vector::const_iterator it = std::lower_bound( + localCallbacks.begin(), localCallbacks.end(), messageId, + [](LocalCallback * const &candidate, MessageID id) + { + return candidate->messageId < id; + }); + unsigned int idx = (unsigned int) (it - localCallbacks.begin()); + *found = (it != localCallbacks.end() && (*it)->messageId == messageId); + return idx; +} void RPC4::RegisterLocalCallback(const char* uniqueID, MessageID messageId) { bool objectExists; @@ -209,31 +222,39 @@ void RPC4::RegisterLocalCallback(const char* uniqueID, MessageID messageId) LocalCallback *lc; MafiaNet::RakString str; str=uniqueID; - index = localCallbacks.GetIndexFromKey(messageId,&objectExists); + index = IndexForMessageId(messageId,&objectExists); if (objectExists) { lc = localCallbacks[index]; - index = lc->functions.GetIndexFromKey(str,&objectExists); + unsigned int fnIndex = lc->functions.GetIndexFromKey(str,&objectExists); if (objectExists==false) - lc->functions.InsertAtIndex(str,index,_FILE_AND_LINE_); + lc->functions.InsertAtIndex(str,fnIndex,_FILE_AND_LINE_); } else { lc = MafiaNet::OP_NEW(_FILE_AND_LINE_); lc->messageId=messageId; lc->functions.Insert(str,str,false,_FILE_AND_LINE_); - localCallbacks.InsertAtIndex(lc,index,_FILE_AND_LINE_); + localCallbacks.insert(localCallbacks.begin() + index, lc); } } bool RPC4::UnregisterFunction(const char* uniqueID) { - RegisteredNonblockingFunction f; - return registeredNonblockingFunctions.Pop(f,uniqueID,_FILE_AND_LINE_); + std::unordered_map::iterator it = + registeredNonblockingFunctions.find(uniqueID); + if (it == registeredNonblockingFunctions.end()) + return false; + registeredNonblockingFunctions.erase(it); + return true; } bool RPC4::UnregisterBlockingFunction(const char* uniqueID) { - RegisteredBlockingFunction f; - return registeredBlockingFunctions.Pop(f,uniqueID,_FILE_AND_LINE_); + std::unordered_map::iterator it = + registeredBlockingFunctions.find(uniqueID); + if (it == registeredBlockingFunctions.end()) + return false; + registeredBlockingFunctions.erase(it); + return true; } bool RPC4::UnregisterLocalCallback(const char* uniqueID, MessageID messageId) { @@ -242,7 +263,7 @@ bool RPC4::UnregisterLocalCallback(const char* uniqueID, MessageID messageId) LocalCallback *lc; MafiaNet::RakString str; str=uniqueID; - index = localCallbacks.GetIndexFromKey(messageId,&objectExists); + index = IndexForMessageId(messageId,&objectExists); if (objectExists) { lc = localCallbacks[index]; @@ -253,9 +274,13 @@ bool RPC4::UnregisterLocalCallback(const char* uniqueID, MessageID messageId) if (lc->functions.Size()==0) { MafiaNet::OP_DELETE(lc,_FILE_AND_LINE_); - localCallbacks.RemoveAtIndex(index); - return true; + localCallbacks.erase(localCallbacks.begin() + index); } + // The documented contract is "true if the combination of uniqueID and messageId was + // in use, and hence removed". The return used to sit inside the branch above, so a + // message id with more than one registered name reported false after successfully + // removing one. + return true; } } return false; @@ -277,9 +302,9 @@ void RPC4::CallLoopback( const char* uniqueID, MafiaNet::BitStream * bitStream ) { Packet *p=0; - DataStructures::HashIndex skhi = registeredNonblockingFunctions.GetIndexOf(uniqueID); + const bool registered = registeredNonblockingFunctions.find(uniqueID) != registeredNonblockingFunctions.end(); - if (skhi.IsInvalid()==true) + if (registered==false) { if (rakPeerInterface) p=AllocatePacketUnified(sizeof(MessageID)+sizeof(unsigned char)+(unsigned int) strlen(uniqueID)+1); @@ -377,7 +402,10 @@ bool RPC4::CallBlocking( const char* uniqueID, MafiaNet::BitStream * bitStream, blockingReturnValue.Reset(); gotBlockingReturnValue=false; Packet *packet; - DataStructures::Queue packetQueue; + // DataStructures::Queue was used here as a stack: PushAtHead(p,0) pushed to the front and + // Pop() took from the front, so held packets are handed back in reverse arrival order. + // std::deque with push_front/pop_front keeps that exactly. + std::deque packetQueue; while (gotBlockingReturnValue==false) { // TODO - block, filter until gotBlockingReturnValue==true or ID_CONNECTION_LOST or ID_DISCONNECTION_NOTIFICXATION or ID_RPC_REMOTE_ERROR/RPC_ERROR_FUNCTION_NOT_REGISTERED @@ -395,8 +423,12 @@ bool RPC4::CallBlocking( const char* uniqueID, MafiaNet::BitStream * bitStream, { // Push back to head in reverse order rakPeerInterface->PushBackPacket(packet,true); - while (packetQueue.Size()) - rakPeerInterface->PushBackPacket(packetQueue.Pop(),true); + while (packetQueue.empty()==false) + { + Packet *held = packetQueue.front(); + packetQueue.pop_front(); + rakPeerInterface->PushBackPacket(held,true); + } return false; } else if (packet->data[0]==ID_RPC_REMOTE_ERROR && packet->data[1]==RPC_ERROR_FUNCTION_NOT_REGISTERED) @@ -409,18 +441,22 @@ bool RPC4::CallBlocking( const char* uniqueID, MafiaNet::BitStream * bitStream, { // Push back to head in reverse order rakPeerInterface->PushBackPacket(packet,true); - while (packetQueue.Size()) - rakPeerInterface->PushBackPacket(packetQueue.Pop(),true); + while (packetQueue.empty()==false) + { + Packet *held = packetQueue.front(); + packetQueue.pop_front(); + rakPeerInterface->PushBackPacket(held,true); + } return false; } else { - packetQueue.PushAtHead(packet,0,_FILE_AND_LINE_); + packetQueue.push_front(packet); } } else { - packetQueue.PushAtHead(packet,0,_FILE_AND_LINE_); + packetQueue.push_front(packet); } } } @@ -553,8 +589,9 @@ PluginReceiveResult RPC4::OnReceive(Packet *packet) bsIn.Read(isBlocking); if (isBlocking==false) { - DataStructures::HashIndex skhi = registeredNonblockingFunctions.GetIndexOf(functionName.C_String()); - if (skhi.IsInvalid()) + std::unordered_map::const_iterator + fnIt = registeredNonblockingFunctions.find(functionName); + if (fnIt == registeredNonblockingFunctions.end()) { MafiaNet::BitStream bsOut; bsOut.Write((unsigned char) ID_RPC_REMOTE_ERROR); @@ -564,15 +601,15 @@ PluginReceiveResult RPC4::OnReceive(Packet *packet) return RR_STOP_PROCESSING_AND_DEALLOCATE; } - RegisteredNonblockingFunction rnf; - rnf = registeredNonblockingFunctions.ItemAtIndex(skhi); + RegisteredNonblockingFunction rnf = fnIt->second; bsIn.AlignReadToByteBoundary(); rnf.functionPointer(&bsIn,packet,rnf.context); } else { - DataStructures::HashIndex skhi = registeredBlockingFunctions.GetIndexOf(functionName.C_String()); - if (skhi.IsInvalid()) + std::unordered_map::const_iterator + fnIt = registeredBlockingFunctions.find(functionName); + if (fnIt == registeredBlockingFunctions.end()) { MafiaNet::BitStream bsOut; bsOut.Write((unsigned char) ID_RPC_REMOTE_ERROR); @@ -583,7 +620,7 @@ PluginReceiveResult RPC4::OnReceive(Packet *packet) } RegisteredBlockingFunction rbf; - rbf = registeredBlockingFunctions.ItemAtIndex(skhi); + rbf = fnIt->second; MafiaNet::BitStream returnData; bsIn.AlignReadToByteBoundary(); rbf.functionPointer(&bsIn, &returnData, packet, rbf.context); @@ -621,7 +658,7 @@ PluginReceiveResult RPC4::OnReceive(Packet *packet) bool objectExists; unsigned int index, index2; - index = localCallbacks.GetIndexFromKey(packet->data[0],&objectExists); + index = IndexForMessageId(packet->data[0],&objectExists); if (objectExists) { LocalCallback *lc; @@ -630,11 +667,11 @@ PluginReceiveResult RPC4::OnReceive(Packet *packet) { MafiaNet::BitStream bsIn(packet->data, packet->length, false); - DataStructures::HashIndex skhi = registeredNonblockingFunctions.GetIndexOf(lc->functions[index2].C_String()); - if (skhi.IsInvalid()==false) + std::unordered_map::const_iterator + fnIt = registeredNonblockingFunctions.find(lc->functions[index2]); + if (fnIt != registeredNonblockingFunctions.end()) { - RegisteredNonblockingFunction rnf; - rnf = registeredNonblockingFunctions.ItemAtIndex(skhi); + RegisteredNonblockingFunction rnf = fnIt->second; bsIn.AlignReadToByteBoundary(); rnf.functionPointer(&bsIn,packet,rnf.context); } diff --git a/Source/src/signaled_event.cpp b/Source/src/signaled_event.cpp index cac22065f..07e235294 100644 --- a/Source/src/signaled_event.cpp +++ b/Source/src/signaled_event.cpp @@ -30,6 +30,7 @@ using namespace MafiaNet; SignaledEvent::SignaledEvent() { + isInitialized=false; #ifdef _WIN32 eventList=INVALID_HANDLE_VALUE; @@ -47,8 +48,19 @@ void SignaledEvent::InitEvent(void) { #if defined(_WIN32) eventList=CreateEvent(0, false, false, 0); + isInitialized=true; #else + isSignaledMutex.Lock(); + if (isInitialized) + { + // pthread_cond_init on an already-initialised condition variable is + // undefined, so a second InitEvent without a CloseEvent is a no-op. + isSignaledMutex.Unlock(); + return; + } + isSignaledMutex.Unlock(); + #if !defined(ANDROID) pthread_condattr_init( &condAttr ); pthread_cond_init(&eventList, &condAttr); @@ -57,12 +69,17 @@ void SignaledEvent::InitEvent(void) #endif pthread_mutexattr_init( &mutexAttr ); pthread_mutex_init(&hMutex, &mutexAttr); + + isSignaledMutex.Lock(); + isInitialized=true; + isSignaledMutex.Unlock(); #endif } void SignaledEvent::CloseEvent(void) { #ifdef _WIN32 + isInitialized=false; if (eventList!=INVALID_HANDLE_VALUE) { CloseHandle(eventList); @@ -78,6 +95,16 @@ void SignaledEvent::CloseEvent(void) #else + isSignaledMutex.Lock(); + if (isInitialized==false) + { + // Nothing was ever created; destroying it would be undefined. + isSignaledMutex.Unlock(); + return; + } + isInitialized=false; + isSignaledMutex.Unlock(); + pthread_cond_destroy(&eventList); pthread_mutex_destroy(&hMutex); #if !defined(ANDROID) @@ -90,6 +117,8 @@ void SignaledEvent::CloseEvent(void) void SignaledEvent::SetEvent(void) { #ifdef _WIN32 + if (eventList==INVALID_HANDLE_VALUE) + return; ::SetEvent(eventList); @@ -106,10 +135,14 @@ void SignaledEvent::SetEvent(void) // We have to record manually that the event was signaled isSignaledMutex.Lock(); isSignaled=true; + const bool broadcast = isInitialized; isSignaledMutex.Unlock(); - // Unblock waiting threads - pthread_cond_broadcast(&eventList); + // Unblock waiting threads. Skipped when there is no condition variable yet: + // isSignaled above is still recorded, so a WaitOnEvent after a later + // InitEvent still sees the signal. + if (broadcast) + pthread_cond_broadcast(&eventList); #endif } @@ -121,6 +154,8 @@ void SignaledEvent::WaitOnEvent(int timeoutMs) // eventList, // false, // timeoutMs); + if (eventList==INVALID_HANDLE_VALUE) + return; WaitForSingleObjectEx(eventList,timeoutMs,FALSE); @@ -173,6 +208,13 @@ void SignaledEvent::WaitOnEvent(int timeoutMs) isSignaledMutex.Unlock(); return; } + if (isInitialized==false) + { + // No condition variable to wait on: pthread_cond_timedwait below would be + // undefined. Return as though the wait timed out. + isSignaledMutex.Unlock(); + return; + } isSignaledMutex.Unlock(); diff --git a/Source/src/statistics_history.cpp b/Source/src/statistics_history.cpp index 3ba358c6c..e5c7166cc 100644 --- a/Source/src/statistics_history.cpp +++ b/Source/src/statistics_history.cpp @@ -17,6 +17,7 @@ #if _RAKNET_SUPPORT_StatisticsHistory==1 #include "mafianet/statistics_history.h" +#include #include "mafianet/get_time.h" #include "mafianet/statistics.h" #include "mafianet/peer_interface.h" @@ -77,12 +78,12 @@ Time StatisticsHistory::GetDefaultTimeToTrack(void) const {return timeToTrack;} bool StatisticsHistory::AddObject(TrackedObjectData tod) { bool objectExists; - unsigned int idx = objects.GetIndexFromKey(tod.objectId, &objectExists); + unsigned int idx = IndexForObjectId(tod.objectId, &objectExists); if (objectExists) return false; TrackedObject *to = MafiaNet::OP_NEW(_FILE_AND_LINE_); to->trackedObjectData=tod; - objects.InsertAtIndex(to,idx,_FILE_AND_LINE_); + objects.insert(objects.begin() + idx, to); return true; } bool StatisticsHistory::RemoveObject(uint64_t objectId, void **userData) @@ -98,18 +99,18 @@ bool StatisticsHistory::RemoveObject(uint64_t objectId, void **userData) void StatisticsHistory::RemoveObjectAtIndex(unsigned int index) { TrackedObject *to = objects[index]; - objects.RemoveAtIndex(index); + objects.erase(objects.begin() + index); MafiaNet::OP_DELETE(to, _FILE_AND_LINE_); } void StatisticsHistory::Clear(void) { - for (unsigned int idx=0; idx < objects.Size(); idx++) + for (unsigned int idx=0; idx < objects.size(); idx++) { MafiaNet::OP_DELETE(objects[idx], _FILE_AND_LINE_); } - objects.Clear(false, _FILE_AND_LINE_); + objects.clear(); } -unsigned int StatisticsHistory::GetObjectCount(void) const {return objects.Size();} +unsigned int StatisticsHistory::GetObjectCount(void) const {return (unsigned int) objects.size();} StatisticsHistory::TrackedObjectData * StatisticsHistory::GetObjectAtIndex(unsigned int index) const {return &objects[index]->trackedObjectData;} bool StatisticsHistory::AddValueByObjectID(uint64_t objectId, RakString key, SHValueType val, Time curTime, bool combineEqualTimes) { @@ -123,17 +124,18 @@ void StatisticsHistory::AddValueByIndex(unsigned int index, RakString key, SHVal { TimeAndValueQueue *queue; TrackedObject *to = objects[index]; - DataStructures::HashIndex hi = to->dataQueues.GetIndexOf(key); - if (hi.IsInvalid()) + std::unordered_map::iterator it = + to->dataQueues.find(key); + if (it == to->dataQueues.end()) { queue = MafiaNet::OP_NEW(_FILE_AND_LINE_); queue->key=key; queue->timeToTrackValues = timeToTrack; - to->dataQueues.Push(key, queue, _FILE_AND_LINE_); + to->dataQueues.insert(std::make_pair(key, queue)); } else { - queue = to->dataQueues.ItemAtIndex(hi); + queue = it->second; } TimeAndValue tav; @@ -173,10 +175,11 @@ StatisticsHistory::SHErrorCode StatisticsHistory::GetHistoryForKey(uint64_t obje if (idx == (unsigned int) -1) return SH_UKNOWN_OBJECT; TrackedObject *to = objects[idx]; - DataStructures::HashIndex hi = to->dataQueues.GetIndexOf(key); - if (hi.IsInvalid()) + std::unordered_map::const_iterator it = + to->dataQueues.find(key); + if (it == to->dataQueues.end()) return SH_UKNOWN_KEY; - *values = to->dataQueues.ItemAtIndex(hi); + *values = it->second; (*values)->CullExpiredValues(curTime); return SH_OK; } @@ -186,13 +189,15 @@ bool StatisticsHistory::GetHistorySorted(uint64_t objectId, SHSortOperation sort if (idx == (unsigned int) -1) return false; TrackedObject *to = objects[idx]; - DataStructures::List itemList; - DataStructures::List keyList; - to->dataQueues.GetAsList(itemList,keyList,_FILE_AND_LINE_); + std::vector itemList; + itemList.reserve(to->dataQueues.size()); + for (std::unordered_map::const_iterator + it = to->dataQueues.begin(); it != to->dataQueues.end(); ++it) + itemList.push_back(it->second); Time curTime = GetTime(); - DataStructures::OrderedList sortedQueues; - for (unsigned int i=0; i < itemList.Size(); i++) + bool ascending = false; + for (unsigned int i=0; i < itemList.size(); i++) { TimeAndValueQueue *tavq = itemList[i]; tavq->CullExpiredValues(curTime); @@ -226,13 +231,21 @@ bool StatisticsHistory::GetHistorySorted(uint64_t objectId, SHSortOperation sort sortType == SH_SORT_BY_RECENT_LOWEST_ASCENDING || sortType == SH_SORT_BY_LONG_TERM_HIGHEST_ASCENDING || sortType == SH_SORT_BY_LONG_TERM_LOWEST_ASCENDING) - sortedQueues.Insert(tavq, tavq, false, _FILE_AND_LINE_, TimeAndValueQueueCompAsc); - else - sortedQueues.Insert(tavq, tavq, false, _FILE_AND_LINE_, TimeAndValueQueueCompDesc); + ascending = true; } - for (unsigned int i=0; i < sortedQueues.Size(); i++) - values.Push(sortedQueues[i], _FILE_AND_LINE_); + // Same comparators the OrderedList was given. They order by sortValue and tie-break on the + // key name, and keys are unique within an object, so no two distinct queues compare equal. + if (ascending) + std::sort(itemList.begin(), itemList.end(), + [](TimeAndValueQueue *a, TimeAndValueQueue *b) { return TimeAndValueQueueCompAsc(a, b) < 0; }); + else + std::sort(itemList.begin(), itemList.end(), + [](TimeAndValueQueue *a, TimeAndValueQueue *b) { return TimeAndValueQueueCompDesc(a, b) < 0; }); + + // values is a public DataStructures::List out-parameter; stage 3 changes the signature. + for (unsigned int i=0; i < itemList.size(); i++) + values.Push(itemList[i], _FILE_AND_LINE_); return true; } void StatisticsHistory::MergeAllObjectsOnKey(RakString key, TimeAndValueQueue *tavqOutput, SHDataCategory dataCategory) const @@ -242,13 +255,14 @@ void StatisticsHistory::MergeAllObjectsOnKey(RakString key, TimeAndValueQueue *t Time curTime = GetTime(); // Find every object with this key - for (unsigned int idx=0; idx < objects.Size(); idx++) + for (unsigned int idx=0; idx < objects.size(); idx++) { TrackedObject *to = objects[idx]; - DataStructures::HashIndex hi = to->dataQueues.GetIndexOf(key); - if (hi.IsInvalid()==false) + std::unordered_map::const_iterator it = + to->dataQueues.find(key); + if (it != to->dataQueues.end()) { - TimeAndValueQueue *tavqInput = to->dataQueues.ItemAtIndex(hi); + TimeAndValueQueue *tavqInput = it->second; tavqInput->CullExpiredValues(curTime); TimeAndValueQueue::MergeSets(tavqOutput, dataCategory, tavqInput, dataCategory, tavqOutput); } @@ -258,18 +272,16 @@ void StatisticsHistory::GetUniqueKeyList(DataStructures::List &keys) { keys.Clear(true, _FILE_AND_LINE_); - for (unsigned int idx=0; idx < objects.Size(); idx++) + for (unsigned int idx=0; idx < objects.size(); idx++) { TrackedObject *to = objects[idx]; - DataStructures::List itemList; - DataStructures::List keyList; - to->dataQueues.GetAsList(itemList, keyList, _FILE_AND_LINE_); - for (unsigned int k=0; k < keyList.Size(); k++) + for (std::unordered_map::const_iterator + it = to->dataQueues.begin(); it != to->dataQueues.end(); ++it) { bool hasKey=false; for (unsigned int j=0; j < keys.Size(); j++) { - if (keys[j]==keyList[k]) + if (keys[j]==it->first) { hasKey=true; break; @@ -277,7 +289,7 @@ void StatisticsHistory::GetUniqueKeyList(DataStructures::List &keys) } if (hasKey==false) - keys.Push(keyList[k], _FILE_AND_LINE_); + keys.Push(it->first, _FILE_AND_LINE_); } } } @@ -699,15 +711,31 @@ StatisticsHistory::TimeAndValueQueue& StatisticsHistory::TimeAndValueQueue::oper StatisticsHistory::TrackedObject::TrackedObject() {} StatisticsHistory::TrackedObject::~TrackedObject() { - DataStructures::List itemList; - DataStructures::List keyList; - for (unsigned int idx=0; idx < itemList.Size(); idx++) - MafiaNet::OP_DELETE(itemList[idx], _FILE_AND_LINE_); + // This used to declare two empty local lists and then iterate the empty one, never + // calling GetAsList, so every TimeAndValueQueue this object owned leaked -- one per + // distinct key passed to AddValueByObjectID. Confirmed under LeakSanitizer. + for (std::unordered_map::iterator + it = dataQueues.begin(); it != dataQueues.end(); ++it) + MafiaNet::OP_DELETE(it->second, _FILE_AND_LINE_); + dataQueues.clear(); +} +unsigned int StatisticsHistory::IndexForObjectId(uint64_t objectId, bool *found) const +{ + // objects is sorted ascending by objectId; see the declaration for why that is a contract. + std::vector::const_iterator it = std::lower_bound( + objects.begin(), objects.end(), objectId, + [](TrackedObject * const &candidate, uint64_t id) + { + return candidate->trackedObjectData.objectId < id; + }); + unsigned int idx = (unsigned int) (it - objects.begin()); + *found = (it != objects.end() && (*it)->trackedObjectData.objectId == objectId); + return idx; } unsigned int StatisticsHistory::GetObjectIndex(uint64_t objectId) const { bool objectExists; - unsigned int idx = objects.GetIndexFromKey(objectId, &objectExists); + unsigned int idx = IndexForObjectId(objectId, &objectExists); if (objectExists) return idx; return (unsigned int) -1; diff --git a/Source/src/tcp_interface.cpp b/Source/src/tcp_interface.cpp index f1b2640e2..c702f31b8 100644 --- a/Source/src/tcp_interface.cpp +++ b/Source/src/tcp_interface.cpp @@ -23,6 +23,7 @@ #include "mafianet/tcp_interface.h" +#include #ifdef _WIN32 typedef int socklen_t; #else @@ -63,6 +64,25 @@ RAK_THREAD_DECLARATION(ConnectionAttemptLoop); using namespace MafiaNet; +namespace +{ + // accept()/socket() report failure as -1 on POSIX and INVALID_SOCKET on Windows, + // while this class uses 0 as its "no socket" value. Everything else here tested + // only against 0, so a failed accept was treated as a new connection and its -1 + // was stored as a client socket; the next FD_SET(-1, ...) is undefined, and + // glibc's fortified fd_set aborts on it in a Release build ("bit out of range + // 0 - FD_SETSIZE on fd_set") while a Debug build corrupts the set in silence. + bool IsUsableSocket(__TCPSOCKET__ s) + { +#ifdef _WIN32 + return s != INVALID_SOCKET && s != 0; +#else + return s > 0; +#endif + } +} + + STATIC_FACTORY_DEFINITIONS(TCPInterface,TCPInterface); TCPInterface::TCPInterface() @@ -102,8 +122,14 @@ bool TCPInterface::CreateListenSocket(unsigned short port, unsigned short maxInc (void) socketFamily; #if RAKNET_SUPPORT_IPV6!=1 listenSocket = socket__(AF_INET, SOCK_STREAM, 0); - if ((int)listenSocket ==-1) + if (IsUsableSocket(listenSocket)==false) + { + // `(int)listenSocket == -1` was the old test: on Windows SOCKET is unsigned, + // so the cast is what made INVALID_SOCKET match at all, and a socket + // numbered 0 -- which this class cannot represent -- was accepted. + listenSocket=0; return false; + } struct sockaddr_in serverAddress; memset(&serverAddress,0,sizeof(sockaddr_in)); @@ -206,7 +232,22 @@ bool TCPInterface::Start(unsigned short port, unsigned short maxIncomingConnecti listenSocket=0; if (maxIncomingConnections>0) { - CreateListenSocket(port, maxIncomingConnections, socketFamily, bindAddress); + // The result was discarded, so a failed bind -- the port already in use, say + // -- left Start() returning true with nothing listening, and the caller had + // no way to find out. + if (CreateListenSocket(port, maxIncomingConnections, socketFamily, bindAddress)==false) + { + if (listenSocket!=0) + { + closesocket__(listenSocket); + listenSocket=0; + } + remoteClientsLength=0; + MafiaNet::OP_DELETE_ARRAY(remoteClients,_FILE_AND_LINE_); + remoteClients=0; + isStarted.Decrement(); + return false; + } } @@ -227,7 +268,7 @@ bool TCPInterface::Start(unsigned short port, unsigned short maxIncomingConnecti RakSleep(0); unsigned int i; - for (i=0; i < messageHandlerList.Size(); i++) + for (i=0; i < messageHandlerList.size(); i++) messageHandlerList[i]->OnRakPeerStartup(); return true; @@ -235,7 +276,7 @@ bool TCPInterface::Start(unsigned short port, unsigned short maxIncomingConnecti void TCPInterface::Stop(void) { unsigned int i; - for (i=0; i < messageHandlerList.Size(); i++) + for (i=0; i < messageHandlerList.size(); i++) messageHandlerList[i]->OnRakPeerShutdown(); if (isStarted.GetValue()==0) @@ -261,10 +302,15 @@ void TCPInterface::Stop(void) // Abort waiting connect calls blockingSocketListMutex.Lock(); - for (i=0; i < blockingSocketList.Size(); i++) + for (i=0; i < blockingSocketList.size(); i++) { closesocket__(blockingSocketList[i]); } + // The list was not cleared here, so a second Stop() -- or the one in the + // destructor -- closed the same descriptors again. Descriptor numbers are + // reused, so the second close can land on a socket something else has since + // opened. + blockingSocketList.clear(); blockingSocketListMutex.Unlock(); // Wait for the thread to stop @@ -278,7 +324,14 @@ void TCPInterface::Stop(void) // Stuff from here on to the end of the function is not threadsafe for (i=0; i < remoteClientsLength; i++) { - closesocket__(remoteClients[i].socket); + // 0 is this class's "no socket" value (RemoteClient's constructor sets it, + // and RemoteClient::SetActive guards on it), but it is also a perfectly + // valid descriptor. Closing it unconditionally closed descriptor 0 -- + // stdin in a normal process -- once per unused slot. Beyond the obvious, + // that freed descriptor 0 for reuse, and the next socket created in the + // process got it and was then read as "no socket" by every check here. + if (remoteClients[i].socket!=0) + closesocket__(remoteClients[i].socket); #if OPEN_SSL_CLIENT_SUPPORT==1 remoteClients[i].FreeSSL(); #endif @@ -299,20 +352,20 @@ void TCPInterface::Stop(void) newRemoteClients.Clear(_FILE_AND_LINE_); lostConnections.Clear(_FILE_AND_LINE_); requestedCloseConnections.Clear(_FILE_AND_LINE_); - failedConnectionAttempts.Clear(_FILE_AND_LINE_); - completedConnectionAttempts.Clear(_FILE_AND_LINE_); - failedConnectionAttempts.Clear(_FILE_AND_LINE_); - for (i=0; i < headPush.Size(); i++) + failedConnectionAttempts.clear(); + completedConnectionAttempts.clear(); + failedConnectionAttempts.clear(); + for (i=0; i < headPush.size(); i++) DeallocatePacket(headPush[i]); - headPush.Clear(_FILE_AND_LINE_); - for (i=0; i < tailPush.Size(); i++) + headPush.clear(); + for (i=0; i < tailPush.size(); i++) DeallocatePacket(tailPush[i]); - tailPush.Clear(_FILE_AND_LINE_); + tailPush.clear(); #if OPEN_SSL_CLIENT_SUPPORT==1 SSL_CTX_free (ctx); startSSL.Clear(_FILE_AND_LINE_); - activeSSLConnections.Clear(false, _FILE_AND_LINE_); + activeSSLConnections.clear(); #endif @@ -358,7 +411,7 @@ SystemAddress TCPInterface::Connect(const char* host, unsigned short remotePort, remoteClients[newRemoteClientIndex].isActiveMutex.Unlock(); failedConnectionAttemptMutex.Lock(); - failedConnectionAttempts.Push(systemAddress, _FILE_AND_LINE_ ); + failedConnectionAttempts.push_back(systemAddress); failedConnectionAttemptMutex.Unlock(); return UNASSIGNED_SYSTEM_ADDRESS; @@ -368,7 +421,7 @@ SystemAddress TCPInterface::Connect(const char* host, unsigned short remotePort, remoteClients[newRemoteClientIndex].systemAddress=systemAddress; completedConnectionAttemptMutex.Lock(); - completedConnectionAttempts.Push(remoteClients[newRemoteClientIndex].systemAddress, _FILE_AND_LINE_ ); + completedConnectionAttempts.push_back(remoteClients[newRemoteClientIndex].systemAddress); completedConnectionAttemptMutex.Unlock(); return remoteClients[newRemoteClientIndex].systemAddress; @@ -395,8 +448,11 @@ SystemAddress TCPInterface::Connect(const char* host, unsigned short remotePort, if (errorCode!=0) { + // Read the address out before freeing s: this used to report + // s->systemAddress after the delete. + const SystemAddress failedAddress = s->systemAddress; MafiaNet::OP_DELETE(s, _FILE_AND_LINE_); - failedConnectionAttempts.Push(s->systemAddress, _FILE_AND_LINE_ ); + failedConnectionAttempts.push_back(failedAddress); } return UNASSIGNED_SYSTEM_ADDRESS; } @@ -416,13 +472,13 @@ void TCPInterface::StartSSLClient(SystemAddress systemAddress) SystemAddress *id = startSSL.Allocate( _FILE_AND_LINE_ ); *id=systemAddress; startSSL.Push(id); - unsigned index = activeSSLConnections.GetIndexOf(systemAddress); - if (index==(unsigned)-1) - activeSSLConnections.Insert(systemAddress,_FILE_AND_LINE_); + if (std::find(activeSSLConnections.begin(), activeSSLConnections.end(), systemAddress) + == activeSSLConnections.end()) + activeSSLConnections.push_back(systemAddress); } bool TCPInterface::IsSSLActive(SystemAddress systemAddress) { - return activeSSLConnections.GetIndexOf(systemAddress)!=-1; + return std::find(activeSSLConnections.begin(), activeSSLConnections.end(), systemAddress) != activeSSLConnections.end(); } #endif void TCPInterface::Send( const char *data, unsigned length, const SystemAddress &systemAddress, bool broadcast ) @@ -483,12 +539,12 @@ bool TCPInterface::SendList( const char **data, const unsigned int *lengths, con } bool TCPInterface::ReceiveHasPackets( void ) { - return headPush.IsEmpty()==false || incomingMessages.IsEmpty()==false || tailPush.IsEmpty()==false; + return headPush.empty()==false || incomingMessages.IsEmpty()==false || tailPush.empty()==false; } Packet* TCPInterface::Receive( void ) { unsigned int i; - for (i=0; i < messageHandlerList.Size(); i++) + for (i=0; i < messageHandlerList.size(); i++) messageHandlerList[i]->Update(); Packet* outgoingPacket = ReceiveInt(); @@ -496,7 +552,7 @@ Packet* TCPInterface::Receive( void ) if (outgoingPacket) { PluginReceiveResult pluginResult; - for (i=0; i < messageHandlerList.Size(); i++) + for (i=0; i < messageHandlerList.size(); i++) { pluginResult=messageHandlerList[i]->OnReceive(outgoingPacket); if (pluginResult==RR_STOP_PROCESSING_AND_DEALLOCATE) @@ -520,22 +576,30 @@ Packet* TCPInterface::ReceiveInt( void ) { if (isStarted.GetValue()==0) return 0; - if (headPush.IsEmpty()==false) - return headPush.Pop(); + if (headPush.empty()==false) + { + Packet *held = headPush.front(); + headPush.pop_front(); + return held; + } Packet *p = incomingMessages.PopInaccurate(); if (p) return p; - if (tailPush.IsEmpty()==false) - return tailPush.Pop(); + if (tailPush.empty()==false) + { + Packet *held = tailPush.front(); + tailPush.pop_front(); + return held; + } return 0; } void TCPInterface::AttachPlugin( PluginInterface2 *plugin ) { - if (messageHandlerList.GetIndexOf(plugin)==MAX_UNSIGNED_LONG) + if (std::find(messageHandlerList.begin(), messageHandlerList.end(), plugin) == messageHandlerList.end()) { - messageHandlerList.Insert(plugin, _FILE_AND_LINE_); + messageHandlerList.push_back(plugin); plugin->SetTCPInterface(this); plugin->OnAttach(); } @@ -545,14 +609,16 @@ void TCPInterface::DetachPlugin( PluginInterface2 *plugin ) if (plugin==0) return; - unsigned int index; - index = messageHandlerList.GetIndexOf(plugin); - if (index!=MAX_UNSIGNED_LONG) + std::vector::iterator it = + std::find(messageHandlerList.begin(), messageHandlerList.end(), plugin); + if (it!=messageHandlerList.end()) { - messageHandlerList[index]->OnDetach(); + (*it)->OnDetach(); // Unordered list so delete from end for speed - messageHandlerList[index]=messageHandlerList[messageHandlerList.Size()-1]; - messageHandlerList.RemoveFromEnd(); + // Swap-remove, exactly as before: the handler order is not published and this avoids + // shifting the rest. + *it=messageHandlerList.back(); + messageHandlerList.pop_back(); plugin->SetTCPInterface(0); } } @@ -564,7 +630,7 @@ void TCPInterface::CloseConnection( SystemAddress systemAddress ) return; unsigned int i; - for (i=0; i < messageHandlerList.Size(); i++) + for (i=0; i < messageHandlerList.size(); i++) messageHandlerList[i]->OnClosedConnection(systemAddress, UNASSIGNED_RAKNET_GUID, LCR_CLOSED_BY_USER); if (systemAddress.systemIndex::iterator sslIt = + std::find(activeSSLConnections.begin(), activeSSLConnections.end(), systemAddress); + if (sslIt!=activeSSLConnections.end()) + activeSSLConnections.erase(sslIt); #endif } void TCPInterface::DeallocatePacket( Packet *packet ) @@ -626,9 +693,9 @@ Packet* TCPInterface::AllocatePacket(unsigned dataSize) void TCPInterface::PushBackPacket( Packet *packet, bool pushAtHead ) { if (pushAtHead) - headPush.Push(packet, _FILE_AND_LINE_ ); + headPush.push_back(packet); else - tailPush.Push(packet, _FILE_AND_LINE_ ); + tailPush.push_back(packet); } bool TCPInterface::WasStarted(void) const { @@ -638,14 +705,17 @@ SystemAddress TCPInterface::HasCompletedConnectionAttempt(void) { SystemAddress sysAddr=UNASSIGNED_SYSTEM_ADDRESS; completedConnectionAttemptMutex.Lock(); - if (completedConnectionAttempts.IsEmpty()==false) - sysAddr=completedConnectionAttempts.Pop(); + if (completedConnectionAttempts.empty()==false) + { + sysAddr=completedConnectionAttempts.front(); + completedConnectionAttempts.pop_front(); + } completedConnectionAttemptMutex.Unlock(); if (sysAddr!=UNASSIGNED_SYSTEM_ADDRESS) { unsigned int i; - for (i=0; i < messageHandlerList.Size(); i++) + for (i=0; i < messageHandlerList.size(); i++) messageHandlerList[i]->OnNewConnection(sysAddr, UNASSIGNED_RAKNET_GUID, true); } @@ -655,14 +725,17 @@ SystemAddress TCPInterface::HasFailedConnectionAttempt(void) { SystemAddress sysAddr=UNASSIGNED_SYSTEM_ADDRESS; failedConnectionAttemptMutex.Lock(); - if (failedConnectionAttempts.IsEmpty()==false) - sysAddr=failedConnectionAttempts.Pop(); + if (failedConnectionAttempts.empty()==false) + { + sysAddr=failedConnectionAttempts.front(); + failedConnectionAttempts.pop_front(); + } failedConnectionAttemptMutex.Unlock(); if (sysAddr!=UNASSIGNED_SYSTEM_ADDRESS) { unsigned int i; - for (i=0; i < messageHandlerList.Size(); i++) + for (i=0; i < messageHandlerList.size(); i++) { Packet p; p.systemAddress=sysAddr; @@ -685,7 +758,7 @@ SystemAddress TCPInterface::HasNewIncomingConnection(void) newIncomingConnections.Deallocate(out, _FILE_AND_LINE_); unsigned int i; - for (i=0; i < messageHandlerList.Size(); i++) + for (i=0; i < messageHandlerList.size(); i++) messageHandlerList[i]->OnNewConnection(out2, UNASSIGNED_RAKNET_GUID, true); return *out; @@ -705,7 +778,7 @@ SystemAddress TCPInterface::HasLostConnection(void) lostConnections.Deallocate(out, _FILE_AND_LINE_); unsigned int i; - for (i=0; i < messageHandlerList.Size(); i++) + for (i=0; i < messageHandlerList.size(); i++) messageHandlerList[i]->OnClosedConnection(out2, UNASSIGNED_RAKNET_GUID, LCR_DISCONNECTION_NOTIFICATION); return *out; @@ -774,11 +847,21 @@ __TCPSOCKET__ TCPInterface::SocketConnect(const char* host, unsigned short remot #if RAKNET_SUPPORT_IPV6!=1 sockaddr_in serverAddress; - struct addrinfo *curAddress = nullptr; - int err = getaddrinfo(host, nullptr, nullptr, &curAddress); + // addressList owns the list getaddrinfo allocated and is what has to be freed. + // curAddress walks it; the walk used to overwrite the only pointer to the head, + // and nothing freed it on any path, so every connect attempt leaked the list. + struct addrinfo *addressList = nullptr; + const int err = getaddrinfo(host, nullptr, nullptr, &addressList); + if (err != 0) + { + if (addressList != nullptr) + freeaddrinfo(addressList); + return 0; + } // #med This should also work with IPv6 addresses in principle. However the old code in RakNet only worked with IPv4. Hence this behavior change won't be in MaxNet 0.1. // get the (first) IPv4 address + struct addrinfo *curAddress = addressList; while(curAddress != nullptr) { if (curAddress->ai_family == AF_INET) { break; // found an IPv4 address @@ -787,15 +870,17 @@ __TCPSOCKET__ TCPInterface::SocketConnect(const char* host, unsigned short remot } if (curAddress == nullptr) + { + freeaddrinfo(addressList); return 0; - - if (err != 0) - return 0; - + } __TCPSOCKET__ sockfd = socket__(AF_INET, SOCK_STREAM, 0); - if (sockfd < 0) + if (IsUsableSocket(sockfd)==false) + { + freeaddrinfo(addressList); return 0; + } memset(&serverAddress, 0, sizeof(serverAddress)); serverAddress.sin_family = AF_INET; @@ -830,12 +915,14 @@ __TCPSOCKET__ TCPInterface::SocketConnect(const char* host, unsigned short remot blockingSocketListMutex.Lock(); - blockingSocketList.Insert(sockfd, _FILE_AND_LINE_); + blockingSocketList.push_back(sockfd); blockingSocketListMutex.Unlock(); // This is blocking connectResult = connect__( sockfd, ( struct sockaddr * ) &serverAddress, sizeof( struct sockaddr ) ); + freeaddrinfo(addressList); + #else (void)bindAddress; @@ -849,7 +936,7 @@ __TCPSOCKET__ TCPInterface::SocketConnect(const char* host, unsigned short remot getaddrinfo(host, portStr, &hints, &res); sockfd = socket__(res->ai_family, res->ai_socktype, res->ai_protocol); blockingSocketListMutex.Lock(); - blockingSocketList.Insert(sockfd, _FILE_AND_LINE_); + blockingSocketList.push_back(sockfd); blockingSocketListMutex.Unlock(); // #low - review usage of static cast here connectResult=connect__(sockfd, res->ai_addr, static_cast(res->ai_addrlen)); @@ -859,11 +946,15 @@ __TCPSOCKET__ TCPInterface::SocketConnect(const char* host, unsigned short remot if (connectResult==-1) { - unsigned sockfdIndex; blockingSocketListMutex.Lock(); - sockfdIndex=blockingSocketList.GetIndexOf(sockfd); - if (sockfdIndex!=(unsigned)-1) - blockingSocketList.RemoveAtIndexFast(sockfdIndex); + std::vector<__TCPSOCKET__>::iterator sockfdIt = + std::find(blockingSocketList.begin(), blockingSocketList.end(), sockfd); + if (sockfdIt!=blockingSocketList.end()) + { + // Swap-remove as before; this order is not published. + *sockfdIt=blockingSocketList.back(); + blockingSocketList.pop_back(); + } blockingSocketListMutex.Unlock(); closesocket__(sockfd); @@ -886,11 +977,15 @@ RAK_THREAD_DECLARATION(MafiaNet::ConnectionAttemptLoop) TCPInterface *tcpInterface = s->tcpInterface; int newRemoteClientIndex=systemAddress.systemIndex; unsigned short socketFamily = s->socketFamily; + // bindAddress has to be copied out too. It used to be read as s->bindAddress + // *after* this delete, on every non-blocking Connect -- the default. + char bindAddress[sizeof(s->bindAddress)]; + memcpy(bindAddress, s->bindAddress, sizeof(bindAddress)); MafiaNet::OP_DELETE(s, _FILE_AND_LINE_); char str1[64]; systemAddress.ToString(false, str1, static_cast(64)); - __TCPSOCKET__ sockfd = tcpInterface->SocketConnect(str1, systemAddress.GetPort(), socketFamily, s->bindAddress); + __TCPSOCKET__ sockfd = tcpInterface->SocketConnect(str1, systemAddress.GetPort(), socketFamily, bindAddress); if (sockfd==0) { tcpInterface->remoteClients[newRemoteClientIndex].isActiveMutex.Lock(); @@ -898,7 +993,7 @@ RAK_THREAD_DECLARATION(MafiaNet::ConnectionAttemptLoop) tcpInterface->remoteClients[newRemoteClientIndex].isActiveMutex.Unlock(); tcpInterface->failedConnectionAttemptMutex.Lock(); - tcpInterface->failedConnectionAttempts.Push(systemAddress, _FILE_AND_LINE_ ); + tcpInterface->failedConnectionAttempts.push_back(systemAddress); tcpInterface->failedConnectionAttemptMutex.Unlock(); return 0; } @@ -910,7 +1005,7 @@ RAK_THREAD_DECLARATION(MafiaNet::ConnectionAttemptLoop) if (tcpInterface->threadRunning.GetValue()>0) { tcpInterface->completedConnectionAttemptMutex.Lock(); - tcpInterface->completedConnectionAttempts.Push(systemAddress, _FILE_AND_LINE_ ); + tcpInterface->completedConnectionAttempts.push_back(systemAddress); tcpInterface->completedConnectionAttemptMutex.Unlock(); } @@ -1050,9 +1145,10 @@ RAK_THREAD_DECLARATION(MafiaNet::UpdateTCPInterfaceLoop) { newSock = accept__(sts->listenSocket, (sockaddr*)&sockAddr, (socklen_t*)&sockAddrSize); - if (newSock != 0) + if (IsUsableSocket(newSock)) { - int newRemoteClientIndex=-1; + int newRemoteClientIndex; + bool slotFound=false; for (newRemoteClientIndex=0; newRemoteClientIndex < sts->remoteClientsLength; newRemoteClientIndex++) { sts->remoteClients[newRemoteClientIndex].isActiveMutex.Lock(); @@ -1085,13 +1181,19 @@ RAK_THREAD_DECLARATION(MafiaNet::UpdateTCPInterfaceLoop) *newConnectionSystemAddress=sts->remoteClients[newRemoteClientIndex].systemAddress; sts->newIncomingConnections.Push(newConnectionSystemAddress); + slotFound=true; break; } sts->remoteClients[newRemoteClientIndex].isActiveMutex.Unlock(); } - if (newRemoteClientIndex==-1) + if (slotFound==false) { - closesocket__(sts->listenSocket); + // The loop above leaves newRemoteClientIndex at + // remoteClientsLength when every slot is taken, never at -1, + // so the old `newRemoteClientIndex==-1` test never fired -- + // and when it was meant to, it closed the *listen* socket + // instead of the connection it could not accommodate. + closesocket__(newSock); } } else diff --git a/Source/src/team_balancer.cpp b/Source/src/team_balancer.cpp index c615292c5..b27dce16a 100644 --- a/Source/src/team_balancer.cpp +++ b/Source/src/team_balancer.cpp @@ -17,6 +17,8 @@ #if _RAKNET_SUPPORT_TeamBalancer==1 #include "mafianet/team_balancer.h" +#include +#include #include "mafianet/bit_stream.h" #include "mafianet/message_identifiers.h" #include "mafianet/peer_interface.h" @@ -24,6 +26,19 @@ using namespace MafiaNet; +namespace +{ + // Replaces DataStructures::List::Replace(input, filler, position): assign at position, growing + // the list to fit and filling any gap with filler. + template + void ReplaceAt(std::vector &v, const T &input, const T &filler, size_t position) + { + if (position >= v.size()) + v.resize(position + 1, filler); + v[position] = input; + } +} + enum TeamBalancerOperations { ID_STATUS_UPDATE_TO_NEW_HOST, @@ -47,9 +62,9 @@ TeamBalancer::~TeamBalancer() } void TeamBalancer::SetTeamSizeLimit(TeamId team, unsigned short limit) { - teamLimits.Replace(limit,0,team,_FILE_AND_LINE_); - if (teamLimits.Size() > teamMemberCounts.Size()) - teamMemberCounts.Replace(0,0,teamLimits.Size()-1,_FILE_AND_LINE_); + ReplaceAt(teamLimits, limit, (unsigned short) 0, (size_t) team); + if (teamLimits.size() > teamMemberCounts.size()) + ReplaceAt(teamMemberCounts, (unsigned short) 0, (unsigned short) 0, teamLimits.size()-1); } void TeamBalancer::SetDefaultAssignmentAlgorithm(DefaultAssigmentAlgorithm daa) { @@ -84,11 +99,11 @@ void TeamBalancer::SetLockTeams(bool lock) { // Process even swaps TeamId i,j; - for (i=0; i < teamMembers.Size(); i++) + for (i=0; i < teamMembers.size(); i++) { if (teamMembers[i].requestedTeam!=UNASSIGNED_TEAM_ID) { - for (j=i+1; j < teamMembers.Size(); j++) + for (j=i+1; j < teamMembers.size(); j++) { if (teamMembers[j].requestedTeam==teamMembers[i].currentTeam && teamMembers[i].requestedTeam==teamMembers[j].currentTeam) @@ -109,7 +124,7 @@ void TeamBalancer::SetLockTeams(bool lock) { // Process requested team changes // Process movement while not full - for (i=0; i < teamMembers.Size(); i++) + for (i=0; i < teamMembers.size(); i++) { TeamId requestedTeam = teamMembers[i].requestedTeam; if (requestedTeam!=UNASSIGNED_TEAM_ID) @@ -127,7 +142,7 @@ void TeamBalancer::SetLockTeams(bool lock) void TeamBalancer::RequestSpecificTeam(NetworkID memberId, TeamId desiredTeam) { bool foundMatch=false; - for (unsigned int i=0; i < myTeamMembers.Size(); i++) + for (unsigned int i=0; i < myTeamMembers.size(); i++) { if (myTeamMembers[i].memberId==memberId) { @@ -149,7 +164,7 @@ void TeamBalancer::RequestSpecificTeam(NetworkID memberId, TeamId desiredTeam) mtm.currentTeam=UNASSIGNED_TEAM_ID; mtm.memberId=memberId; mtm.requestedTeam=desiredTeam; - myTeamMembers.Push(mtm, _FILE_AND_LINE_); + myTeamMembers.push_back(mtm); } // Send desiredTeam to the current host. @@ -163,7 +178,7 @@ void TeamBalancer::RequestSpecificTeam(NetworkID memberId, TeamId desiredTeam) } void TeamBalancer::CancelRequestSpecificTeam(NetworkID memberId) { - for (unsigned int i=0; i < myTeamMembers.Size(); i++) + for (unsigned int i=0; i < myTeamMembers.size(); i++) { if (myTeamMembers[i].memberId==memberId) { @@ -184,7 +199,7 @@ void TeamBalancer::RequestAnyTeam(NetworkID memberId) { bool foundMatch=false; - for (unsigned int i=0; i < myTeamMembers.Size(); i++) + for (unsigned int i=0; i < myTeamMembers.size(); i++) { if (myTeamMembers[i].memberId==memberId) { @@ -203,7 +218,7 @@ void TeamBalancer::RequestAnyTeam(NetworkID memberId) mtm.currentTeam=UNASSIGNED_TEAM_ID; mtm.memberId=memberId; mtm.requestedTeam=UNASSIGNED_TEAM_ID; - myTeamMembers.Push(mtm, _FILE_AND_LINE_); + myTeamMembers.push_back(mtm); } // Else send to the current host that we need a team. @@ -216,7 +231,7 @@ void TeamBalancer::RequestAnyTeam(NetworkID memberId) TeamId TeamBalancer::GetMyTeam(NetworkID memberId) const { // Return team returned by last ID_TEAM_BALANCER_TEAM_ASSIGNED packet - for (unsigned int i=0; i < myTeamMembers.Size(); i++) + for (unsigned int i=0; i < myTeamMembers.size(); i++) { if (myTeamMembers[i].memberId==memberId) { @@ -228,16 +243,18 @@ TeamId TeamBalancer::GetMyTeam(NetworkID memberId) const } void TeamBalancer::DeleteMember(NetworkID memberId) { - for (unsigned int i=0; i < myTeamMembers.Size(); i++) + for (unsigned int i=0; i < myTeamMembers.size(); i++) { if (myTeamMembers[i].memberId==memberId) { - myTeamMembers.RemoveAtIndexFast(i); + // Swap-remove as before; see the declaration for why this is safe here. + myTeamMembers[i] = myTeamMembers.back(); + myTeamMembers.pop_back(); break; } } - for (unsigned int i=0; i < teamMembers.Size(); i++) + for (unsigned int i=0; i < teamMembers.size(); i++) { if (teamMembers[i].memberId==memberId) { @@ -254,14 +271,14 @@ PluginReceiveResult TeamBalancer::OnReceive(Packet *packet) { hostGuid=packet->guid; - if (myTeamMembers.Size()>0) + if (myTeamMembers.size()>0) { BitStream bsOut; bsOut.Write((MessageID)ID_TEAM_BALANCER_INTERNAL); bsOut.Write((MessageID)ID_STATUS_UPDATE_TO_NEW_HOST); - bsOut.WriteCasted(myTeamMembers.Size()); - for (unsigned int i=0; i < myTeamMembers.Size(); i++) + bsOut.WriteCasted(myTeamMembers.size()); + for (unsigned int i=0; i < myTeamMembers.size(); i++) { bsOut.Write(myTeamMembers[i].memberId); bsOut.Write(myTeamMembers[i].currentTeam); @@ -341,7 +358,7 @@ void TeamBalancer::RemoveByGuid(RakNetGUID rakNetGUID) if (WeAreHost()) { unsigned int droppedMemberIndex=0; - while (droppedMemberIndex < teamMembers.Size()) + while (droppedMemberIndex < teamMembers.size()) { if (teamMembers[droppedMemberIndex].memberGuid==rakNetGUID) { @@ -388,13 +405,13 @@ void TeamBalancer::OnStatusUpdateToNewHost(Packet *packet) bsIn.Read(tm.currentTeam); bsIn.Read(tm.requestedTeam); - if (tm.currentTeam!=UNASSIGNED_TEAM_ID && tm.currentTeam>teamLimits.Size()) + if (tm.currentTeam!=UNASSIGNED_TEAM_ID && tm.currentTeam>teamLimits.size()) { RakAssert("Current team out of range in TeamBalancer::OnStatusUpdateToNewHost" && 0); return; } - if (tm.requestedTeam!=UNASSIGNED_TEAM_ID && tm.requestedTeam>teamLimits.Size()) + if (tm.requestedTeam!=UNASSIGNED_TEAM_ID && tm.requestedTeam>teamLimits.size()) { RakAssert("Requested team out of range in TeamBalancer::OnStatusUpdateToNewHost" && 0); return; @@ -420,7 +437,7 @@ void TeamBalancer::OnStatusUpdateToNewHost(Packet *packet) else { // Assign to requested team if possible. Otherwise, assign to a default team - if (TeamWouldBeOverpopulatedOnAddition(tm.requestedTeam, teamMembers.Size())==false) + if (TeamWouldBeOverpopulatedOnAddition(tm.requestedTeam, teamMembers.size())==false) { tm.currentTeam=tm.requestedTeam; } @@ -500,7 +517,7 @@ void TeamBalancer::OnRequestSpecificTeam(Packet *packet) return; } - if (tm.requestedTeam>teamLimits.Size()) + if (tm.requestedTeam>teamLimits.size()) { RakAssert("Requested team out of range in TeamBalancer::OnRequestSpecificTeam" && 0); return; @@ -510,7 +527,7 @@ void TeamBalancer::OnRequestSpecificTeam(Packet *packet) tm.memberGuid=packet->guid; // Assign to requested team if possible. Otherwise, assign to a default team - if (TeamWouldBeOverpopulatedOnAddition(tm.requestedTeam, teamMembers.Size())==false) + if (TeamWouldBeOverpopulatedOnAddition(tm.requestedTeam, teamMembers.size())==false) { tm.currentTeam=tm.requestedTeam; tm.requestedTeam=UNASSIGNED_TEAM_ID; @@ -547,13 +564,13 @@ void TeamBalancer::OnRequestSpecificTeam(Packet *packet) { // If someone wants to join this user's old team, and we want to join their team, they can swap unsigned int swappableMemberIndex; - for (swappableMemberIndex=0; swappableMemberIndex < teamMembers.Size(); swappableMemberIndex++) + for (swappableMemberIndex=0; swappableMemberIndex < teamMembers.size(); swappableMemberIndex++) { if (teamMembers[swappableMemberIndex].currentTeam==tm.requestedTeam && teamMembers[swappableMemberIndex].requestedTeam==oldTeamThisUserWasOn) break; } - if (swappableMemberIndex!=teamMembers.Size()) + if (swappableMemberIndex!=teamMembers.size()) { SwapTeamMembersByRequest(memberIndex,swappableMemberIndex); NotifyTeamAssigment(memberIndex); @@ -569,7 +586,7 @@ void TeamBalancer::OnRequestSpecificTeam(Packet *packet) } unsigned int TeamBalancer::GetMemberIndex(NetworkID memberId, RakNetGUID guid) const { - for (unsigned int i=0; i < teamMembers.Size(); i++) + for (unsigned int i=0; i < teamMembers.size(); i++) { if (teamMembers[i].memberGuid==guid && teamMembers[i].memberId==memberId) return i; @@ -578,7 +595,7 @@ unsigned int TeamBalancer::GetMemberIndex(NetworkID memberId, RakNetGUID guid) c } unsigned int TeamBalancer::AddTeamMember(const TeamMember &tm) { - if (tm.currentTeam>teamLimits.Size()) + if (tm.currentTeam>teamLimits.size()) { RakAssert("TeamBalancer::AddTeamMember team index out of bounds" && 0); return (unsigned int) -1; @@ -586,23 +603,25 @@ unsigned int TeamBalancer::AddTeamMember(const TeamMember &tm) RakAssert(tm.currentTeam!=UNASSIGNED_TEAM_ID); - teamMembers.Push(tm,_FILE_AND_LINE_); - if (teamMemberCounts.Size() overpopulatedTeams; + std::vector overpopulatedTeams; TeamId teamMemberCountsIndex; unsigned int memberIndexToSwitch; - for (teamMemberCountsIndex=0; teamMemberCountsIndex0); + RakAssert(overpopulatedTeams.size()>0); memberIndexToSwitch=GetMemberIndexToSwitchTeams(overpopulatedTeams,teamMemberCountsIndex); RakAssert(memberIndexToSwitch!=(unsigned int)-1); SwitchMemberTeam(memberIndexToSwitch,teamMemberCountsIndex); @@ -633,33 +652,33 @@ void TeamBalancer::EvenTeams(void) } } } -unsigned int TeamBalancer::GetMemberIndexToSwitchTeams(const DataStructures::List &sourceTeamNumbers, TeamId targetTeamNumber) +unsigned int TeamBalancer::GetMemberIndexToSwitchTeams(const std::vector &sourceTeamNumbers, TeamId targetTeamNumber) { - DataStructures::List preferredSwapIndices; - DataStructures::List potentialSwapIndices; + std::vector preferredSwapIndices; + std::vector potentialSwapIndices; unsigned int i,j; - for (j=0; j < sourceTeamNumbers.Size(); j++) + for (j=0; j < sourceTeamNumbers.size(); j++) { RakAssert(sourceTeamNumbers[j]!=targetTeamNumber); - for (i=0; i < teamMembers.Size(); i++) + for (i=0; i < teamMembers.size(); i++) { if (teamMembers[i].currentTeam==sourceTeamNumbers[j]) { if (teamMembers[i].requestedTeam==targetTeamNumber) - preferredSwapIndices.Push(i,_FILE_AND_LINE_); + preferredSwapIndices.push_back(i); else - potentialSwapIndices.Push(i,_FILE_AND_LINE_); + potentialSwapIndices.push_back(i); } } } - if (preferredSwapIndices.Size()>0) + if (preferredSwapIndices.size()>0) { - return preferredSwapIndices[ randomMT() % preferredSwapIndices.Size() ]; + return preferredSwapIndices[ randomMT() % preferredSwapIndices.size() ]; } - else if (potentialSwapIndices.Size()>0) + else if (potentialSwapIndices.size()>0) { - return potentialSwapIndices[ randomMT() % potentialSwapIndices.Size() ]; + return potentialSwapIndices[ randomMT() % potentialSwapIndices.size() ]; } else { @@ -674,19 +693,19 @@ void TeamBalancer::SwitchMemberTeam(unsigned int teamMemberIndex, TeamId destina if (teamMembers[teamMemberIndex].requestedTeam==destinationTeam) teamMembers[teamMemberIndex].requestedTeam=UNASSIGNED_TEAM_ID; } -void TeamBalancer::GetOverpopulatedTeams(DataStructures::List &overpopulatedTeams, int maxTeamSize) +void TeamBalancer::GetOverpopulatedTeams(std::vector &overpopulatedTeams, int maxTeamSize) { - overpopulatedTeams.Clear(true,_FILE_AND_LINE_); - for (TeamId i=0; i < teamMemberCounts.Size(); i++) + overpopulatedTeams.clear(); + for (TeamId i=0; i < teamMemberCounts.size(); i++) { if (teamMemberCounts[i]>=maxTeamSize) - overpopulatedTeams.Push(i,_FILE_AND_LINE_); + overpopulatedTeams.push_back(i); } } void TeamBalancer::NotifyTeamAssigment(unsigned int teamMemberIndex) { - RakAssert(teamMemberIndex < teamMembers.Size()); - if (teamMemberIndex>=teamMembers.Size()) + RakAssert(teamMemberIndex < teamMembers.size()); + if (teamMemberIndex>=teamMembers.size()) return; BitStream bsOut; @@ -713,7 +732,7 @@ PluginReceiveResult TeamBalancer::OnTeamAssigned(Packet *packet) mtm.requestedTeam=UNASSIGNED_TEAM_ID; bool foundMatch=false; - for (unsigned int i=0; i < myTeamMembers.Size(); i++) + for (unsigned int i=0; i < myTeamMembers.size(); i++) { if (myTeamMembers[i].memberId==mtm.memberId) { @@ -778,7 +797,7 @@ bool TeamBalancer::TeamWouldBeOverpopulatedOnAddition(TeamId teamId, unsigned in if (forceTeamsToBeEven) { - int allowedLimit = teamMemberSize/teamLimits.Size() + 1; + int allowedLimit = teamMemberSize/teamLimits.size() + 1; return teamMemberCounts[teamId]>=allowedLimit; } @@ -788,7 +807,7 @@ bool TeamBalancer::TeamWouldBeUnderpopulatedOnLeave(TeamId teamId, unsigned int { if (forceTeamsToBeEven) { - unsigned int minMembersOnASingleTeam = (teamMemberSize-1)/teamLimits.Size(); + unsigned int minMembersOnASingleTeam = (teamMemberSize-1)/teamLimits.size(); return teamMemberCounts[teamId]<=minMembersOnASingleTeam; } return false; @@ -798,7 +817,7 @@ TeamId TeamBalancer::GetSmallestNonFullTeam(void) const TeamId idx; unsigned long smallestTeamCount=MAX_UNSIGNED_LONG; TeamId smallestTeamIndex = UNASSIGNED_TEAM_ID; - for (idx=0; idx < teamMemberCounts.Size(); idx++) + for (idx=0; idx < teamMemberCounts.size(); idx++) { if (teamMemberCounts[idx] membersThatWantToJoinTheTeam; - for (TeamId i=0; i < teamMembers.Size(); i++) + std::vector membersThatWantToJoinTheTeam; + for (TeamId i=0; i < teamMembers.size(); i++) { if (teamMembers[i].requestedTeam==teamId) - membersThatWantToJoinTheTeam.Push(i,_FILE_AND_LINE_); + membersThatWantToJoinTheTeam.push_back(i); } - if (membersThatWantToJoinTheTeam.Size()>0) + if (membersThatWantToJoinTheTeam.size()>0) { TeamId oldTeam; - unsigned int swappedMemberIndex = membersThatWantToJoinTheTeam[ randomMT() % membersThatWantToJoinTheTeam.Size() ]; + unsigned int swappedMemberIndex = membersThatWantToJoinTheTeam[ randomMT() % membersThatWantToJoinTheTeam.size() ]; oldTeam=teamMembers[swappedMemberIndex].currentTeam; SwitchMemberTeam(swappedMemberIndex,teamId); NotifyTeamAssigment(swappedMemberIndex); @@ -882,9 +901,9 @@ void TeamBalancer::NotifyNoTeam(NetworkID memberId, RakNetGUID target) } bool TeamBalancer::TeamsWouldBeEvenOnSwitch(TeamId t1, TeamId t2) { - RakAssert(teamMembers.Size()!=0); - return TeamWouldBeOverpopulatedOnAddition(t1, teamMembers.Size()-1)==false && - TeamWouldBeUnderpopulatedOnLeave(t2, teamMembers.Size()-1)==false; + RakAssert(teamMembers.size()!=0); + return TeamWouldBeOverpopulatedOnAddition(t1, teamMembers.size()-1)==false && + TeamWouldBeUnderpopulatedOnLeave(t2, teamMembers.size()-1)==false; } #endif // _RAKNET_SUPPORT_* diff --git a/Source/src/team_manager.cpp b/Source/src/team_manager.cpp index 6480bf39b..727f72fe2 100644 --- a/Source/src/team_manager.cpp +++ b/Source/src/team_manager.cpp @@ -17,12 +17,36 @@ #if _RAKNET_SUPPORT_TeamManager==1 #include "mafianet/team_manager.h" +#include #include "mafianet/bit_stream.h" #include "mafianet/message_identifiers.h" #include "mafianet/get_time.h" using namespace MafiaNet; +namespace +{ + // Replaces DataStructures::List::GetIndexOf, including its (unsigned)-1 "not found" value. + template + unsigned int IndexOf(const std::vector &v, const T &value) + { + typename std::vector::const_iterator it = std::find(v.begin(), v.end(), value); + if (it == v.end()) + return (unsigned int) -1; + return (unsigned int) (it - v.begin()); + } + + // Fills a still-public DataStructures::List out-parameter from an internal std::vector. + // Stage 3 of #60 changes those signatures and removes these conversions. + template + void CopyToList(const std::vector &from, DataStructures::List &to) + { + to.Clear(true, _FILE_AND_LINE_); + for (size_t i = 0; i < from.size(); ++i) + to.Push(from[i], _FILE_AND_LINE_); + } +} + enum TeamManagerOperations { @@ -126,7 +150,7 @@ bool TM_TeamMember::RequestTeam(TeamSelection teamSelection) // - remote Host executes JoinNoTeam() and broadcasts event. This may cause may cause rebalance if team balancing is on. // - - JoinNoTeam(): Remove from all current and requested teams. Set no-team category. - if (teams.Size()==0 && noTeamSubcategory==teamSelection.teamParameter.noTeamSubcategory) + if (teams.size()==0 && noTeamSubcategory==teamSelection.teamParameter.noTeamSubcategory) { // No change return false; @@ -246,7 +270,7 @@ bool TM_TeamMember::RequestTeamSwitch(TM_Team *teamToJoin, TM_Team *teamToLeave) TeamSelection TM_TeamMember::GetRequestedTeam(void) const { - if (teamsRequested.Size()>0) + if (teamsRequested.size()>0) return TeamSelection::SpecificTeam(teamsRequested[0].requested); else if (joinTeamType==JOIN_NO_TEAM) return TeamSelection::NoTeam(noTeamSubcategory); @@ -259,7 +283,7 @@ TeamSelection TM_TeamMember::GetRequestedTeam(void) const void TM_TeamMember::GetRequestedSpecificTeams(DataStructures::List &requestedTeams) const { requestedTeams.Clear(true, _FILE_AND_LINE_); - for (unsigned int i=0; i < teamsRequested.Size(); i++) + for (unsigned int i=0; i < teamsRequested.size(); i++) requestedTeams.Push(teamsRequested[i].requested, _FILE_AND_LINE_); } @@ -278,7 +302,7 @@ bool TM_TeamMember::HasRequestedTeam(TM_Team *team) const unsigned int TM_TeamMember::GetRequestedTeamIndex(TM_Team *team) const { unsigned int i; - for (i=0; i < teamsRequested.Size(); i++) + for (i=0; i < teamsRequested.size(); i++) { if (teamsRequested[i].requested==team) return i; @@ -290,7 +314,7 @@ unsigned int TM_TeamMember::GetRequestedTeamIndex(TM_Team *team) const unsigned int TM_TeamMember::GetRequestedTeamCount(void) const { - return teamsRequested.Size(); + return teamsRequested.size(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -329,7 +353,7 @@ bool TM_TeamMember::LeaveTeam(TM_Team* team, NoTeamId _noTeamSubcategory) return false; RemoveFromSpecificTeamInternal(team); - if (teams.Size()==0) + if (teams.size()==0) { noTeamSubcategory=_noTeamSubcategory; joinTeamType=JOIN_NO_TEAM; @@ -382,7 +406,7 @@ bool TM_TeamMember::LeaveAllTeams(NoTeamId inNoTeamSubcategory) TM_Team* TM_TeamMember::GetCurrentTeam(void) const { - if (teams.Size()>0) + if (teams.size()>0) return teams[0]; return 0; } @@ -391,7 +415,7 @@ TM_Team* TM_TeamMember::GetCurrentTeam(void) const unsigned int TM_TeamMember::GetCurrentTeamCount(void) const { - return teams.Size(); + return teams.size(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -405,14 +429,14 @@ TM_Team* TM_TeamMember::GetCurrentTeamByIndex(unsigned int index) void TM_TeamMember::GetCurrentTeams(DataStructures::List &_teams) const { - _teams=teams; + CopyToList(teams, _teams); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- void TM_TeamMember::GetLastTeams(DataStructures::List &_teams) const { - _teams=lastTeams; + CopyToList(lastTeams, _teams); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -420,7 +444,7 @@ void TM_TeamMember::GetLastTeams(DataStructures::List &_teams) const bool TM_TeamMember::IsOnTeam(TM_Team *team) const { unsigned int i; - for (i=0; i < teams.Size(); i++) + for (i=0; i < teams.size(); i++) { if (teams[i]==team) return true; @@ -449,8 +473,8 @@ void TM_TeamMember::SerializeConstruction(BitStream *constructionBitstream) // Write requested teams constructionBitstream->Write(world->GetWorldId()); constructionBitstream->Write(networkId); - constructionBitstream->WriteCasted(teamsRequested.Size()); - for (unsigned int i=0; i < teamsRequested.Size(); i++) + constructionBitstream->WriteCasted(teamsRequested.size()); + for (unsigned int i=0; i < teamsRequested.size(); i++) { constructionBitstream->Write(teamsRequested[i].isTeamSwitch); if (teamsRequested[i].teamToLeave) @@ -526,7 +550,7 @@ bool TM_TeamMember::DeserializeConstruction(TeamManager *teamManager, BitStream (hasTeamRequested==false || (hasTeamRequested==true && rt.requested!=0)) ) { - teamsRequested.Push(rt, _FILE_AND_LINE_); + teamsRequested.push_back(rt); } } @@ -575,12 +599,12 @@ unsigned long TM_TeamMember::ToUint32( const NetworkID &g ) void TM_TeamMember::UpdateListsToNoTeam(NoTeamId nti) { - teamsRequested.Clear(true, _FILE_AND_LINE_ ); - for (unsigned int i=0; i < teams.Size(); i++) + teamsRequested.clear(); + for (unsigned int i=0; i < teams.size(); i++) { teams[i]->RemoveFromTeamMemberList(this); } - teams.Clear(true, _FILE_AND_LINE_ ); + teams.clear(); noTeamSubcategory=nti; joinTeamType=JOIN_NO_TEAM; } @@ -590,11 +614,11 @@ void TM_TeamMember::UpdateListsToNoTeam(NoTeamId nti) bool TM_TeamMember::JoinAnyTeamCheck(void) const { // - - If already on a team, return false - if (teams.Size() > 0) + if (teams.size() > 0) return false; // - - If any team is already in requested teams, return false. - if (teamsRequested.Size()==0 && joinTeamType==JOIN_ANY_AVAILABLE_TEAM) + if (teamsRequested.size()==0 && joinTeamType==JOIN_ANY_AVAILABLE_TEAM) return false; return true; @@ -612,7 +636,7 @@ bool TM_TeamMember::JoinSpecificTeamCheck(TM_Team *specificTeamToJoin, bool igno return true; unsigned int i; - for (i=0; i < teamsRequested.Size(); i++) + for (i=0; i < teamsRequested.size(); i++) { if (teamsRequested[i].requested==specificTeamToJoin) { @@ -648,7 +672,7 @@ bool TM_TeamMember::SwitchSpecificTeamCheck(TM_Team *teamToJoin, TM_Team *teamTo return true; unsigned int i; - for (i=0; i < teamsRequested.Size(); i++) + for (i=0; i < teamsRequested.size(); i++) { if (teamsRequested[i].requested==teamToJoin) { @@ -680,7 +704,7 @@ bool TM_TeamMember::LeaveTeamCheck(TM_Team *team) const void TM_TeamMember::UpdateTeamsRequestedToAny(void) { - teamsRequested.Clear(true, _FILE_AND_LINE_); + teamsRequested.clear(); joinTeamType=JOIN_ANY_AVAILABLE_TEAM; whenJoinAnyRequested= MafiaNet::GetTime(); joinAnyRequestIndex=world->teamRequestIndex++; // In case whenRequested is the same between two teams when sorting team requests @@ -690,7 +714,7 @@ void TM_TeamMember::UpdateTeamsRequestedToAny(void) void TM_TeamMember::UpdateTeamsRequestedToNone(void) { - teamsRequested.Clear(true, _FILE_AND_LINE_); + teamsRequested.clear(); joinTeamType=JOIN_NO_TEAM; } @@ -706,7 +730,7 @@ void TM_TeamMember::AddToRequestedTeams(TM_Team *teamToJoin) rt.teamToLeave=0; rt.whenRequested= MafiaNet::GetTime(); rt.requestIndex=world->teamRequestIndex++; // In case whenRequested is the same between two teams when sorting team requests - teamsRequested.Push(rt, _FILE_AND_LINE_ ); + teamsRequested.push_back(rt); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -721,7 +745,7 @@ void TM_TeamMember::AddToRequestedTeams(TM_Team *teamToJoin, TM_Team *teamToLeav rt.teamToLeave=teamToLeave; rt.whenRequested= MafiaNet::GetTime(); rt.requestIndex=world->teamRequestIndex++; // In case whenRequested is the same between two teams when sorting team requests - teamsRequested.Push(rt, _FILE_AND_LINE_ ); + teamsRequested.push_back(rt); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -730,19 +754,19 @@ bool TM_TeamMember::RemoveFromRequestedTeams(TM_Team *team) { if (team==0) { - teamsRequested.Clear(true, _FILE_AND_LINE_); + teamsRequested.clear(); joinTeamType=JOIN_NO_TEAM; return true; } else { unsigned int i; - for (i=0; i < teamsRequested.Size(); i++) + for (i=0; i < teamsRequested.size(); i++) { if (teamsRequested[i].requested==team) { - teamsRequested.RemoveAtIndex(i); - if (teamsRequested.Size()==0) + teamsRequested.erase(teamsRequested.begin() + i); + if (teamsRequested.size()==0) { joinTeamType=JOIN_NO_TEAM; } @@ -757,8 +781,8 @@ bool TM_TeamMember::RemoveFromRequestedTeams(TM_Team *team) void TM_TeamMember::AddToTeamList(TM_Team *team) { - team->teamMembers.Push(this, _FILE_AND_LINE_ ); - teams.Push(team, _FILE_AND_LINE_ ); + team->teamMembers.push_back(this); + teams.push_back(team); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -766,19 +790,19 @@ void TM_TeamMember::AddToTeamList(TM_Team *team) void TM_TeamMember::RemoveFromSpecificTeamInternal(TM_Team *team) { unsigned int i,j; - for (i=0; i < teams.Size(); i++) + for (i=0; i < teams.size(); i++) { if (teams[i]==team) { - for (j=0; j < team->teamMembers.Size(); j++) + for (j=0; j < team->teamMembers.size(); j++) { if (team->teamMembers[j]==this) { - team->teamMembers.RemoveAtIndex(j); + team->teamMembers.erase(team->teamMembers.begin() + j); break; } } - teams.RemoveAtIndex(i); + teams.erase(teams.begin() + i); break; } } @@ -790,20 +814,20 @@ void TM_TeamMember::RemoveFromAllTeamsInternal(void) { TM_Team *team; unsigned int i,j; - for (i=0; i < teams.Size(); i++) + for (i=0; i < teams.size(); i++) { team = teams[i]; - for (j=0; j < team->teamMembers.Size(); j++) + for (j=0; j < team->teamMembers.size(); j++) { if (team->teamMembers[j]==this) { - team->teamMembers.RemoveAtIndex(j); + team->teamMembers.erase(team->teamMembers.begin() + j); break; } } } - teams.Clear(true, _FILE_AND_LINE_); + teams.clear(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -934,14 +958,14 @@ bool TM_Team::GetBalancingApplies(void) const void TM_Team::GetTeamMembers(DataStructures::List &_teamMembers) const { - _teamMembers=teamMembers; + CopyToList(teamMembers, _teamMembers); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- unsigned int TM_Team::GetTeamMembersCount(void) const { - return teamMembers.Size(); + return teamMembers.size(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1032,9 +1056,9 @@ void TM_Team::SetOwner(void *o) void TM_Team::RemoveFromTeamMemberList(TM_TeamMember *teamMember) { - unsigned int index = teamMembers.GetIndexOf(teamMember); + unsigned int index = IndexOf(teamMembers, teamMember); RakAssert(index != (unsigned int) -1); - teamMembers.RemoveAtIndex(index); + teamMembers.erase(teamMembers.begin() + index); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1042,7 +1066,7 @@ void TM_Team::RemoveFromTeamMemberList(TM_TeamMember *teamMember) unsigned int TM_Team::GetMemberWithRequestedSingleTeamSwitch(TM_Team *team) { unsigned int i; - for (i=0; i < teamMembers.Size(); i++) + for (i=0; i < teamMembers.size(); i++) { if (teamMembers[i]->GetCurrentTeamCount()==1) { @@ -1093,7 +1117,7 @@ TeamManager *TM_World::GetTeamManager(void) const void TM_World::AddParticipant(RakNetGUID rakNetGUID) { - participants.Push(rakNetGUID, _FILE_AND_LINE_ ); + participants.push_back(rakNetGUID); // Send to remote system status of balanceTeamsIsActive @@ -1114,9 +1138,9 @@ void TM_World::AddParticipant(RakNetGUID rakNetGUID) void TM_World::RemoveParticipant(RakNetGUID rakNetGUID) { unsigned int i; - i = participants.GetIndexOf(rakNetGUID); + i = IndexOf(participants, rakNetGUID); if (i!=(unsigned int)-1) - participants.RemoveAtIndex(i); + participants.erase(participants.begin() + i); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1130,7 +1154,7 @@ void TM_World::SetAutoManageConnections(bool autoAdd) void TM_World::GetParticipantList(DataStructures::List &participantList) { - participantList = participants; + CopyToList(participants, participantList); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1138,7 +1162,7 @@ void TM_World::GetParticipantList(DataStructures::List &participantL void TM_World::ReferenceTeam(TM_Team *team, NetworkID networkId, bool applyBalancing) { unsigned int i; - for (i=0; i < teams.Size(); i++) + for (i=0; i < teams.size(); i++) { if (teams[i]==team) return; @@ -1149,9 +1173,9 @@ void TM_World::ReferenceTeam(TM_Team *team, NetworkID networkId, bool applyBalan team->world=this; // Add this team to the list of teams - teams.Push(team, _FILE_AND_LINE_); + teams.push_back(team); - teamsHash.Push(networkId,team,_FILE_AND_LINE_); + teamsHash.insert(std::make_pair(networkId, team)); // If autobalancing is on, and the team lock state supports it, then call EnforceTeamBalancing() if (applyBalancing && balanceTeamsIsActive) @@ -1165,18 +1189,18 @@ void TM_World::ReferenceTeam(TM_Team *team, NetworkID networkId, bool applyBalan void TM_World::DereferenceTeam(TM_Team *team, NoTeamId noTeamSubcategory) { unsigned int i; - for (i=0; i < teams.Size(); i++) + for (i=0; i < teams.size(); i++) { if (teams[i]==team) { TM_Team *curTeam = teams[i]; - while (curTeam->teamMembers.Size()) + while (curTeam->teamMembers.size()) { - curTeam->teamMembers[curTeam->teamMembers.Size()-1]->LeaveTeam(curTeam, noTeamSubcategory); + curTeam->teamMembers[curTeam->teamMembers.size()-1]->LeaveTeam(curTeam, noTeamSubcategory); } - teams.RemoveAtIndex(i); + teams.erase(teams.begin() + i); - teamsHash.Remove(curTeam->GetNetworkID(),_FILE_AND_LINE_); + teamsHash.erase(curTeam->GetNetworkID()); break; } @@ -1187,7 +1211,7 @@ void TM_World::DereferenceTeam(TM_Team *team, NoTeamId noTeamSubcategory) unsigned int TM_World::GetTeamCount(void) const { - return teams.Size(); + return teams.size(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1201,10 +1225,10 @@ TM_Team *TM_World::GetTeamByIndex(unsigned int index) const TM_Team *TM_World::GetTeamByNetworkID(NetworkID teamId) { - DataStructures::HashIndex hi = teamsHash.GetIndexOf(teamId); - if (hi.IsInvalid()) + std::unordered_map::const_iterator it = teamsHash.find(teamId); + if (it == teamsHash.end()) return 0; - return teamsHash.ItemAtIndex(hi); + return it->second; } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1212,7 +1236,7 @@ TM_Team *TM_World::GetTeamByNetworkID(NetworkID teamId) unsigned int TM_World::GetTeamIndex(const TM_Team *team) const { unsigned int i; - for (i=0; i < teams.Size(); i++) + for (i=0; i < teams.size(); i++) { if (teams[i]==team) return i; @@ -1225,7 +1249,7 @@ unsigned int TM_World::GetTeamIndex(const TM_Team *team) const void TM_World::ReferenceTeamMember(TM_TeamMember *teamMember, NetworkID networkId) { unsigned int i; - for (i=0; i < teamMembers.Size(); i++) + for (i=0; i < teamMembers.size(); i++) { if (teamMembers[i]==teamMember) return; @@ -1234,9 +1258,9 @@ void TM_World::ReferenceTeamMember(TM_TeamMember *teamMember, NetworkID networkI teamMember->world=this; teamMember->networkId=networkId; - teamMembers.Push(teamMember, _FILE_AND_LINE_); + teamMembers.push_back(teamMember); - teamMembersHash.Push(networkId,teamMember,_FILE_AND_LINE_); + teamMembersHash.insert(std::make_pair(networkId, teamMember)); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1244,13 +1268,13 @@ void TM_World::ReferenceTeamMember(TM_TeamMember *teamMember, NetworkID networkI void TM_World::DereferenceTeamMember(TM_TeamMember *teamMember) { unsigned int i; - for (i=0; i < teamMembers.Size(); i++) + for (i=0; i < teamMembers.size(); i++) { if (teamMembers[i]==teamMember) { teamMembers[i]->UpdateListsToNoTeam(0); - teamMembersHash.Remove(teamMembers[i]->GetNetworkID(),_FILE_AND_LINE_); - teamMembers.RemoveAtIndex(i); + teamMembersHash.erase(teamMembers[i]->GetNetworkID()); + teamMembers.erase(teamMembers.begin() + i); break; } } @@ -1260,7 +1284,7 @@ void TM_World::DereferenceTeamMember(TM_TeamMember *teamMember) unsigned int TM_World::GetTeamMemberCount(void) const { - return teamMembers.Size(); + return teamMembers.size(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1281,10 +1305,10 @@ NetworkID TM_World::GetTeamMemberIDByIndex(unsigned int index) const TM_TeamMember *TM_World::GetTeamMemberByNetworkID(NetworkID teamMemberId) { - DataStructures::HashIndex hi = teamMembersHash.GetIndexOf(teamMemberId); - if (hi.IsInvalid()) + std::unordered_map::const_iterator it = teamMembersHash.find(teamMemberId); + if (it == teamMembersHash.end()) return 0; - return teamMembersHash.ItemAtIndex(hi); + return it->second; } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1292,7 +1316,7 @@ TM_TeamMember *TM_World::GetTeamMemberByNetworkID(NetworkID teamMemberId) unsigned int TM_World::GetTeamMemberIndex(const TM_TeamMember *teamMember) const { unsigned int i; - for (i=0; i < teamMembers.Size(); i++) + for (i=0; i < teamMembers.size(); i++) { if (teamMembers[i]==teamMember) return i; @@ -1361,17 +1385,17 @@ WorldId TM_World::GetWorldId(void) const void TM_World::Clear(void) { - for (unsigned int i=0; i < teams.Size(); i++) + for (unsigned int i=0; i < teams.size(); i++) { teams[i]->world=0; } - for (unsigned int i=0; i < teamMembers.Size(); i++) + for (unsigned int i=0; i < teamMembers.size(); i++) { teamMembers[i]->world=0; } - participants.Clear(true, _FILE_AND_LINE_); - teams.Clear(true, _FILE_AND_LINE_); - teamMembers.Clear(true, _FILE_AND_LINE_); + participants.clear(); + teams.clear(); + teamMembers.clear(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1424,13 +1448,13 @@ void TM_World::KickExcessMembers(NoTeamId noTeamId) TM_Team *team, *teamToJoin; unsigned int i, teamIndex; - for (i=0; i < teams.Size(); i++) + for (i=0; i < teams.size(); i++) { team = teams[i]; while (team->GetMemberLimitSetting() < team->GetTeamMembersCount() || (balancedTeamLimit < team->GetTeamMembersCount() && team->GetBalancingApplies()) ) { - TM_TeamMember *teamMember = team->teamMembers[team->teamMembers.Size()-1]; + TM_TeamMember *teamMember = team->teamMembers[team->teamMembers.size()-1]; teamIndex = GetAvailableTeamIndexWithFewestMembers(balancedTeamLimit, ALLOW_JOIN_REBALANCING); if (teamIndex == (unsigned int)-1) @@ -1479,14 +1503,14 @@ void TM_World::FillRequestedSlots(void) unsigned int teamIndex, indexIntoTeamsRequested = (unsigned int)-1; TM_Team *team; TM_TeamMember *teamMember; - DataStructures::OrderedList joinRequests; + std::vector joinRequests; GetSortedJoinRequests(joinRequests); unsigned int joinRequestIndex; - for (joinRequestIndex=0; joinRequestIndex < joinRequests.Size(); joinRequestIndex++) + for (joinRequestIndex=0; joinRequestIndex < joinRequests.size(); joinRequestIndex++) { teamMember = teamMembers[joinRequests[joinRequestIndex].teamMemberIndex]; - if (teamMember->teamsRequested.Size()==0) + if (teamMember->teamsRequested.size()==0) { if (teamMember->joinTeamType==JOIN_ANY_AVAILABLE_TEAM) teamIndex = GetAvailableTeamIndexWithFewestMembers(balancedTeamLimit, ALLOW_JOIN_ANY_AVAILABLE_TEAM); @@ -1502,7 +1526,7 @@ void TM_World::FillRequestedSlots(void) team->GetTeamMembersCount() < team->GetMemberLimitSetting() && (ALLOW_JOIN_SPECIFIC_TEAM & team->GetJoinPermissions())!=0) { - teamIndex=teams.GetIndexOf(team); + teamIndex=IndexOf(teams, team); } else { @@ -1514,7 +1538,7 @@ void TM_World::FillRequestedSlots(void) { team = teams[teamIndex]; - if (teamMember->teamsRequested.Size()==0) + if (teamMember->teamsRequested.size()==0) { if (teamMember->joinTeamType==JOIN_ANY_AVAILABLE_TEAM) { @@ -1536,7 +1560,7 @@ void TM_World::FillRequestedSlots(void) else { // Switch or join specific - DataStructures::List teamsWeAreLeaving; + std::vector teamsWeAreLeaving; bool isSwitch = teamMember->teamsRequested[indexIntoTeamsRequested].isTeamSwitch; TM_Team *teamToLeave; if (isSwitch) @@ -1546,7 +1570,7 @@ void TM_World::FillRequestedSlots(void) { if (teamMember->IsOnTeam(teamToLeave)) { - teamsWeAreLeaving.Push(teamToLeave, _FILE_AND_LINE_); + teamsWeAreLeaving.push_back(teamToLeave); } else { @@ -1596,7 +1620,7 @@ unsigned int TM_World::GetAvailableTeamIndexWithFewestMembers(TeamMemberLimit se unsigned int lowestTeamMembers = (unsigned int) -1; unsigned int lowestIndex = (unsigned int) -1; - for (teamIndex=0; teamIndex < teams.Size(); teamIndex++) + for (teamIndex=0; teamIndex < teams.size(); teamIndex++) { if (teams[teamIndex]->GetTeamMembersCount() < secondaryLimit && teams[teamIndex]->GetTeamMembersCount() < teams[teamIndex]->GetMemberLimitSetting() && @@ -1613,14 +1637,14 @@ unsigned int TM_World::GetAvailableTeamIndexWithFewestMembers(TeamMemberLimit se // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -void TM_World::GetSortedJoinRequests(DataStructures::OrderedList &joinRequests) +void TM_World::GetSortedJoinRequests(std::vector &joinRequests) { unsigned int i; - for (i=0; i < teamMembers.Size(); i++) + for (i=0; i < teamMembers.size(); i++) { TM_TeamMember *teamMember = teamMembers[i]; - if (teamMember->teamsRequested.Size()==0) + if (teamMember->teamsRequested.size()==0) { if (teamMember->joinTeamType==JOIN_ANY_AVAILABLE_TEAM) { @@ -1628,30 +1652,37 @@ void TM_World::GetSortedJoinRequests(DataStructures::OrderedListwhenJoinAnyRequested; jrh.teamMemberIndex=i; jrh.requestIndex=teamMember->joinAnyRequestIndex; - joinRequests.Insert(jrh, jrh, true, _FILE_AND_LINE_); + joinRequests.push_back(jrh); } } else { unsigned int j; - for (j=0; j < teamMember->teamsRequested.Size(); j++) + for (j=0; j < teamMember->teamsRequested.size(); j++) { TM_World::JoinRequestHelper jrh; jrh.whenRequestMade=teamMember->teamsRequested[j].whenRequested; jrh.teamMemberIndex=i; jrh.indexIntoTeamsRequested=j; jrh.requestIndex=teamMember->teamsRequested[j].requestIndex; - joinRequests.Insert(jrh, jrh, true, _FILE_AND_LINE_); + joinRequests.push_back(jrh); } } } + + // Same ordering the OrderedList maintained on insert. + std::sort(joinRequests.begin(), joinRequests.end(), + [](const JoinRequestHelper &a, const JoinRequestHelper &b) + { + return JoinRequestHelperComp(a, b) < 0; + }); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- void TM_World::BroadcastToParticipants(MafiaNet::BitStream *bsOut, RakNetGUID exclusionGuid) { - for (unsigned int i=0; i < participants.Size(); i++) + for (unsigned int i=0; i < participants.size(); i++) { if (participants[i]==exclusionGuid) continue; @@ -1663,7 +1694,7 @@ void TM_World::BroadcastToParticipants(MafiaNet::BitStream *bsOut, RakNetGUID ex void TM_World::BroadcastToParticipants(unsigned char *data, const int length, RakNetGUID exclusionGuid) { - for (unsigned int i=0; i < participants.Size(); i++) + for (unsigned int i=0; i < participants.size(); i++) { if (participants[i]==exclusionGuid) continue; @@ -1681,7 +1712,7 @@ TM_Team* TM_World::JoinAnyTeam(TM_TeamMember *teamMember, int *resultCode) if (idx == (unsigned int ) -1) { // If any team is joinable but full, return full. Otherwise return locked - for (idx=0; idx < teams.Size(); idx++) + for (idx=0; idx < teams.size(); idx++) { if ((teams[idx]->GetTeamMembersCount() >= balancedLimit || teams[idx]->GetTeamMembersCount() >= teams[idx]->GetMemberLimitSetting()) && @@ -1712,11 +1743,11 @@ TM_Team* TM_World::JoinAnyTeam(TM_TeamMember *teamMember, int *resultCode) // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -int TM_World::JoinSpecificTeam(TM_TeamMember *teamMember, TM_Team *team, bool isTeamSwitch, TM_Team *teamToLeave, DataStructures::List &teamsWeAreLeaving) +int TM_World::JoinSpecificTeam(TM_TeamMember *teamMember, TM_Team *team, bool isTeamSwitch, TM_Team *teamToLeave, std::vector &teamsWeAreLeaving) { if (team->GetJoinPermissions() & ALLOW_JOIN_SPECIFIC_TEAM) { - if (balanceTeamsIsActive==false || teamsWeAreLeaving.Size()==0) + if (balanceTeamsIsActive==false || teamsWeAreLeaving.size()==0) { if (team->GetMemberLimit() > team->GetTeamMembersCount()) { @@ -1737,7 +1768,7 @@ int TM_World::JoinSpecificTeam(TM_TeamMember *teamMember, TM_Team *team, bool is // Do limited team swap // We must be on one team, target must be on one team, and we want to exchange teams - if (teamsWeAreLeaving.Size()==1) + if (teamsWeAreLeaving.size()==1) { unsigned int j = team->GetMemberWithRequestedSingleTeamSwitch(teamsWeAreLeaving[0]); if (j!=(unsigned int)-1) @@ -1774,7 +1805,7 @@ int TM_World::JoinSpecificTeam(TM_TeamMember *teamMember, TM_Team *team, bool is TeamMemberLimit TM_World::GetBalancedTeamLimit(void) const { - if (teams.Size()==0) + if (teams.size()==0) return 0; if (balanceTeamsIsActive==false) @@ -1783,23 +1814,25 @@ TeamMemberLimit TM_World::GetBalancedTeamLimit(void) const unsigned int i; bool additionalTeamsExcluded; TeamMemberLimit balancedLimit; - unsigned int teamsCount=teams.Size(); - unsigned int membersCount=teamMembers.Size(); - DataStructures::List consideredTeams = teams; + unsigned int teamsCount=teams.size(); + unsigned int membersCount=teamMembers.size(); + std::vector consideredTeams = teams; do { additionalTeamsExcluded=false; balancedLimit = (TeamMemberLimit) ((membersCount+(teamsCount-1))/(teamsCount)); i=0; - while (i < consideredTeams.Size()) + while (i < consideredTeams.size()) { if (consideredTeams[i]->GetMemberLimitSetting() < balancedLimit) { additionalTeamsExcluded=true; membersCount-=consideredTeams[i]->GetMemberLimitSetting(); teamsCount--; - consideredTeams.RemoveAtIndexFast(i); + // Swap-remove as before: this is a local working copy and the loop re-tests index i. + consideredTeams[i] = consideredTeams.back(); + consideredTeams.pop_back(); } else { @@ -1843,7 +1876,7 @@ TM_World* TeamManager::AddWorld(WorldId worldId) newWorld->teamManager=this; newWorld->hostGuid=GetMyGUIDUnified(); worldsArray[worldId]=newWorld; - worldsList.Push(newWorld,_FILE_AND_LINE_); + worldsList.push_back(newWorld); return newWorld; } @@ -1852,12 +1885,15 @@ TM_World* TeamManager::AddWorld(WorldId worldId) void TeamManager::RemoveWorld(WorldId worldId) { RakAssert(worldsArray[worldId]!=0 && "World not in use"); - for (unsigned int i=0; i < worldsList.Size(); i++) + for (unsigned int i=0; i < worldsList.size(); i++) { if (worldsList[i]==worldsArray[worldId]) { MafiaNet::OP_DELETE(worldsList[i],_FILE_AND_LINE_); - worldsList.RemoveAtIndexFast(i); + // Swap-remove, not erase: GetWorldAtIndex exposes this index and RemoveWorld has + // always swapped the last world in, unlike the team lists which shift down. + worldsList[i] = worldsList.back(); + worldsList.pop_back(); break; } } @@ -1868,7 +1904,7 @@ void TeamManager::RemoveWorld(WorldId worldId) unsigned int TeamManager::GetWorldCount(void) const { - return worldsList.Size(); + return worldsList.size(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1891,7 +1927,7 @@ void TeamManager::SetAutoManageConnections(bool autoAdd) { autoAddParticipants=autoAdd; - for (unsigned int i=0; i < worldsList.Size(); i++) + for (unsigned int i=0; i < worldsList.size(); i++) { worldsList[i]->SetAutoManageConnections(autoAdd); } @@ -1990,8 +2026,8 @@ void TeamManager::EncodeTeamAssigned(MafiaNet::BitStream *bitStream, TM_TeamMemb { bitStream->Write(teamMember->world->GetWorldId()); bitStream->Write(teamMember->GetNetworkID()); - bitStream->WriteCasted(teamMember->teams.Size()); - for (unsigned int i=0; i < teamMember->teams.Size(); i++) + bitStream->WriteCasted(teamMember->teams.size()); + for (unsigned int i=0; i < teamMember->teams.size(); i++) { bitStream->Write(teamMember->teams[i]->GetNetworkID()); } @@ -2007,18 +2043,18 @@ void TeamManager::ProcessTeamAssigned(MafiaNet::BitStream *bsIn) TM_TeamMember *teamMember; NoTeamId noTeamId; JoinTeamType joinTeamType; - DataStructures::List newTeam; - DataStructures::List teamsLeft; - DataStructures::List teamsJoined; + std::vector newTeam; + std::vector teamsLeft; + std::vector teamsJoined; DecodeTeamAssigned(bsIn, &world, &teamMember, noTeamId, joinTeamType, newTeam, teamsLeft, teamsJoined); if (teamMember) { teamMember->StoreLastTeams(); - for (unsigned int i=0; i < teamsLeft.Size(); i++) + for (unsigned int i=0; i < teamsLeft.size(); i++) { teamMember->RemoveFromSpecificTeamInternal(teamsLeft[i]); } - for (unsigned int i=0; i < teamsJoined.Size(); i++) + for (unsigned int i=0; i < teamsJoined.size(); i++) { if (teamMember->IsOnTeam(teamsJoined[i])==false) { @@ -2091,13 +2127,13 @@ void TeamManager::DecodeTeamCancelled(Packet *packet, TM_World **world, TM_TeamM // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- void TeamManager::DecodeTeamAssigned(BitStream *bsIn, TM_World **world, TM_TeamMember **teamMember, NoTeamId &noTeamId, - JoinTeamType &joinTeamType, DataStructures::List &newTeam, - DataStructures::List &teamsLeft, DataStructures::List &teamsJoined + JoinTeamType &joinTeamType, std::vector &newTeam, + std::vector &teamsLeft, std::vector &teamsJoined ) { - newTeam.Clear(true, _FILE_AND_LINE_); - teamsLeft.Clear(true, _FILE_AND_LINE_); - teamsJoined.Clear(true, _FILE_AND_LINE_); + newTeam.clear(); + teamsLeft.clear(); + teamsJoined.clear(); WorldId worldId; NetworkID teamMemberId; @@ -2118,25 +2154,25 @@ void TeamManager::DecodeTeamAssigned(BitStream *bsIn, TM_World **world, TM_TeamM TM_Team * team = (*world)->GetTeamByNetworkID(teamId); RakAssert(team); if (team) - newTeam.Push(team, _FILE_AND_LINE_); + newTeam.push_back(team); // else probably didn't reference team first } if (*teamMember) { - for (unsigned int i=0; i < (*teamMember)->teams.Size(); i++) + for (unsigned int i=0; i < (*teamMember)->teams.size(); i++) { TM_Team *team = (*teamMember)->teams[i]; - if (newTeam.GetIndexOf(team)==(unsigned int)-1) - teamsLeft.Push(team, _FILE_AND_LINE_); + if (IndexOf(newTeam, team)==(unsigned int)-1) + teamsLeft.push_back(team); } } - for (unsigned int i=0; i < newTeam.Size(); i++) + for (unsigned int i=0; i < newTeam.size(); i++) { TM_Team *team = newTeam[i]; - if ((*teamMember)->teams.GetIndexOf(team)==(unsigned int)-1) - teamsJoined.Push(team, _FILE_AND_LINE_); + if (IndexOf((*teamMember)->teams, team)==(unsigned int)-1) + teamsJoined.push_back(team); } bsIn->Read(noTeamId); @@ -2152,13 +2188,13 @@ void TeamManager::DecodeTeamAssigned(BitStream *bsIn, TM_World **world, TM_TeamM void TeamManager::Clear(void) { - for (unsigned int i=0; i < worldsList.Size(); i++) + for (unsigned int i=0; i < worldsList.size(); i++) { worldsArray[worldsList[i]->worldId]=0; worldsList[i]->Clear(); MafiaNet::OP_DELETE(worldsList[i], _FILE_AND_LINE_); } - worldsList.Clear(false, _FILE_AND_LINE_); + worldsList.clear(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2176,7 +2212,7 @@ PluginReceiveResult TeamManager::OnReceive(Packet *packet) case ID_FCM2_NEW_HOST: { unsigned int i; - for (i=0; i < worldsList.Size(); i++) + for (i=0; i < worldsList.size(); i++) worldsList[i]->SetHost(packet->guid); } break; @@ -2265,7 +2301,7 @@ PluginReceiveResult TeamManager::OnReceive(Packet *packet) void TeamManager::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) { - for (unsigned int i=0; i < worldsList.Size(); i++) + for (unsigned int i=0; i < worldsList.size(); i++) { worldsList[i]->OnClosedConnection(systemAddress, rakNetGUID, lostConnectionReason); } @@ -2275,7 +2311,7 @@ void TeamManager::OnClosedConnection(const SystemAddress &systemAddress, RakNetG void TeamManager::OnNewConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, bool isIncoming) { - for (unsigned int i=0; i < worldsList.Size(); i++) + for (unsigned int i=0; i < worldsList.size(); i++) { worldsList[i]->OnNewConnection(systemAddress, rakNetGUID, isIncoming); } @@ -2493,7 +2529,7 @@ void TeamManager::OnJoinRequestedTeam(Packet *packet, TM_World *world) teamMember->AddToRequestedTeams(teamToJoin); } - DataStructures::List teamsWeAreLeaving; + std::vector teamsWeAreLeaving; if (isTeamSwitch) { if (teamToLeave==0) @@ -2503,10 +2539,10 @@ void TeamManager::OnJoinRequestedTeam(Packet *packet, TM_World *world) else { if (teamMember->IsOnTeam(teamToLeave)) - teamsWeAreLeaving.Push(teamToLeave, _FILE_AND_LINE_); + teamsWeAreLeaving.push_back(teamToLeave); } - if (teamsWeAreLeaving.Size()==0) + if (teamsWeAreLeaving.size()==0) isTeamSwitch=false; } diff --git a/Tests/Integration/PacketizedTCPFramingTests.cpp b/Tests/Integration/PacketizedTCPFramingTests.cpp new file mode 100644 index 000000000..8ba67d4ba --- /dev/null +++ b/Tests/Integration/PacketizedTCPFramingTests.cpp @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + * + * Message framing and connection-notification tests for PacketizedTCP. + * + * PacketizedTCP had no tests. It adds a length prefix on top of TCPInterface's + * raw byte stream, which means a received TCP segment may hold part of a + * message, exactly one, or several -- and the three protected containers being + * migrated to std containers (issue #60) are what hold that state: + * + * connections SystemAddress -> ByteQueue, the per-peer reassembly buffer + * waitingPackets whole messages already reassembled, waiting for Receive() + * the four SystemAddress queues feeding HasNewIncomingConnection and friends + * + * None of it is reachable without a real connection: Send() on an address with + * no entry in `connections` returns without sending, and the notification queues + * are filled from TCPInterface's own lock-free queues. So these run over + * loopback TCP. + */ + +#include + +#include "mafianet/packetized_tcp.h" +#include "mafianet/message_identifiers.h" + +#if _RAKNET_SUPPORT_PacketizedTCP == 1 + +#include "mafianet/get_time.h" +#include "mafianet/sleep.h" +#include "TestHelpers.h" + +#include + +#include +#include + +using namespace MafiaNet; + +namespace +{ + class PacketizedTCPFraming : public ::testing::Test + { + protected: + void SetUp() override + { + server = PacketizedTCP::GetInstance(); + client = PacketizedTCP::GetInstance(); + ASSERT_NE(server, nullptr); + ASSERT_NE(client, nullptr); + + port = TestHelpers::StartTcpOnAFreePort(*server, 4); + ASSERT_NE(port, 0) << "could not start a listener on any free TCP port"; + + ASSERT_TRUE(client->Start(0, 0)); + } + + void TearDown() override + { + // Must survive a failed ASSERT_, so no cleanup is left to the test body. + if (client) + { + DrainAndFree(client); + client->Stop(); + PacketizedTCP::DestroyInstance(client); + client = 0; + } + if (server) + { + DrainAndFree(server); + server->Stop(); + PacketizedTCP::DestroyInstance(server); + server = 0; + } + } + + static void DrainAndFree(PacketizedTCP *t) + { + Packet *p; + while ((p = t->Receive()) != 0) + t->DeallocatePacket(p); + } + + // Connects client to server and waits until each side has observed it. + // Returns the address the client is known by on the server. + SystemAddress ConnectAndWait(unsigned int timeoutMS = 10000) + { + // A non-blocking Connect always returns UNASSIGNED_SYSTEM_ADDRESS -- it hands + // the work to a thread and reports the outcome through + // HasCompletedConnectionAttempt / HasFailedConnectionAttempt. Its return + // value says nothing, so it is not checked. + client->Connect("127.0.0.1", port, false); + + SystemAddress completed = UNASSIGNED_SYSTEM_ADDRESS; + SystemAddress incoming = UNASSIGNED_SYSTEM_ADDRESS; + const TimeMS start = GetTimeMS(); + while ((completed == UNASSIGNED_SYSTEM_ADDRESS || incoming == UNASSIGNED_SYSTEM_ADDRESS) + && GetTimeMS() - start < timeoutMS) + { + if (completed == UNASSIGNED_SYSTEM_ADDRESS) + completed = client->HasCompletedConnectionAttempt(); + if (incoming == UNASSIGNED_SYSTEM_ADDRESS) + incoming = server->HasNewIncomingConnection(); + if (completed == UNASSIGNED_SYSTEM_ADDRESS || incoming == UNASSIGNED_SYSTEM_ADDRESS) + RakSleep(10); + } + + if (completed == UNASSIGNED_SYSTEM_ADDRESS || incoming == UNASSIGNED_SYSTEM_ADDRESS) + return UNASSIGNED_SYSTEM_ADDRESS; + + return incoming; + } + + // Collects exactly `count` messages, as strings, or fewer on timeout. + // + // ID_DOWNLOAD_PROGRESS is skipped and counted separately. PacketizedTCP + // emits one of those every 64KB while a message is still incomplete, so a + // large payload -- or two interleaved ones -- produces them alongside the + // real messages. They are part of the protocol, not noise: see + // LargeTransfersEmitWellFormedProgressPackets below. + std::vector ReceiveMessages(PacketizedTCP *t, size_t count, + unsigned int timeoutMS = 10000) + { + std::vector out; + const TimeMS start = GetTimeMS(); + while (out.size() < count && GetTimeMS() - start < timeoutMS) + { + Packet *p = t->Receive(); + if (p) + { + if (p->length > 0 && p->data[0] == ID_DOWNLOAD_PROGRESS) + progressPackets++; + else + out.push_back(std::string((const char *) p->data, p->length)); + t->DeallocatePacket(p); + } + else + RakSleep(10); + } + return out; + } + + int progressPackets = 0; + + PacketizedTCP *server; + PacketizedTCP *client; + unsigned short port; + }; +} + +TEST_F(PacketizedTCPFraming, ConnectionIsObservedByBothSides) +{ + const SystemAddress clientOnServer = ConnectAndWait(); + ASSERT_NE(clientOnServer, UNASSIGNED_SYSTEM_ADDRESS) << "connection was never observed"; + + // Each notification is handed out once. + ASSERT_EQ(server->HasNewIncomingConnection(), UNASSIGNED_SYSTEM_ADDRESS); + ASSERT_EQ(client->HasCompletedConnectionAttempt(), UNASSIGNED_SYSTEM_ADDRESS); + + // And the wrong queue stays empty. + ASSERT_EQ(client->HasFailedConnectionAttempt(), UNASSIGNED_SYSTEM_ADDRESS); + ASSERT_EQ(server->HasLostConnection(), UNASSIGNED_SYSTEM_ADDRESS); +} + +// One message per Receive, with its exact length -- the length prefix must not +// leak into the payload and the payload must not be truncated. +TEST_F(PacketizedTCPFraming, ASingleMessageArrivesWithItsExactLength) +{ + ASSERT_NE(ConnectAndWait(), UNASSIGNED_SYSTEM_ADDRESS); + + const std::string msg = "hello packetized tcp"; + client->Send(msg.c_str(), (unsigned) msg.size(), UNASSIGNED_SYSTEM_ADDRESS, true); + + const std::vector got = ReceiveMessages(server, 1); + + ASSERT_EQ(got.size(), 1u) << "the message never arrived"; + ASSERT_EQ(got[0], msg); +} + +// Several small messages sent back to back almost certainly arrive in one TCP +// segment, so the receive path has to split them out of the ByteQueue and queue +// them in waitingPackets. This is the case that pins waitingPackets' FIFO order: +// draining it as a stack would reverse the batch. +TEST_F(PacketizedTCPFraming, ABatchOfMessagesInOneSegmentKeepsItsOrder) +{ + ASSERT_NE(ConnectAndWait(), UNASSIGNED_SYSTEM_ADDRESS); + + std::vector sent; + for (int i = 0; i < 12; i++) + { + char buf[32]; + snprintf(buf, sizeof(buf), "msg-%02d", i); + sent.push_back(buf); + } + + for (size_t i = 0; i < sent.size(); i++) + client->Send(sent[i].c_str(), (unsigned) sent[i].size(), UNASSIGNED_SYSTEM_ADDRESS, true); + + const std::vector got = ReceiveMessages(server, sent.size()); + + ASSERT_EQ(got.size(), sent.size()) << "only " << got.size() << " of " << sent.size() << " arrived"; + for (size_t i = 0; i < sent.size(); i++) + ASSERT_EQ(got[i], sent[i]) << "message " << i << " is out of order or corrupt"; +} + +// A payload far larger than one segment has to be reassembled from several +// TCPInterface packets inside the per-connection ByteQueue before any of it is +// handed to Receive. +TEST_F(PacketizedTCPFraming, AMessageLargerThanOneSegmentIsReassembled) +{ + ASSERT_NE(ConnectAndWait(), UNASSIGNED_SYSTEM_ADDRESS); + + std::string big; + big.reserve(200000); + for (int i = 0; i < 200000; i++) + big.push_back((char) ('a' + (i % 26))); + + client->Send(big.c_str(), (unsigned) big.size(), UNASSIGNED_SYSTEM_ADDRESS, true); + + const std::vector got = ReceiveMessages(server, 1, 20000); + + ASSERT_EQ(got.size(), 1u) << "the large message never completed"; + ASSERT_EQ(got[0].size(), big.size()) << "wrong reassembled length"; + ASSERT_EQ(got[0], big) << "the reassembled payload does not match"; +} + +// A large message followed by a small one: the small one must not be lost in the +// tail of the ByteQueue, and must not arrive first. +TEST_F(PacketizedTCPFraming, ASmallMessageAfterALargeOneStillArrivesInOrder) +{ + ASSERT_NE(ConnectAndWait(), UNASSIGNED_SYSTEM_ADDRESS); + + std::string big(120000, 'x'); + const std::string small = "tail"; + + client->Send(big.c_str(), (unsigned) big.size(), UNASSIGNED_SYSTEM_ADDRESS, true); + client->Send(small.c_str(), (unsigned) small.size(), UNASSIGNED_SYSTEM_ADDRESS, true); + + const std::vector got = ReceiveMessages(server, 2, 20000); + + ASSERT_EQ(got.size(), 2u); + ASSERT_EQ(got[0].size(), big.size()); + ASSERT_EQ(got[0], big); + ASSERT_EQ(got[1], small); +} + +// Both directions at once, so each side keeps its own reassembly buffer. +TEST_F(PacketizedTCPFraming, BothDirectionsAreFramedIndependently) +{ + const SystemAddress clientOnServer = ConnectAndWait(); + ASSERT_NE(clientOnServer, UNASSIGNED_SYSTEM_ADDRESS); + + const std::string toServer = "client says hello"; + const std::string toClient = "server says hi back"; + + client->Send(toServer.c_str(), (unsigned) toServer.size(), UNASSIGNED_SYSTEM_ADDRESS, true); + server->Send(toClient.c_str(), (unsigned) toClient.size(), clientOnServer, false); + + const std::vector atServer = ReceiveMessages(server, 1); + const std::vector atClient = ReceiveMessages(client, 1); + + ASSERT_EQ(atServer.size(), 1u); + ASSERT_EQ(atServer[0], toServer); + ASSERT_EQ(atClient.size(), 1u); + ASSERT_EQ(atClient[0], toClient); +} + +// CloseConnection removes the entry from `connections` on the closing side and +// surfaces a lost-connection notification on the other. +TEST_F(PacketizedTCPFraming, ClosingAConnectionIsReportedToThePeer) +{ + const SystemAddress clientOnServer = ConnectAndWait(); + ASSERT_NE(clientOnServer, UNASSIGNED_SYSTEM_ADDRESS); + + server->CloseConnection(clientOnServer); + + SystemAddress lost = UNASSIGNED_SYSTEM_ADDRESS; + const TimeMS start = GetTimeMS(); + while (lost == UNASSIGNED_SYSTEM_ADDRESS && GetTimeMS() - start < 10000) + { + lost = client->HasLostConnection(); + if (lost == UNASSIGNED_SYSTEM_ADDRESS) + RakSleep(10); + } + + ASSERT_NE(lost, UNASSIGNED_SYSTEM_ADDRESS) << "the client never saw the connection drop"; + ASSERT_EQ(client->HasLostConnection(), UNASSIGNED_SYSTEM_ADDRESS) << "reported more than once"; +} + +// Connecting to a port nothing is listening on fills _failedConnectionAttempts, +// which no other test here touches. +TEST_F(PacketizedTCPFraming, AFailedConnectionAttemptIsReported) +{ + const unsigned short deadPort = TestHelpers::FindFreeTcpPort(); + ASSERT_NE(deadPort, 0); + ASSERT_NE(deadPort, port); + + client->Connect("127.0.0.1", deadPort, false); + + SystemAddress failed = UNASSIGNED_SYSTEM_ADDRESS; + const TimeMS start = GetTimeMS(); + while (failed == UNASSIGNED_SYSTEM_ADDRESS && GetTimeMS() - start < 15000) + { + failed = client->HasFailedConnectionAttempt(); + if (failed == UNASSIGNED_SYSTEM_ADDRESS) + RakSleep(10); + } + + ASSERT_NE(failed, UNASSIGNED_SYSTEM_ADDRESS) << "the failed attempt was never reported"; + ASSERT_EQ(client->HasCompletedConnectionAttempt(), UNASSIGNED_SYSTEM_ADDRESS) + << "a failed attempt must not also report as completed"; +} + +// Two clients at once: each needs its own entry in `connections`, keyed by +// address. A single shared buffer would interleave their byte streams and the +// framing would fall apart. +TEST_F(PacketizedTCPFraming, TwoClientsAreFramedSeparately) +{ + ASSERT_NE(ConnectAndWait(), UNASSIGNED_SYSTEM_ADDRESS); + + PacketizedTCP *second = PacketizedTCP::GetInstance(); + ASSERT_NE(second, nullptr); + ASSERT_TRUE(second->Start(0, 0)); + + second->Connect("127.0.0.1", port, false); + + SystemAddress secondOnServer = UNASSIGNED_SYSTEM_ADDRESS; + const TimeMS start = GetTimeMS(); + while (secondOnServer == UNASSIGNED_SYSTEM_ADDRESS && GetTimeMS() - start < 10000) + { + secondOnServer = server->HasNewIncomingConnection(); + if (secondOnServer == UNASSIGNED_SYSTEM_ADDRESS) + RakSleep(10); + } + + if (secondOnServer == UNASSIGNED_SYSTEM_ADDRESS) + { + DrainAndFree(second); + second->Stop(); + PacketizedTCP::DestroyInstance(second); + FAIL() << "the second connection was never observed"; + } + + // Each payload is far bigger than one segment and filled with a distinct + // pattern, so the server's reads from the two sockets necessarily interleave. + // A single shared reassembly buffer would splice one client's bytes into the + // other's message; two small messages would not catch that, because each would + // already be complete when it was written and would still read back in order. + std::string fromFirst(180000, 'A'); + std::string fromSecond(180000, 'B'); + + client->Send(fromFirst.c_str(), (unsigned) fromFirst.size(), UNASSIGNED_SYSTEM_ADDRESS, true); + second->Send(fromSecond.c_str(), (unsigned) fromSecond.size(), UNASSIGNED_SYSTEM_ADDRESS, true); + + // Both messages must arrive intact; the order between two sockets is not ours + // to determine, so match as a set. + const std::vector got = ReceiveMessages(server, 2, 30000); + + DrainAndFree(second); + second->Stop(); + PacketizedTCP::DestroyInstance(second); + + ASSERT_EQ(got.size(), 2u); + const bool matched = (got[0] == fromFirst && got[1] == fromSecond) + || (got[0] == fromSecond && got[1] == fromFirst); + ASSERT_TRUE(matched) << "payloads were mixed between the two connections: got " + << got[0].size() << " and " << got[1].size() << " bytes"; +} + +// The progress notifications the other tests skip over: they must actually be +// emitted for a payload far larger than the socket buffers, and each one must be +// internally consistent -- its bitSize has to describe its own length. +// +// The size is what makes this deterministic. The progress branch only runs when a +// message is still incomplete after Receive() has drained everything available, +// so a payload that fits in the socket buffers may complete in one go and emit +// nothing; 4MB cannot, and the poll below runs Receive() with no sleep so it +// keeps looking while the transfer is still in flight. +TEST_F(PacketizedTCPFraming, LargeTransfersEmitWellFormedProgressPackets) +{ + ASSERT_NE(ConnectAndWait(), UNASSIGNED_SYSTEM_ADDRESS); + + const size_t payloadSize = 4u * 1024u * 1024u; + std::string payload(payloadSize, 'Z'); + client->Send(payload.c_str(), (unsigned) payload.size(), UNASSIGNED_SYSTEM_ADDRESS, true); + + int progressSeen = 0; + bool progressWellFormed = true; + bool completed = false; + + const TimeMS start = GetTimeMS(); + while (completed == false && GetTimeMS() - start < 30000) + { + Packet *p = server->Receive(); + if (p == 0) + continue; // no sleep: keep polling while the transfer is in flight + + if (p->length > 0 && p->data[0] == ID_DOWNLOAD_PROGRESS) + { + progressSeen++; + if (p->bitSize != BYTES_TO_BITS(p->length)) + progressWellFormed = false; + } + else if (p->length == payloadSize) + completed = true; + + server->DeallocatePacket(p); + } + + ASSERT_TRUE(completed) << "the 4MB message never completed"; + ASSERT_GT(progressSeen, 0) << "no ID_DOWNLOAD_PROGRESS was emitted for a 4MB transfer"; + ASSERT_TRUE(progressWellFormed) + << "an ID_DOWNLOAD_PROGRESS packet's bitSize did not match its own length"; +} + +// Receive() reassembles everything available and returns one message, leaving the +// rest in waitingPackets. Stop() used to free those and leave the freed pointers +// in the queue, so a Receive() after Stop() handed one straight back. +TEST_F(PacketizedTCPFraming, ReceiveAfterStopReturnsNothing) +{ + ASSERT_NE(ConnectAndWait(), UNASSIGNED_SYSTEM_ADDRESS); + + for (int i = 0; i < 6; i++) + { + char buf[16]; + snprintf(buf, sizeof(buf), "m%d", i); + client->Send(buf, (unsigned) strlen(buf), UNASSIGNED_SYSTEM_ADDRESS, true); + } + + // One Receive gets the first message and queues the other five. + Packet *first = 0; + const TimeMS start = GetTimeMS(); + while (first == 0 && GetTimeMS() - start < 10000) + { + first = server->Receive(); + if (first == 0) + RakSleep(10); + } + ASSERT_NE(first, nullptr); + server->DeallocatePacket(first); + + server->Stop(); + + ASSERT_EQ(server->Receive(), nullptr) << "Receive() returned a packet Stop() had already freed"; +} + +// Destroying the instance with messages still queued. ~PacketizedTCP freed the +// per-connection buffers but not the reassembled messages, so everything still +// waiting for Receive leaked. The leak itself is only visible under +// LeakSanitizer (Linux; macOS ASAN has no leak detection), so what this case +// asserts on every platform is that the destroy path runs clean -- it exists to +// give LSAN the scenario to look at in the Linux gate. +TEST_F(PacketizedTCPFraming, DestroyingTheInstanceWithQueuedMessagesIsClean) +{ + ASSERT_NE(ConnectAndWait(), UNASSIGNED_SYSTEM_ADDRESS); + + for (int i = 0; i < 6; i++) + { + char buf[16]; + snprintf(buf, sizeof(buf), "m%d", i); + client->Send(buf, (unsigned) strlen(buf), UNASSIGNED_SYSTEM_ADDRESS, true); + } + + Packet *first = 0; + const TimeMS start = GetTimeMS(); + while (first == 0 && GetTimeMS() - start < 10000) + { + first = server->Receive(); + if (first == 0) + RakSleep(10); + } + ASSERT_NE(first, nullptr); + server->DeallocatePacket(first); + + // No Stop(), no drain: straight to the destructor with five messages queued. + PacketizedTCP::DestroyInstance(server); + server = 0; + + SUCCEED(); +} + +// And stopping twice must not free them twice. +TEST_F(PacketizedTCPFraming, StoppingTwiceWithQueuedMessagesIsSafe) +{ + ASSERT_NE(ConnectAndWait(), UNASSIGNED_SYSTEM_ADDRESS); + + for (int i = 0; i < 6; i++) + { + char buf[16]; + snprintf(buf, sizeof(buf), "m%d", i); + client->Send(buf, (unsigned) strlen(buf), UNASSIGNED_SYSTEM_ADDRESS, true); + } + + Packet *first = 0; + const TimeMS start = GetTimeMS(); + while (first == 0 && GetTimeMS() - start < 10000) + { + first = server->Receive(); + if (first == 0) + RakSleep(10); + } + ASSERT_NE(first, nullptr); + server->DeallocatePacket(first); + + server->Stop(); + server->Stop(); + + ASSERT_EQ(server->Receive(), nullptr); +} + +#endif // _RAKNET_SUPPORT_PacketizedTCP diff --git a/Tests/Integration/TCPInterfaceLifecycleTests.cpp b/Tests/Integration/TCPInterfaceLifecycleTests.cpp new file mode 100644 index 000000000..9bc926c1f --- /dev/null +++ b/Tests/Integration/TCPInterfaceLifecycleTests.cpp @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + * + * Start/Stop lifecycle tests for TCPInterface. + * + * These exist because of a specific failure: TCPInterface uses 0 as its + * "no socket" value, and Stop() closed remoteClients[i].socket for every slot, + * including the ones that were never used and still held that 0. On POSIX, 0 is + * a perfectly good descriptor -- stdin in a normal process -- so Stop() closed + * it, once per unused slot. The visible consequence was worse than the obvious + * one: with descriptor 0 free, the next socket created anywhere in the process + * was handed number 0, and every `listenSocket != 0` check then read it as + * "no socket". A second TCPInterface in the same process would start, report + * success, and never accept anything. + * + * They run here rather than in the unit suite because proving a listener works + * takes a real connection. + */ + +#include + +#include "mafianet/tcp_interface.h" + +#if _RAKNET_SUPPORT_TCPInterface == 1 + +#include "mafianet/get_time.h" +#include "mafianet/sleep.h" +#include "TestHelpers.h" + +#ifndef _WIN32 +#include +#endif + +using namespace MafiaNet; + +namespace +{ + class TCPInterfaceLifecycle : public ::testing::Test + { + protected: + // Brings up a listener and a client, connects them, and tears both down. + // Returns true if both sides observed the connection. + static bool RunOneCycle(unsigned int timeoutMS = 10000) + { + TCPInterface server; + TCPInterface client; + + // Four slots, one of which gets used: the other three are the unused + // slots whose socket value is 0. + const unsigned short port = TestHelpers::StartTcpOnAFreePort(server, 4); + if (port == 0) + return false; + if (client.Start(0, 0) == false) + return false; + + client.Connect("127.0.0.1", port, false); + + SystemAddress completed = UNASSIGNED_SYSTEM_ADDRESS; + SystemAddress incoming = UNASSIGNED_SYSTEM_ADDRESS; + const TimeMS start = GetTimeMS(); + while ((completed == UNASSIGNED_SYSTEM_ADDRESS || incoming == UNASSIGNED_SYSTEM_ADDRESS) + && GetTimeMS() - start < timeoutMS) + { + if (completed == UNASSIGNED_SYSTEM_ADDRESS) + completed = client.HasCompletedConnectionAttempt(); + if (incoming == UNASSIGNED_SYSTEM_ADDRESS) + incoming = server.HasNewIncomingConnection(); + if (completed == UNASSIGNED_SYSTEM_ADDRESS || incoming == UNASSIGNED_SYSTEM_ADDRESS) + RakSleep(10); + } + + const bool ok = (completed != UNASSIGNED_SYSTEM_ADDRESS) + && (incoming != UNASSIGNED_SYSTEM_ADDRESS); + + client.Stop(); + server.Stop(); + return ok; + } + }; +} + +// The behavioural form, portable: three Start/Stop cycles in one process. The +// first one always worked; the second was the one that silently stopped +// accepting once descriptor 0 had been freed and reassigned. +TEST_F(TCPInterfaceLifecycle, RepeatedStartStopCyclesKeepAccepting) +{ + ASSERT_TRUE(RunOneCycle()) << "cycle 1 failed"; + ASSERT_TRUE(RunOneCycle()) << "cycle 2 failed -- a previous Stop() left the process in a bad state"; + ASSERT_TRUE(RunOneCycle()) << "cycle 3 failed"; +} + +#ifndef _WIN32 +// The direct form: descriptor 0 must still be open afterwards. POSIX only -- +// on Windows descriptor 0 is not a socket handle and SOCKET 0 does not collide +// with stdin the same way. +TEST_F(TCPInterfaceLifecycle, StopDoesNotCloseDescriptorZero) +{ + ASSERT_NE(fcntl(0, F_GETFD), -1) << "descriptor 0 was already closed before the test ran"; + + { + TCPInterface tcp; + // Eight slots, none of them ever used, so eight slots hold the 0 value. + ASSERT_NE(TestHelpers::StartTcpOnAFreePort(tcp, 8), 0); + tcp.Stop(); + } + + ASSERT_NE(fcntl(0, F_GETFD), -1) + << "Stop() closed descriptor 0 -- it closed the unused remoteClients slots' 0 value"; +} + +// Stopping twice must not close anything a second time either. +TEST_F(TCPInterfaceLifecycle, StoppingTwiceDoesNotCloseDescriptorZero) +{ + ASSERT_NE(fcntl(0, F_GETFD), -1); + + { + TCPInterface tcp; + ASSERT_NE(TestHelpers::StartTcpOnAFreePort(tcp, 4), 0); + tcp.Stop(); + tcp.Stop(); + } + + ASSERT_NE(fcntl(0, F_GETFD), -1); +} +#endif + +// Start() discarded CreateListenSocket's result, so a bind failure produced a +// started interface with no listener and a return value of true. +TEST_F(TCPInterfaceLifecycle, StartFailsWhenThePortIsAlreadyInUse) +{ + TCPInterface first; + const unsigned short port = TestHelpers::StartTcpOnAFreePort(first, 2); + ASSERT_NE(port, 0) << "the first listener should have bound a port"; + + TCPInterface second; + ASSERT_FALSE(second.Start(port, 2)) << "Start() must report a failed bind"; + + // And the first one still works afterwards. + TCPInterface client; + ASSERT_TRUE(client.Start(0, 0)); + client.Connect("127.0.0.1", port, false); + + SystemAddress incoming = UNASSIGNED_SYSTEM_ADDRESS; + const TimeMS start = GetTimeMS(); + while (incoming == UNASSIGNED_SYSTEM_ADDRESS && GetTimeMS() - start < 10000) + { + incoming = first.HasNewIncomingConnection(); + if (incoming == UNASSIGNED_SYSTEM_ADDRESS) + RakSleep(10); + } + + client.Stop(); + first.Stop(); + second.Stop(); + + ASSERT_NE(incoming, UNASSIGNED_SYSTEM_ADDRESS) << "the surviving listener stopped accepting"; +} + +// A failed Start must leave the interface stoppable and restartable. +TEST_F(TCPInterfaceLifecycle, AnInterfaceWhoseStartFailedCanStartAgain) +{ + TCPInterface blocker; + const unsigned short taken = TestHelpers::StartTcpOnAFreePort(blocker, 1); + ASSERT_NE(taken, 0); + + TCPInterface tcp; + ASSERT_FALSE(tcp.Start(taken, 1)); + + tcp.Stop(); // must be harmless after a failed Start + + const bool restarted = TestHelpers::StartTcpOnAFreePort(tcp, 1) != 0; + + tcp.Stop(); + blocker.Stop(); + + ASSERT_TRUE(restarted) << "a failed Start left the interface unusable"; +} + +// A connection accepted when every client slot is already taken used to be +// leaked: the "no free slot" branch tested `newRemoteClientIndex == -1`, which +// the loop above can never produce -- it leaves the index at remoteClientsLength +// -- so the accepted socket was never closed and the client was left connected to +// a server that would never service it. And had the branch fired, it closed the +// *listen* socket rather than the connection it could not take. +TEST_F(TCPInterfaceLifecycle, AConnectionRefusedForLackOfSlotsIsClosed) +{ + TCPInterface server; + const unsigned short port = TestHelpers::StartTcpOnAFreePort(server, 1); // exactly one slot + ASSERT_NE(port, 0); + + TCPInterface first; + ASSERT_TRUE(first.Start(0, 0)); + first.Connect("127.0.0.1", port, false); + + SystemAddress accepted = UNASSIGNED_SYSTEM_ADDRESS; + TimeMS start = GetTimeMS(); + while (accepted == UNASSIGNED_SYSTEM_ADDRESS && GetTimeMS() - start < 10000) + { + accepted = server.HasNewIncomingConnection(); + if (accepted == UNASSIGNED_SYSTEM_ADDRESS) + RakSleep(10); + } + + TCPInterface second; + bool secondStarted = false; + SystemAddress secondLost = UNASSIGNED_SYSTEM_ADDRESS; + SystemAddress stillAccepting = UNASSIGNED_SYSTEM_ADDRESS; + + if (accepted != UNASSIGNED_SYSTEM_ADDRESS) + { + secondStarted = second.Start(0, 0); + if (secondStarted) + { + second.Connect("127.0.0.1", port, false); + + // The server has no slot for it, so the server must close the socket and + // the client must see the connection go away. + start = GetTimeMS(); + while (secondLost == UNASSIGNED_SYSTEM_ADDRESS && GetTimeMS() - start < 10000) + { + secondLost = second.HasLostConnection(); + if (secondLost == UNASSIGNED_SYSTEM_ADDRESS) + RakSleep(10); + } + + // Freeing the slot must let a later connection in -- the listener has to + // have survived the refusal. + server.CloseConnection(accepted); + RakSleep(100); + + second.Connect("127.0.0.1", port, false); + start = GetTimeMS(); + while (stillAccepting == UNASSIGNED_SYSTEM_ADDRESS && GetTimeMS() - start < 10000) + { + stillAccepting = server.HasNewIncomingConnection(); + if (stillAccepting == UNASSIGNED_SYSTEM_ADDRESS) + RakSleep(10); + } + } + } + + second.Stop(); + first.Stop(); + server.Stop(); + + ASSERT_NE(accepted, UNASSIGNED_SYSTEM_ADDRESS) << "the first connection was never accepted"; + ASSERT_TRUE(secondStarted); + ASSERT_NE(secondLost, UNASSIGNED_SYSTEM_ADDRESS) + << "the refused connection was left open instead of being closed"; + ASSERT_NE(stillAccepting, UNASSIGNED_SYSTEM_ADDRESS) + << "the listener stopped accepting after refusing a connection"; +} + +#endif // _RAKNET_SUPPORT_TCPInterface diff --git a/Tests/Integration/ThreadPoolWorkerTests.cpp b/Tests/Integration/ThreadPoolWorkerTests.cpp new file mode 100644 index 000000000..06cb84d9e --- /dev/null +++ b/Tests/Integration/ThreadPoolWorkerTests.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + * + * Worker-thread tests for ThreadPool. + * + * The hermetic cases in Tests/Unit/ThreadPoolQueueTests.cpp cover every queue + * operation the public API performs on the calling thread, but one thing is only + * observable once a worker runs: inputFunctionQueue and inputQueue are *paired* + * arrays, and the worker pops one element from each. If a removal ever took the + * element from only one of them, every later job would run with another job's + * callback -- silently, with no size mismatch to notice. These tests pair + * callbacks and inputs so that mix-up changes the output values. + * + * They live here rather than in the unit suite because they need a real thread + * and therefore a deadline poll. + */ + +#include + +#include "mafianet/thread_pool.h" +#include "mafianet/get_time.h" +#include "mafianet/sleep.h" + +#include + +namespace +{ + // Each callback tags its result with a different offset, so an output value + // says which callback produced it and which input it was handed. + int AddOneHundred(int in, bool *returnOutput, void *) { *returnOutput = true; return in + 100; } + int AddTwoHundred(int in, bool *returnOutput, void *) { *returnOutput = true; return in + 200; } + int ProduceNothing(int, bool *returnOutput, void *) { *returnOutput = false; return -1; } + + class ThreadPoolWorker : public ::testing::Test + { + protected: + void TearDown() override + { + pool.StopThreads(); + pool.ClearInput(); + pool.ClearOutput(); + } + + // Collects exactly `expected` outputs, or fails on the deadline. + std::vector DrainOutputs(size_t expected, unsigned int timeoutMS = 10000) + { + std::vector got; + const MafiaNet::TimeMS start = MafiaNet::GetTimeMS(); + while (got.size() < expected && MafiaNet::GetTimeMS() - start < timeoutMS) + { + if (pool.HasOutput()) + got.push_back(pool.GetOutput()); + else + RakSleep(10); + } + return got; + } + + ThreadPool pool; + }; +} + +// The pairing test. Inputs are queued before any thread starts, so the removal +// happens against a queue nothing is draining; then one worker runs them in order. +TEST_F(ThreadPoolWorker, RemovingAnInputKeepsEveryOtherInputWithItsOwnCallback) +{ + pool.AddInput(AddOneHundred, 10); + pool.AddInput(AddTwoHundred, 20); + pool.AddInput(AddOneHundred, 30); + pool.AddInput(AddTwoHundred, 40); + + pool.RemoveInputAtIndex(1); // drops (AddTwoHundred, 20) + + ASSERT_EQ(pool.InputSize(), 3u); + + ASSERT_TRUE(pool.StartThreads(1, 0)); + + const std::vector got = DrainOutputs(3); + + ASSERT_EQ(got.size(), 3u) << "expected three outputs, got " << got.size(); + + // A single worker drains the queue head-first, so the order is determined. + // 230 or 140 here would mean an input was run with its neighbour's callback. + ASSERT_EQ(got[0], 110) << "AddOneHundred(10)"; + ASSERT_EQ(got[1], 130) << "AddOneHundred(30)"; + ASSERT_EQ(got[2], 240) << "AddTwoHundred(40)"; +} + +// Removing the head, where an off-by-one in the paired removal is easiest to hide. +TEST_F(ThreadPoolWorker, RemovingTheHeadInputKeepsTheRestPaired) +{ + pool.AddInput(AddOneHundred, 1); + pool.AddInput(AddTwoHundred, 2); + pool.AddInput(AddOneHundred, 3); + + pool.RemoveInputAtIndex(0); // drops (AddOneHundred, 1) + + ASSERT_TRUE(pool.StartThreads(1, 0)); + + const std::vector got = DrainOutputs(2); + + ASSERT_EQ(got.size(), 2u); + ASSERT_EQ(got[0], 202) << "AddTwoHundred(2)"; + ASSERT_EQ(got[1], 103) << "AddOneHundred(3)"; +} + +TEST_F(ThreadPoolWorker, WorkerRunsQueuedJobsInFifoOrder) +{ + for (int i = 1; i <= 8; i++) + pool.AddInput(AddOneHundred, i); + + ASSERT_TRUE(pool.StartThreads(1, 0)); + + const std::vector got = DrainOutputs(8); + + ASSERT_EQ(got.size(), 8u); + for (int i = 0; i < 8; i++) + ASSERT_EQ(got[(size_t) i], 100 + i + 1) << "output " << i << " is out of order"; +} + +// A callback that clears returnOutput must not push anything, while the jobs +// around it still do. +TEST_F(ThreadPoolWorker, CallbacksThatSuppressOutputPushNothing) +{ + pool.AddInput(AddOneHundred, 1); + pool.AddInput(ProduceNothing, 2); + pool.AddInput(AddOneHundred, 3); + + ASSERT_TRUE(pool.StartThreads(1, 0)); + + const std::vector got = DrainOutputs(2); + + ASSERT_EQ(got.size(), 2u); + ASSERT_EQ(got[0], 101); + ASSERT_EQ(got[1], 103); + + // Give the pool a moment to produce a third output it must not produce. + const MafiaNet::TimeMS start = MafiaNet::GetTimeMS(); + while (MafiaNet::GetTimeMS() - start < 200) + { + ASSERT_FALSE(pool.HasOutput()) << "ProduceNothing pushed an output"; + RakSleep(10); + } +} + +// Jobs added while the worker is already running are picked up too, which is the +// path send_to_thread and file_list_transfer actually use. +TEST_F(ThreadPoolWorker, JobsAddedAfterStartupAreStillRun) +{ + ASSERT_TRUE(pool.StartThreads(1, 0)); + + pool.AddInput(AddOneHundred, 5); + pool.AddInput(AddTwoHundred, 6); + + const std::vector got = DrainOutputs(2); + + ASSERT_EQ(got.size(), 2u); + ASSERT_EQ(got[0], 105); + ASSERT_EQ(got[1], 206); +} + +// StopThreads must leave any unprocessed input in place, so the caller can walk +// the queue and free whatever it owns -- the contract AddInput's comment states +// and send_to_thread's shutdown path relies on. +TEST_F(ThreadPoolWorker, StopThreadsLeavesUnprocessedInputInTheQueue) +{ + ASSERT_TRUE(pool.StartThreads(1, 0)); + + // Let the worker reach its idle wait before queueing anything, so the jobs + // below are not consumed between StartThreads and StopThreads. + RakSleep(100); + pool.StopThreads(); + + pool.AddInput(AddOneHundred, 7); + pool.AddInput(AddOneHundred, 8); + + ASSERT_EQ(pool.InputSize(), 2u) << "input queued after StopThreads must stay queued"; + ASSERT_EQ(pool.GetInputAtIndex(0), 7); + ASSERT_EQ(pool.GetInputAtIndex(1), 8); + ASSERT_FALSE(pool.HasOutput()); +} diff --git a/Tests/Support/TestHelpers.cpp b/Tests/Support/TestHelpers.cpp index 0c2d492c3..3779a0490 100644 --- a/Tests/Support/TestHelpers.cpp +++ b/Tests/Support/TestHelpers.cpp @@ -10,6 +10,15 @@ #include "TestHelpers.h" +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif + TestHelpers::TestHelpers(void) { } @@ -132,3 +141,71 @@ void RecieveForXTime(RakPeerInterface *reciever,int millisecondsToWait) } } + +unsigned short TestHelpers::FindFreeTcpPort(void) +{ +#ifdef _WIN32 + // TCPInterface::Start calls WSAStartup, but this helper runs before it, so ask + // for Winsock here too. WSAStartup/WSACleanup are reference counted. + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) + return 0; +#endif + + unsigned short port = 0; + +#ifdef _WIN32 + SOCKET s = socket(AF_INET, SOCK_STREAM, 0); + if (s != INVALID_SOCKET) +#else + int s = socket(AF_INET, SOCK_STREAM, 0); + if (s >= 0) +#endif + { + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = 0; // let the OS choose + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(s, (sockaddr *) &addr, sizeof(addr)) == 0) + { + sockaddr_in bound; + memset(&bound, 0, sizeof(bound)); +#ifdef _WIN32 + int boundLen = (int) sizeof(bound); +#else + socklen_t boundLen = (socklen_t) sizeof(bound); +#endif + if (getsockname(s, (sockaddr *) &bound, &boundLen) == 0) + port = ntohs(bound.sin_port); + } + +#ifdef _WIN32 + closesocket(s); +#else + close(s); +#endif + } + +#ifdef _WIN32 + WSACleanup(); +#endif + + return port; +} + +unsigned short TestHelpers::StartTcpOnAFreePort(TCPInterface &tcp, unsigned short maxIncomingConnections, + int attempts) +{ + for (int i = 0; i < attempts; i++) + { + const unsigned short port = FindFreeTcpPort(); + if (port == 0) + continue; + if (tcp.Start(port, maxIncomingConnections)) + return port; + // Start() tears itself down on a failed bind, so it is safe to call again. + } + return 0; +} diff --git a/Tests/Support/TestHelpers.h b/Tests/Support/TestHelpers.h index dd04f8054..bfa99e762 100644 --- a/Tests/Support/TestHelpers.h +++ b/Tests/Support/TestHelpers.h @@ -17,6 +17,7 @@ #include "mafianet/message_identifiers.h" #include "mafianet/bit_stream.h" #include "mafianet/peer.h" +#include "mafianet/tcp_interface.h" #include "mafianet/sleep.h" #include "DebugTools.h" #include "CommonFunctions.h" @@ -42,4 +43,27 @@ class TestHelpers static void RecieveForXTime(RakPeerInterface *reciever,int millisecondsToWait); static bool SendTestPacketDirected(RakPeerInterface *sender,char * ip,int port,MafiaNet::Reliability rel=MafiaNet::Reliability::ReliableOrdered,MafiaNet::Priority pr=MafiaNet::Priority::High,int typeNum=ID_USER_PACKET_ENUM+1); + /// Asks the OS for a free TCP port on the loopback interface and gives it back. + /// + /// TCPInterface::Start takes a port and exposes no accessor for the one it bound, + /// so a test that needs a listening TCPInterface cannot use port 0 and read the + /// result back the way the peer tests do. This binds a throwaway socket to port 0, + /// reads the assigned port and closes it, so the caller gets a port that was free a + /// moment ago instead of a hard-coded one. Returns 0 if no port could be obtained. + static unsigned short FindFreeTcpPort(void); + + /// Starts \a tcp listening on a port that was free, retrying on a different + /// port if the bind loses the race. + /// + /// FindFreeTcpPort probes 127.0.0.1 while TCPInterface binds INADDR_ANY, and + /// the OS hands out ephemeral numbers again soon after a test releases them, + /// so a bind can fail because the port is in use on another interface or still + /// in TIME_WAIT from an earlier connection -- TCPInterface sets no + /// SO_REUSEADDR. Retrying the port selection absorbs that; it does not retry + /// anything the caller is asserting about. + /// + /// \return the port it is listening on, or 0 if every attempt failed. + static unsigned short StartTcpOnAFreePort(TCPInterface &tcp, unsigned short maxIncomingConnections, + int attempts = 20); + }; diff --git a/Tests/Unit/PeerListsTests.cpp b/Tests/Unit/PeerListsTests.cpp new file mode 100644 index 000000000..948030f75 --- /dev/null +++ b/Tests/Unit/PeerListsTests.cpp @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + */ + +#include + +#include "mafianet/peer.h" +#include "mafianet/peer_interface.h" +#include "mafianet/sleep.h" + +#include + +using namespace MafiaNet; + +// Pins the observable behaviour of RakPeer's protected lists before they move from +// DataStructures::List/Queue to std containers (issue #60): +// +// banList -> AddToBanList / RemoveFromBanList / IsBanned / ClearBanList +// securityExceptionList -> Add/Remove/IsInSecurityExceptionList +// packetReturnQueue -> PushBackPacket(packet, pushAtHead) / Receive / GetReceiveBufferSize +// socketList -> GetSockets / GetSocket after Startup +// +// All hermetic: no Connect(), no loopback traffic, no wall-clock polling. The one +// place time appears is the temporary-ban expiry, where the ban is created already +// expired rather than waited out. + +namespace +{ + class PeerLists : public ::testing::Test + { + protected: + void SetUp() override + { + peer = RakPeerInterface::GetInstance(); + ASSERT_NE(peer, nullptr); + } + + void TearDown() override + { + if (peer) + { + peer->Shutdown(0); + RakPeerInterface::DestroyInstance(peer); + peer = 0; + } + } + + // Starts the peer on an OS-assigned ephemeral loopback port. No peer ever + // connects to it; it exists so socketList and the packet pool are populated. + void StartOnEphemeralPort(unsigned short socketCount = 1) + { + SocketDescriptor sd[4]; + ASSERT_LE(socketCount, 4u); + for (unsigned short i = 0; i < socketCount; i++) + sd[i] = SocketDescriptor(0, "127.0.0.1"); + + ASSERT_EQ(peer->Startup(8, sd, socketCount), RAKNET_STARTED); + } + + RakPeerInterface *peer; + }; +} + +// --- banList --------------------------------------------------------------- + +TEST_F(PeerLists, ExactIpIsBannedAndUnbanned) +{ + ASSERT_FALSE(peer->IsBanned("1.2.3.4")); + + peer->AddToBanList("1.2.3.4", 0); + ASSERT_TRUE(peer->IsBanned("1.2.3.4")); + ASSERT_FALSE(peer->IsBanned("1.2.3.5")) << "a ban on one address must not cover its neighbour"; + + peer->RemoveFromBanList("1.2.3.4"); + ASSERT_FALSE(peer->IsBanned("1.2.3.4")); +} + +TEST_F(PeerLists, WildcardBanCoversTheWholePrefix) +{ + peer->AddToBanList("10.0.0.*", 0); + + ASSERT_TRUE(peer->IsBanned("10.0.0.1")); + ASSERT_TRUE(peer->IsBanned("10.0.0.254")); + ASSERT_FALSE(peer->IsBanned("10.0.1.1")) << "the wildcard only covers the last octet"; +} + +// The swap-remove in RemoveFromBanList moves the last entry into the hole. Removing +// a middle entry must leave every other ban queryable: an off-by-one there would +// silently drop the entry that got swapped in. +TEST_F(PeerLists, RemovingAMiddleBanKeepsEveryOtherBan) +{ + const char *ips[] = { "1.1.1.1", "2.2.2.2", "3.3.3.3", "4.4.4.4", "5.5.5.5" }; + for (size_t i = 0; i < 5; i++) + peer->AddToBanList(ips[i], 0); + + peer->RemoveFromBanList("2.2.2.2"); + + ASSERT_FALSE(peer->IsBanned("2.2.2.2")); + ASSERT_TRUE(peer->IsBanned("1.1.1.1")); + ASSERT_TRUE(peer->IsBanned("3.3.3.3")); + ASSERT_TRUE(peer->IsBanned("4.4.4.4")); + ASSERT_TRUE(peer->IsBanned("5.5.5.5")) << "the entry swapped into the hole must survive"; +} + +TEST_F(PeerLists, RemovingTheLastBanIsNotASelfAssignmentBug) +{ + peer->AddToBanList("1.1.1.1", 0); + peer->AddToBanList("2.2.2.2", 0); + + peer->RemoveFromBanList("2.2.2.2"); // index == size-1: swap with itself, then pop + + ASSERT_FALSE(peer->IsBanned("2.2.2.2")); + ASSERT_TRUE(peer->IsBanned("1.1.1.1")); +} + +// AddToBanList must find an existing entry for the IP and update it in place. If it +// appended a second entry instead, one RemoveFromBanList (which removes the first +// match only) would leave the IP banned. +TEST_F(PeerLists, ReAddingAnIpDoesNotCreateASecondEntry) +{ + peer->AddToBanList("7.7.7.7", 0); + peer->AddToBanList("7.7.7.7", 0); + + ASSERT_TRUE(peer->IsBanned("7.7.7.7")); + + peer->RemoveFromBanList("7.7.7.7"); + ASSERT_FALSE(peer->IsBanned("7.7.7.7")) + << "a single RemoveFromBanList must clear the IP, so only one entry was ever stored"; +} + +// The in-place update also has to be able to *shorten* a ban: a permanent ban +// followed by a 1ms ban for the same IP must expire. An append would leave the +// original permanent entry in the list and the IP would stay banned forever. +TEST_F(PeerLists, ReAddingAnIpWithATimeoutReplacesAPermanentBan) +{ + peer->AddToBanList("8.8.8.8", 0); + peer->AddToBanList("8.8.8.8", 1); + + RakSleep(30); + + ASSERT_FALSE(peer->IsBanned("8.8.8.8")) + << "the 1ms timeout must have replaced the permanent ban on the same entry"; +} + +// IsBanned sweeps expired entries out as it walks, with a swap-remove that must not +// advance the cursor. Three expired entries in a row is the case that catches a +// wrongly-placed increment. +TEST_F(PeerLists, ExpiredBansAreSweptAndDoNotHideLaterEntries) +{ + peer->AddToBanList("1.1.1.1", 1); + peer->AddToBanList("2.2.2.2", 1); + peer->AddToBanList("3.3.3.3", 1); + peer->AddToBanList("9.9.9.9", 0); // permanent + + RakSleep(30); // the three temporary bans are now in the past + + ASSERT_TRUE(peer->IsBanned("9.9.9.9")) + << "the permanent ban must still be found after the expired entries are swept"; + ASSERT_FALSE(peer->IsBanned("1.1.1.1")); + ASSERT_FALSE(peer->IsBanned("2.2.2.2")); + ASSERT_FALSE(peer->IsBanned("3.3.3.3")); +} + +TEST_F(PeerLists, ClearBanListDropsEverything) +{ + peer->AddToBanList("1.1.1.1", 0); + peer->AddToBanList("2.2.2.2", 0); + peer->AddToBanList("3.3.3.*", 0); + + peer->ClearBanList(); + + ASSERT_FALSE(peer->IsBanned("1.1.1.1")); + ASSERT_FALSE(peer->IsBanned("2.2.2.2")); + ASSERT_FALSE(peer->IsBanned("3.3.3.9")); + + // And the list is usable again afterwards. + peer->AddToBanList("4.4.4.4", 0); + ASSERT_TRUE(peer->IsBanned("4.4.4.4")); +} + +TEST_F(PeerLists, MalformedBanInputIsIgnored) +{ + peer->AddToBanList(0, 0); + peer->AddToBanList("", 0); + peer->AddToBanList("1234567890123456", 0); // 16 chars, over the 15-char limit + + ASSERT_FALSE(peer->IsBanned("1234567890123456")); + ASSERT_FALSE(peer->IsBanned(0)); + ASSERT_FALSE(peer->IsBanned("")); +} + +// --- securityExceptionList ------------------------------------------------- + +TEST_F(PeerLists, SecurityExceptionMatchesExactlyAndByWildcard) +{ + ASSERT_FALSE(peer->IsInSecurityExceptionList("10.0.0.1")); + + peer->AddToSecurityExceptionList("10.0.0.1"); + peer->AddToSecurityExceptionList("192.168.1.*"); + + ASSERT_TRUE(peer->IsInSecurityExceptionList("10.0.0.1")); + ASSERT_TRUE(peer->IsInSecurityExceptionList("192.168.1.77")); + ASSERT_FALSE(peer->IsInSecurityExceptionList("192.168.2.77")); + ASSERT_FALSE(peer->IsInSecurityExceptionList("10.0.0.2")); +} + +// RemoveFromSecurityExceptionList removes *every* matching entry, and its swap-remove +// deliberately does not advance the cursor when it removes. Two adjacent duplicates +// at the end of the list is what distinguishes that from a single removal. +TEST_F(PeerLists, RemovingASecurityExceptionRemovesAllMatchingEntries) +{ + peer->AddToSecurityExceptionList("5.5.5.5"); + peer->AddToSecurityExceptionList("10.0.0.1"); + peer->AddToSecurityExceptionList("10.0.0.1"); + peer->AddToSecurityExceptionList("10.0.0.1"); + + peer->RemoveFromSecurityExceptionList("10.0.0.1"); + + ASSERT_FALSE(peer->IsInSecurityExceptionList("10.0.0.1")) + << "all three duplicate entries must be gone"; + ASSERT_TRUE(peer->IsInSecurityExceptionList("5.5.5.5")); +} + +TEST_F(PeerLists, RemovingAMiddleSecurityExceptionKeepsTheRest) +{ + peer->AddToSecurityExceptionList("1.1.1.1"); + peer->AddToSecurityExceptionList("2.2.2.2"); + peer->AddToSecurityExceptionList("3.3.3.3"); + peer->AddToSecurityExceptionList("4.4.4.4"); + + peer->RemoveFromSecurityExceptionList("2.2.2.2"); + + ASSERT_FALSE(peer->IsInSecurityExceptionList("2.2.2.2")); + ASSERT_TRUE(peer->IsInSecurityExceptionList("1.1.1.1")); + ASSERT_TRUE(peer->IsInSecurityExceptionList("3.3.3.3")); + ASSERT_TRUE(peer->IsInSecurityExceptionList("4.4.4.4")); +} + +TEST_F(PeerLists, NullIpClearsTheWholeSecurityExceptionList) +{ + peer->AddToSecurityExceptionList("1.1.1.1"); + peer->AddToSecurityExceptionList("2.2.2.2"); + + peer->RemoveFromSecurityExceptionList(0); + + ASSERT_FALSE(peer->IsInSecurityExceptionList("1.1.1.1")); + ASSERT_FALSE(peer->IsInSecurityExceptionList("2.2.2.2")); + + peer->AddToSecurityExceptionList("3.3.3.3"); + ASSERT_TRUE(peer->IsInSecurityExceptionList("3.3.3.3")) << "the list must be reusable after a clear"; +} + +// --- packetReturnQueue ----------------------------------------------------- + +namespace +{ + // Allocates a one-byte packet carrying a tag, so Receive order is checkable. + Packet *TaggedPacket(RakPeerInterface *peer, unsigned char tag) + { + Packet *p = peer->AllocatePacket(1); + p->data[0] = tag; + return p; + } +} + +TEST_F(PeerLists, PushBackPacketAtTailIsFifo) +{ + StartOnEphemeralPort(); + + peer->PushBackPacket(TaggedPacket(peer, 10), false); + peer->PushBackPacket(TaggedPacket(peer, 20), false); + peer->PushBackPacket(TaggedPacket(peer, 30), false); + + ASSERT_EQ(peer->GetReceiveBufferSize(), 3u); + + unsigned char seen[3] = { 0, 0, 0 }; + for (int i = 0; i < 3; i++) + { + Packet *p = peer->Receive(); + ASSERT_NE(p, nullptr) << "packet " << i << " was pushed but not returned"; + seen[i] = p->data[0]; + peer->DeallocatePacket(p); + } + + ASSERT_EQ(seen[0], 10); + ASSERT_EQ(seen[1], 20); + ASSERT_EQ(seen[2], 30); +} + +// pushAtHead is the whole reason the return buffer is a queue and not a stack: a +// plugin that re-pushes a packet it decided not to consume must get it back first. +TEST_F(PeerLists, PushBackPacketAtHeadJumpsTheQueue) +{ + StartOnEphemeralPort(); + + peer->PushBackPacket(TaggedPacket(peer, 10), false); + peer->PushBackPacket(TaggedPacket(peer, 20), false); + peer->PushBackPacket(TaggedPacket(peer, 99), true); + + unsigned char seen[3] = { 0, 0, 0 }; + for (int i = 0; i < 3; i++) + { + Packet *p = peer->Receive(); + ASSERT_NE(p, nullptr); + seen[i] = p->data[0]; + peer->DeallocatePacket(p); + } + + ASSERT_EQ(seen[0], 99) << "the head push must be returned before the earlier packets"; + ASSERT_EQ(seen[1], 10); + ASSERT_EQ(seen[2], 20); +} + +TEST_F(PeerLists, InterleavedHeadAndTailPushesKeepBothOrders) +{ + StartOnEphemeralPort(); + + peer->PushBackPacket(TaggedPacket(peer, 1), false); // [1] + peer->PushBackPacket(TaggedPacket(peer, 2), true); // [2,1] + peer->PushBackPacket(TaggedPacket(peer, 3), false); // [2,1,3] + peer->PushBackPacket(TaggedPacket(peer, 4), true); // [4,2,1,3] + + ASSERT_EQ(peer->GetReceiveBufferSize(), 4u); + + const unsigned char expected[4] = { 4, 2, 1, 3 }; + for (int i = 0; i < 4; i++) + { + Packet *p = peer->Receive(); + ASSERT_NE(p, nullptr) << "missing packet at position " << i; + ASSERT_EQ(p->data[0], expected[i]) << "wrong packet at position " << i; + peer->DeallocatePacket(p); + } +} + +TEST_F(PeerLists, PushBackPacketIgnoresNull) +{ + StartOnEphemeralPort(); + + peer->PushBackPacket(0, false); + peer->PushBackPacket(0, true); + + ASSERT_EQ(peer->GetReceiveBufferSize(), 0u); +} + +// --- socketList ------------------------------------------------------------ + +TEST_F(PeerLists, GetSocketsReturnsOneSocketPerDescriptorInOrder) +{ + StartOnEphemeralPort(3); + + DataStructures::List sockets; + peer->GetSockets(sockets); + + ASSERT_EQ(sockets.Size(), 3u) << "one socket per SocketDescriptor passed to Startup"; + + // Every socket is distinct and bound to a different port; the indices Startup + // handed out are the indices GetSockets reports. + for (unsigned int i = 0; i < sockets.Size(); i++) + { + ASSERT_NE(sockets[i], nullptr) << "socket " << i << " is null"; + ASSERT_EQ(sockets[i]->GetUserConnectionSocketIndex(), i) + << "socket at index " << i << " does not carry that index"; + } + + for (unsigned int i = 0; i < sockets.Size(); i++) + for (unsigned int j = i + 1; j < sockets.Size(); j++) + ASSERT_NE(sockets[i], sockets[j]) << "sockets " << i << " and " << j << " are the same object"; +} + +TEST_F(PeerLists, GetSocketsIsEmptyBeforeStartup) +{ + DataStructures::List sockets; + peer->GetSockets(sockets); + + ASSERT_EQ(sockets.Size(), 0u); +} + +// GetSockets clears the caller's list up front, so a caller reusing one list across +// calls does not accumulate stale sockets. +TEST_F(PeerLists, GetSocketsClearsTheCallersListFirst) +{ + StartOnEphemeralPort(2); + + DataStructures::List sockets; + sockets.Push(0, _FILE_AND_LINE_); + sockets.Push(0, _FILE_AND_LINE_); + sockets.Push(0, _FILE_AND_LINE_); + + peer->GetSockets(sockets); + + ASSERT_EQ(sockets.Size(), 2u) << "GetSockets must replace the caller's contents, not append to them"; + ASSERT_NE(sockets[0], nullptr); + ASSERT_NE(sockets[1], nullptr); + + // Calling it twice must be idempotent. + peer->GetSockets(sockets); + ASSERT_EQ(sockets.Size(), 2u); +} + +// GetSocket(UNASSIGNED_SYSTEM_ADDRESS) runs the same network-thread query but hands +// back only the first socket, so it exercises the copy out of +// SocketQueryOutput::sockets separately from GetSockets. +TEST_F(PeerLists, GetSocketForAnUnassignedAddressReturnsTheFirstSocket) +{ + StartOnEphemeralPort(2); + + DataStructures::List sockets; + peer->GetSockets(sockets); + ASSERT_EQ(sockets.Size(), 2u); + + RakNetSocket2 *first = peer->GetSocket(UNASSIGNED_SYSTEM_ADDRESS); + ASSERT_NE(first, nullptr); + ASSERT_EQ(first, sockets[0]) << "GetSocket must return the socket GetSockets reports at index 0"; +} diff --git a/Tests/Unit/RPC4RegistryTests.cpp b/Tests/Unit/RPC4RegistryTests.cpp new file mode 100644 index 000000000..9b119f3b7 --- /dev/null +++ b/Tests/Unit/RPC4RegistryTests.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + */ + +#include + +#include +#include + +#include "mafianet/rpc4_plugin.h" +#include "mafianet/peer_interface.h" +#include "mafianet/bit_stream.h" +#include "mafianet/message_identifiers.h" + +using namespace MafiaNet; + +/* +Description: +Covers RPC4's three registration tables -- non-blocking functions, blocking functions and +local callbacks -- written before they move to the standard library (std migration stage 2, #60). +The two tables are DataStructures::Hash keyed by function name; localCallbacks is a +DataStructures::OrderedList keyed by MessageID holding one entry per message id, each owning an +ordered list of function names. + +The existing RPC4 coverage is two cases about global-registration name handling plus one +integration test, and none of it exercises registration, unregistration, replacement or dispatch, +which is exactly what a container swap can break quietly: + + - Hash::Push on an existing key and OrderedList::Insert on an existing key behave differently + from each other (one appends a second entry for the same key, the other refuses), and both + differ from std::unordered_map::insert and operator[]. A migration that picks the wrong one + either loses a registration or silently keeps a stale function pointer. + - localCallbacks holds a sorted list of names per message id, and the dispatch loop walks it, so + both the per-id lookup and the per-id name list have to survive. + +Success conditions: register/unregister round-trips report accurately, re-registering replaces +rather than duplicates, the blocking and non-blocking tables are independent, a local callback can +carry several function names, and loopback dispatch reaches the registered function. +*/ + +namespace +{ + int g_calls = 0; + std::string g_lastPayload; + + void CountingRpc(MafiaNet::BitStream *userData, Packet *, void *) + { + ++g_calls; + g_lastPayload.clear(); + if (userData != 0) + { + char buf[64]; + memset(buf, 0, sizeof(buf)); + // Read whatever the caller wrote, bounded by the buffer. + const unsigned bytes = (unsigned) BITS_TO_BYTES(userData->GetNumberOfUnreadBits()); + if (bytes > 0 && bytes < sizeof(buf)) + { + userData->Read(buf, bytes); + g_lastPayload = buf; + } + } + } + + void OtherRpc(MafiaNet::BitStream *, Packet *, void *) {} + + void CountingBlockingRpc(MafiaNet::BitStream *, MafiaNet::BitStream *, Packet *, void *) {} + + class RPC4Registry : public ::testing::Test + { + protected: + void SetUp() override + { + g_calls = 0; + g_lastPayload.clear(); + peer = RakPeerInterface::GetInstance(); + peer->AttachPlugin(&rpc4); + // Started but unconnected: CallLoopback pushes a packet that only surfaces + // through Receive() on a started peer. Ephemeral port, no traffic. + SocketDescriptor sd(0, "127.0.0.1"); + ASSERT_EQ(peer->Startup(1, &sd, 1), RAKNET_STARTED); + } + void TearDown() override + { + peer->DetachPlugin(&rpc4); + peer->Shutdown(100); + RakPeerInterface::DestroyInstance(peer); + } + + RakPeerInterface *peer = nullptr; + RPC4 rpc4; + }; +} + +TEST_F(RPC4Registry, UnregisteringAnUnknownFunctionFails) +{ + EXPECT_FALSE(rpc4.UnregisterFunction("never.registered")); +} + +TEST_F(RPC4Registry, RegisterThenUnregisterRoundTrips) +{ + EXPECT_TRUE(rpc4.RegisterFunction("ping", CountingRpc, 0)); + EXPECT_TRUE(rpc4.UnregisterFunction("ping")); + EXPECT_FALSE(rpc4.UnregisterFunction("ping")) << "a second unregister must report nothing to remove"; +} + +TEST_F(RPC4Registry, RegisteringTheSameNameTwiceIsRefused) +{ + EXPECT_TRUE(rpc4.RegisterFunction("ping", CountingRpc, 0)); + EXPECT_FALSE(rpc4.RegisterFunction("ping", OtherRpc, 0)); + + // Exactly one entry must exist under that name, whichever function it holds. + EXPECT_TRUE(rpc4.UnregisterFunction("ping")); + EXPECT_FALSE(rpc4.UnregisterFunction("ping")) << "the refused registration must not have added a second entry"; +} + +TEST_F(RPC4Registry, SeveralDistinctNamesCoexist) +{ + ASSERT_TRUE(rpc4.RegisterFunction("a", CountingRpc, 0)); + ASSERT_TRUE(rpc4.RegisterFunction("b", CountingRpc, 0)); + ASSERT_TRUE(rpc4.RegisterFunction("c", CountingRpc, 0)); + + EXPECT_TRUE(rpc4.UnregisterFunction("b")); + EXPECT_TRUE(rpc4.UnregisterFunction("a")); + EXPECT_TRUE(rpc4.UnregisterFunction("c")); +} + +TEST_F(RPC4Registry, ManyNamesSurviveTogether) +{ + // Enough names to spread across hash buckets, so a migration that collapses + // colliding keys shows up here rather than in production. + const int count = 200; + for (int i = 0; i < count; ++i) + { + char name[32]; + snprintf(name, sizeof(name), "fn.%d", i); + ASSERT_TRUE(rpc4.RegisterFunction(name, CountingRpc, 0)) << "registering " << name; + } + for (int i = 0; i < count; ++i) + { + char name[32]; + snprintf(name, sizeof(name), "fn.%d", i); + EXPECT_TRUE(rpc4.UnregisterFunction(name)) << "unregistering " << name; + } +} + +TEST_F(RPC4Registry, BlockingAndNonBlockingTablesAreIndependent) +{ + ASSERT_TRUE(rpc4.RegisterFunction("shared.name", CountingRpc, 0)); + ASSERT_TRUE(rpc4.RegisterBlockingFunction("shared.name", CountingBlockingRpc, 0)); + + // Removing one must leave the other in place. + EXPECT_TRUE(rpc4.UnregisterFunction("shared.name")); + EXPECT_FALSE(rpc4.UnregisterFunction("shared.name")); + EXPECT_TRUE(rpc4.UnregisterBlockingFunction("shared.name")); + EXPECT_FALSE(rpc4.UnregisterBlockingFunction("shared.name")); +} + +TEST_F(RPC4Registry, BlockingRegisterThenUnregisterRoundTrips) +{ + EXPECT_FALSE(rpc4.UnregisterBlockingFunction("blocking.absent")); + EXPECT_TRUE(rpc4.RegisterBlockingFunction("blocking.present", CountingBlockingRpc, 0)); + EXPECT_TRUE(rpc4.UnregisterBlockingFunction("blocking.present")); + EXPECT_FALSE(rpc4.UnregisterBlockingFunction("blocking.present")); +} + +TEST_F(RPC4Registry, LocalCallbackRoundTripsForOneMessageId) +{ + const MessageID id = ID_USER_PACKET_ENUM + 1; + ASSERT_TRUE(rpc4.RegisterFunction("cb", CountingRpc, 0)); + rpc4.RegisterLocalCallback("cb", id); + + EXPECT_TRUE(rpc4.UnregisterLocalCallback("cb", id)); + EXPECT_FALSE(rpc4.UnregisterLocalCallback("cb", id)) + << "a second unregister must report nothing to remove"; +} + +TEST_F(RPC4Registry, OneMessageIdCarriesSeveralFunctionNames) +{ + const MessageID id = ID_USER_PACKET_ENUM + 2; + ASSERT_TRUE(rpc4.RegisterFunction("first", CountingRpc, 0)); + ASSERT_TRUE(rpc4.RegisterFunction("second", OtherRpc, 0)); + rpc4.RegisterLocalCallback("first", id); + rpc4.RegisterLocalCallback("second", id); + + // Removing one name must leave the other attached to the same message id. + EXPECT_TRUE(rpc4.UnregisterLocalCallback("first", id)); + EXPECT_TRUE(rpc4.UnregisterLocalCallback("second", id)); + EXPECT_FALSE(rpc4.UnregisterLocalCallback("second", id)); +} + +TEST_F(RPC4Registry, DistinctMessageIdsAreKeptApart) +{ + const MessageID idA = ID_USER_PACKET_ENUM + 3; + const MessageID idB = ID_USER_PACKET_ENUM + 4; + ASSERT_TRUE(rpc4.RegisterFunction("cb", CountingRpc, 0)); + rpc4.RegisterLocalCallback("cb", idA); + rpc4.RegisterLocalCallback("cb", idB); + + EXPECT_TRUE(rpc4.UnregisterLocalCallback("cb", idA)); + EXPECT_FALSE(rpc4.UnregisterLocalCallback("cb", idA)); + EXPECT_TRUE(rpc4.UnregisterLocalCallback("cb", idB)) << "the other message id must be unaffected"; +} + +TEST_F(RPC4Registry, UnregisteringALocalCallbackOnAnUnknownMessageIdFails) +{ + ASSERT_TRUE(rpc4.RegisterFunction("cb", CountingRpc, 0)); + EXPECT_FALSE(rpc4.UnregisterLocalCallback("cb", ID_USER_PACKET_ENUM + 99)); +} + +TEST_F(RPC4Registry, LoopbackReachesTheRegisteredFunction) +{ + ASSERT_TRUE(rpc4.RegisterFunction("loop", CountingRpc, 0)); + + MafiaNet::BitStream bs; + bs.Write("hi", 3); // includes the terminator + rpc4.CallLoopback("loop", &bs); + + // CallLoopback pushes a packet the plugin consumes on the next Receive. + for (int i = 0; i < 20 && g_calls == 0; ++i) + { + Packet *p = peer->Receive(); + if (p != 0) + peer->DeallocatePacket(p); + } + + EXPECT_EQ(g_calls, 1); + EXPECT_EQ(g_lastPayload, "hi"); +} + +TEST_F(RPC4Registry, LoopbackToAnUnknownNameDoesNotInvokeAnything) +{ + ASSERT_TRUE(rpc4.RegisterFunction("loop", CountingRpc, 0)); + + MafiaNet::BitStream bs; + rpc4.CallLoopback("absent", &bs); + + for (int i = 0; i < 20; ++i) + { + Packet *p = peer->Receive(); + if (p != 0) + peer->DeallocatePacket(p); + } + + EXPECT_EQ(g_calls, 0); +} + +TEST_F(RPC4Registry, SlotRegisterThenUnregisterRoundTrips) +{ + EXPECT_FALSE(rpc4.UnregisterSlot("slot.absent")); + rpc4.RegisterSlot("slot.present", CountingRpc, 0, 0); + EXPECT_TRUE(rpc4.UnregisterSlot("slot.present")); + EXPECT_FALSE(rpc4.UnregisterSlot("slot.present")); +} diff --git a/Tests/Unit/ReliabilityLayerBlackHoleTests.cpp b/Tests/Unit/ReliabilityLayerBlackHoleTests.cpp index 93a64b2da..1b1be6b6b 100644 --- a/Tests/Unit/ReliabilityLayerBlackHoleTests.cpp +++ b/Tests/Unit/ReliabilityLayerBlackHoleTests.cpp @@ -74,6 +74,10 @@ class RelLayerBlackHole : public ::testing::Test // When non-zero, drop this fraction (in percent) of ALL a->b datagrams, // deterministically, to model plain loss. int aToBLossPercent; + // When true, the datagrams a produced during one tick are delivered to b in reverse order. + // Plain reordering, which a real path does routinely, and the only way from here to make a + // later split-packet id reach b before an earlier one. + bool reverseAToBWithinTick; uint32_t lcgState; std::vector > received; // complete messages b got @@ -84,6 +88,7 @@ class RelLayerBlackHole : public ::testing::Test , now(0) , aToBDropOverBytes(MAXIMUM_MTU_SIZE) , aToBLossPercent(0) + , reverseAToBWithinTick(false) , lcgState(0x12345678) { } @@ -132,8 +137,9 @@ class RelLayerBlackHole : public ::testing::Test a.Update(&aSock, bAddr, TEST_MTU, now, 0, handlers, &rnr, updateBitStream); b.Update(&bSock, aAddr, TEST_MTU, now, 0, handlers, &rnr, updateBitStream); - for (size_t i = 0; i < aSock.sent.size(); i++) + for (size_t n = 0; n < aSock.sent.size(); n++) { + const size_t i = reverseAToBWithinTick ? aSock.sent.size() - 1 - n : n; if (DropAToB((int) aSock.sent[i].size())) continue; b.HandleSocketReceiveFromConnectedPlayer(&aSock.sent[i][0], (unsigned int) aSock.sent[i].size(), @@ -320,3 +326,156 @@ TEST_F(RelLayerBlackHole, OrdinaryPacketLossDoesNotShrinkTheMtu) } } // namespace + +// -------------------------------------------------------------------------------------------- +// The cases below are not about MTU black holes. They exist because stage 2 of #60 moves +// ReliabilityLayer's protected containers to the standard library, and two of them had no +// coverage that could tell a correct migration from a broken one: +// +// - datagramHistory is a sliding window indexed by (datagramNumber - datagramHistoryPopCount) +// in WRAPPING uint24_t arithmetic. Every existing test sends few enough datagrams that the +// pop count stays at or near zero, where an off-by-one in that arithmetic is invisible. +// - splitPacketChannelList is an ordered list keyed by split-packet id, and every existing +// test has exactly ONE split message in flight. With a single element any ordering or lookup +// passes, so the container's whole job is untested. +// -------------------------------------------------------------------------------------------- + +// Drives datagramHistory well past its initial window: hundreds of datagrams pushed, acked and +// popped, with loss forcing resends so lookups land at varying offsets into the window. +TEST_F(RelLayerBlackHole, SustainedTrafficKeepsOrderWhileDatagramHistoryAdvances) +{ + aToBLossPercent = 10; + + const int count = 300; + std::vector > sent; + for (int i = 0; i < count; i++) + { + // Small messages, so each is its own datagram and the history advances once per message. + std::vector msg = PatternMessage(40, (unsigned char) i); + sent.push_back(msg); + SendFromA(msg); + Tick(); + } + + ASSERT_TRUE(PumpUntilReceived((size_t) count, 600000)) << "only " << received.size() << " of " + << count << " messages arrived"; + ASSERT_EQ(received.size(), (size_t) count); + for (int i = 0; i < count; i++) + EXPECT_EQ(received[i], sent[i]) << "reliable-ordered delivery broke at index " << i; +} + +// Two split messages in flight at once, so splitPacketChannelList holds more than one entry and +// its keyed lookup actually decides which message a fragment belongs to. +TEST_F(RelLayerBlackHole, TwoConcurrentSplitMessagesBothReassembleCorrectly) +{ + std::vector first = PatternMessage(9000, 61); + std::vector second = PatternMessage(7000, 62); + + // Queued back to back, so their fragments interleave on the wire. + SendFromA(first); + SendFromA(second); + + ASSERT_TRUE(PumpUntilReceived(2, 150000)); + ASSERT_EQ(received.size(), 2u); + EXPECT_EQ(received[0], first) << "the first split message reassembled wrongly"; + EXPECT_EQ(received[1], second) << "the second split message reassembled wrongly"; +} + +// Same, under loss, so fragments of both messages are resent and arrive interleaved with the +// fragments of the other -- the case where a wrong split-packet lookup silently mixes payloads. +TEST_F(RelLayerBlackHole, ConcurrentSplitMessagesSurviveLossWithoutMixingPayloads) +{ + aToBLossPercent = 10; + + std::vector first = PatternMessage(9000, 71); + std::vector second = PatternMessage(9000, 72); + + SendFromA(first); + SendFromA(second); + + ASSERT_TRUE(PumpUntilReceived(2, 600000)); + ASSERT_EQ(received.size(), 2u); + EXPECT_EQ(received[0], first); + EXPECT_EQ(received[1], second); + EXPECT_NE(received[0], received[1]) << "the two messages must not have been conflated"; +} + +// Three split messages at once, to push splitPacketChannelList past two entries. +TEST_F(RelLayerBlackHole, ThreeConcurrentSplitMessagesAllReassemble) +{ + std::vector > sent; + sent.push_back(PatternMessage(6000, 81)); + sent.push_back(PatternMessage(9000, 82)); + sent.push_back(PatternMessage(12000, 83)); + + for (size_t i = 0; i < sent.size(); i++) + SendFromA(sent[i]); + + ASSERT_TRUE(PumpUntilReceived(3, 300000)); + ASSERT_EQ(received.size(), 3u); + for (size_t i = 0; i < sent.size(); i++) + EXPECT_EQ(received[i], sent[i]) << "split message " << i << " reassembled wrongly"; +} + +// The case that actually pins splitPacketChannelList's ordering. Sending several split messages +// is not enough on its own: split-packet ids are handed out in ascending order, so a channel list +// that merely appends stays sorted by accident and a broken lookup still works. Reversing each +// tick's datagrams makes the higher id's first fragment reach b first, so the list is only sorted +// if the insert actually places it. +TEST_F(RelLayerBlackHole, ConcurrentSplitMessagesReassembleWhenDatagramsAreReordered) +{ + reverseAToBWithinTick = true; + + std::vector first = PatternMessage(9000, 91); + std::vector second = PatternMessage(9000, 92); + + SendFromA(first); + SendFromA(second); + + ASSERT_TRUE(PumpUntilReceived(2, 300000)); + ASSERT_EQ(received.size(), 2u); + EXPECT_EQ(received[0], first); + EXPECT_EQ(received[1], second); +} + +TEST_F(RelLayerBlackHole, ReorderedDatagramsStillDeliverManyMessagesInOrder) +{ + reverseAToBWithinTick = true; + + const int count = 60; + std::vector > sent; + for (int i = 0; i < count; i++) + { + std::vector msg = PatternMessage(40, (unsigned char) (100 + i)); + sent.push_back(msg); + SendFromA(msg); + } + + ASSERT_TRUE(PumpUntilReceived((size_t) count, 300000)); + ASSERT_EQ(received.size(), (size_t) count); + for (int i = 0; i < count; i++) + EXPECT_EQ(received[i], sent[i]) << "ordered delivery broke at index " << i; +} + +// Eight split messages in flight with reordering, so splitPacketChannelList holds many entries +// and a fragment of an early id has to be matched against a list whose entries were created in +// a different order. +TEST_F(RelLayerBlackHole, ManyConcurrentSplitMessagesReassembleUnderReordering) +{ + reverseAToBWithinTick = true; + + const int count = 8; + std::vector > sent; + for (int i = 0; i < count; i++) + { + std::vector msg = PatternMessage(5000 + i * 700, (unsigned char) (120 + i)); + sent.push_back(msg); + SendFromA(msg); + } + + ASSERT_TRUE(PumpUntilReceived((size_t) count, 600000)) << "only " << received.size() + << " of " << count << " split messages reassembled"; + ASSERT_EQ(received.size(), (size_t) count); + for (int i = 0; i < count; i++) + EXPECT_EQ(received[i], sent[i]) << "split message " << i << " reassembled wrongly"; +} diff --git a/Tests/Unit/ReplicaManager3WorldTests.cpp b/Tests/Unit/ReplicaManager3WorldTests.cpp index 883243003..3683e2906 100644 --- a/Tests/Unit/ReplicaManager3WorldTests.cpp +++ b/Tests/Unit/ReplicaManager3WorldTests.cpp @@ -9,6 +9,11 @@ #include "mafianet/replica_manager3.h" +#include + +#include "mafianet/network_id_manager.h" +#include "mafianet/peer_interface.h" + using namespace MafiaNet; namespace @@ -45,3 +50,186 @@ TEST(ReplicaManager3Worlds, HighestWorldIdIsUsable) manager.RemoveWorld(255); EXPECT_EQ(manager.GetWorldCount(), 1u); } + +// RemoveWorld documents its own ordering: "Worlds will not necessarily be in the order added with +// AddWorld(). Edit RemoveWorld() changing RemoveAtIndexFast() to RemoveAtIndex() to preserve +// order." That swap-remove is a documented, publicly observable contract because +// GetWorldIdAtIndex exposes the index, so it has to survive the move off DataStructures::List +// (std migration stage 2, #60). A plain erase would shift instead of swapping and renumber +// every world after the removed one. +TEST(ReplicaManager3Worlds, RemoveWorldSwapsTheLastWorldIntoTheHole) +{ + WorldTestManager manager; + manager.AddWorld(1); + manager.AddWorld(2); + manager.AddWorld(3); + ASSERT_EQ(manager.GetWorldCount(), 4u); + ASSERT_EQ(manager.GetWorldIdAtIndex(0), static_cast(0)); + ASSERT_EQ(manager.GetWorldIdAtIndex(1), static_cast(1)); + ASSERT_EQ(manager.GetWorldIdAtIndex(2), static_cast(2)); + ASSERT_EQ(manager.GetWorldIdAtIndex(3), static_cast(3)); + + manager.RemoveWorld(1); // index 1, with index 3 being last + + ASSERT_EQ(manager.GetWorldCount(), 3u); + EXPECT_EQ(manager.GetWorldIdAtIndex(0), static_cast(0)); + EXPECT_EQ(manager.GetWorldIdAtIndex(1), static_cast(3)) + << "the last world must be swapped into the hole, not shifted up"; + EXPECT_EQ(manager.GetWorldIdAtIndex(2), static_cast(2)); +} + +TEST(ReplicaManager3Worlds, RemovingTheLastWorldLeavesTheRestInPlace) +{ + WorldTestManager manager; + manager.AddWorld(1); + manager.AddWorld(2); + ASSERT_EQ(manager.GetWorldCount(), 3u); + + manager.RemoveWorld(2); // already last + + ASSERT_EQ(manager.GetWorldCount(), 2u); + EXPECT_EQ(manager.GetWorldIdAtIndex(0), static_cast(0)); + EXPECT_EQ(manager.GetWorldIdAtIndex(1), static_cast(1)); +} + +TEST(ReplicaManager3Worlds, WorldsCanBeAddedAgainAfterRemoval) +{ + WorldTestManager manager; + manager.AddWorld(7); + ASSERT_EQ(manager.GetWorldCount(), 2u); + manager.RemoveWorld(7); + ASSERT_EQ(manager.GetWorldCount(), 1u); + manager.AddWorld(7); + ASSERT_EQ(manager.GetWorldCount(), 2u); + EXPECT_EQ(manager.GetWorldIdAtIndex(1), static_cast(7)); +} + +TEST(ReplicaManager3Worlds, EveryAddedWorldIsReachableByIndex) +{ + WorldTestManager manager; + const WorldId ids[] = { 5, 9, 200, 255 }; + for (int i = 0; i < 4; ++i) + manager.AddWorld(ids[i]); + ASSERT_EQ(manager.GetWorldCount(), 5u); + + // Collect what the index exposes and compare as a set: the order is explicitly not promised, + // but membership is. + std::set seen; + for (unsigned i = 0; i < manager.GetWorldCount(); ++i) + seen.insert((int) manager.GetWorldIdAtIndex(i)); + std::set want; + want.insert(0); + for (int i = 0; i < 4; ++i) + want.insert((int) ids[i]); + EXPECT_EQ(seen, want); +} + +// constructedReplicaList is looked up by binary search on Replica3::referenceIndex, so the list +// has to stay sorted. Nothing covered that: with one replica any order is trivially sorted, and a +// migration that appends instead of inserting in place passed the whole unit suite. These +// construct several replicas in an order that is NOT the reference order, then assert every one +// is still found -- which fails the moment the list stops being sorted. +namespace +{ + class LookupTestReplica : public Replica3 + { + public: + void WriteAllocationID(Connection_RM3 *, MafiaNet::BitStream *) const {} + RM3ConstructionState QueryConstruction(Connection_RM3 *, ReplicaManager3 *) { return RM3CS_NEVER_CONSTRUCT; } + bool QueryRemoteConstruction(Connection_RM3 *) { return false; } + void SerializeConstruction(MafiaNet::BitStream *, Connection_RM3 *) {} + bool DeserializeConstruction(MafiaNet::BitStream *, Connection_RM3 *) { return false; } + void SerializeDestruction(MafiaNet::BitStream *, Connection_RM3 *) {} + bool DeserializeDestruction(MafiaNet::BitStream *, Connection_RM3 *) { return false; } + RM3ActionOnPopConnection QueryActionOnPopConnection(Connection_RM3 *) const { return RM3AOPC_DO_NOTHING; } + void DeallocReplica(Connection_RM3 *) {} + RM3QuerySerializationResult QuerySerialization(Connection_RM3 *) { return RM3QSR_DO_NOT_CALL_SERIALIZE; } + RM3SerializationResult Serialize(SerializeParameters *) { return RM3SR_DO_NOT_SERIALIZE; } + void Deserialize(DeserializeParameters *) {} + }; + + // QueryConstructionMode must be QUERY_CONNECTION_FOR_REPLICA_LIST for the direct + // OnConstructToThisConnection(Replica3*) entry point. + class LookupTestConnection : public Connection_RM3 + { + public: + LookupTestConnection() : Connection_RM3(UNASSIGNED_SYSTEM_ADDRESS, RakNetGUID(3)) {} + Replica3 *AllocReplica(MafiaNet::BitStream *, ReplicaManager3 *) { return 0; } + ConstructionMode QueryConstructionMode(void) const { return QUERY_CONNECTION_FOR_REPLICA_LIST; } + + // OnConstructToThisConnection is protected; a derived class is the sanctioned way in. + void Construct(Replica3 *replica, ReplicaManager3 *manager) + { + OnConstructToThisConnection(replica, manager); + } + }; +} + +TEST(ReplicaManager3ConstructedLookup, EveryConstructedReplicaIsFoundWhateverTheConstructionOrder) +{ + // Reference() dereferences rakPeerInterface, so the manager must be attached to a peer; + // the peer is never started and no traffic occurs. + RakPeerInterface *peer = RakPeerInterface::GetInstance(); + WorldTestManager manager; + NetworkIDManager ids; + manager.SetNetworkIDManager(&ids); + peer->AttachPlugin(&manager); + LookupTestConnection connection; + + const int count = 16; + LookupTestReplica replicas[count]; + + // Reference assigns ascending referenceIndex in this order. + for (int i = 0; i < count; ++i) + { + replicas[i].SetNetworkIDManager(&ids); + replicas[i].SetNetworkID((NetworkID) (100 + i)); + manager.Reference(&replicas[i]); + } + + // Construct in an interleaved order, so insertion order differs from reference order and an + // append-only list ends up unsorted. + for (int i = 0; i < count; i += 2) + connection.Construct(&replicas[i], &manager); + for (int i = 1; i < count; i += 2) + connection.Construct(&replicas[i], &manager); + + for (int i = 0; i < count; ++i) + EXPECT_TRUE(connection.HasReplicaConstructed(&replicas[i])) + << "replica " << i << " was constructed but cannot be found"; + + for (int i = 0; i < count; ++i) + manager.Dereference(&replicas[i]); + + peer->DetachPlugin(&manager); + RakPeerInterface::DestroyInstance(peer); +} + +TEST(ReplicaManager3ConstructedLookup, AnUnconstructedReplicaIsNotReported) +{ + RakPeerInterface *peer = RakPeerInterface::GetInstance(); + WorldTestManager manager; + NetworkIDManager ids; + manager.SetNetworkIDManager(&ids); + peer->AttachPlugin(&manager); + LookupTestConnection connection; + + LookupTestReplica a, b; + a.SetNetworkIDManager(&ids); + a.SetNetworkID(201); + b.SetNetworkIDManager(&ids); + b.SetNetworkID(202); + manager.Reference(&a); + manager.Reference(&b); + + connection.Construct(&a, &manager); + + EXPECT_TRUE(connection.HasReplicaConstructed(&a)); + EXPECT_FALSE(connection.HasReplicaConstructed(&b)); + + manager.Dereference(&a); + manager.Dereference(&b); + + peer->DetachPlugin(&manager); + RakPeerInterface::DestroyInstance(peer); +} diff --git a/Tests/Unit/SignaledEventLifecycleTests.cpp b/Tests/Unit/SignaledEventLifecycleTests.cpp new file mode 100644 index 000000000..6b8e4f09d --- /dev/null +++ b/Tests/Unit/SignaledEventLifecycleTests.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + */ + +#include + +#include "mafianet/signaled_event.h" +#include "mafianet/get_time.h" + +using namespace MafiaNet; + +/* +SignaledEvent had no tests, and it had a hole: on POSIX the condition variable and +mutex are created by InitEvent, not by the constructor, while SetEvent called +pthread_cond_broadcast unconditionally. Broadcasting on an uninitialised +pthread_cond_t is undefined, and on glibc it does not fail -- it blocks forever in +__condvar_quiesce_and_switch_g1. Any caller that signalled before starting its +threads hung, with no diagnostic. + +That is reachable through a public API: ThreadPool::AddInput signals +quitAndIncomingDataEvents, and nothing requires StartThreads first. It is also +platform-specific, which is why it went unnoticed -- on Windows SetEvent on +INVALID_HANDLE_VALUE just fails, and on macOS the uninitialised broadcast happens +to return. + +The cases below cover the lifecycle out of order: signal and wait before init, +init twice, close without init, and init again after close. Each timing +assertion is deliberately loose -- they are there to turn a hang into a failure, +not to measure anything. +*/ + +namespace +{ + // A wait is "prompt" if it returns far sooner than the timeout it was given. + const unsigned int kGenerousMS = 2000; +} + +TEST(SignaledEventLifecycle, SetEventBeforeInitEventReturns) +{ + SignaledEvent e; + + const TimeMS start = GetTimeMS(); + e.SetEvent(); // hangs forever against the unguarded version + const TimeMS elapsed = GetTimeMS() - start; + + ASSERT_LT(elapsed, kGenerousMS) << "SetEvent blocked before InitEvent"; +} + +TEST(SignaledEventLifecycle, WaitOnEventBeforeInitEventReturnsPromptly) +{ + SignaledEvent e; + + const TimeMS start = GetTimeMS(); + e.WaitOnEvent(30000); + const TimeMS elapsed = GetTimeMS() - start; + + ASSERT_LT(elapsed, kGenerousMS) << "WaitOnEvent blocked with no condition variable"; +} + +// The signal still has to be recorded, so the state is not lost just because the +// condition variable did not exist yet. +TEST(SignaledEventLifecycle, ASignalRaisedBeforeInitIsStillDeliveredAfterIt) +{ + SignaledEvent e; + + e.SetEvent(); + e.InitEvent(); + + const TimeMS start = GetTimeMS(); + e.WaitOnEvent(30000); + const TimeMS elapsed = GetTimeMS() - start; + + e.CloseEvent(); + + ASSERT_LT(elapsed, kGenerousMS) << "the signal raised before InitEvent was lost"; +} + +TEST(SignaledEventLifecycle, SetThenWaitAfterInitReturnsImmediately) +{ + SignaledEvent e; + e.InitEvent(); + + e.SetEvent(); + + const TimeMS start = GetTimeMS(); + e.WaitOnEvent(30000); + const TimeMS elapsed = GetTimeMS() - start; + + e.CloseEvent(); + + ASSERT_LT(elapsed, kGenerousMS) << "a set event did not satisfy the wait"; +} + +// A second InitEvent must not re-create the condition variable: pthread_cond_init +// on an already-initialised one is undefined. +TEST(SignaledEventLifecycle, InitEventTwiceIsSafe) +{ + SignaledEvent e; + e.InitEvent(); + e.InitEvent(); + + e.SetEvent(); + + const TimeMS start = GetTimeMS(); + e.WaitOnEvent(30000); + const TimeMS elapsed = GetTimeMS() - start; + + e.CloseEvent(); + + ASSERT_LT(elapsed, kGenerousMS); +} + +TEST(SignaledEventLifecycle, CloseEventWithoutInitEventIsSafe) +{ + SignaledEvent e; + e.CloseEvent(); + e.CloseEvent(); + + // And it is still usable afterwards. + e.InitEvent(); + e.SetEvent(); + + const TimeMS start = GetTimeMS(); + e.WaitOnEvent(30000); + const TimeMS elapsed = GetTimeMS() - start; + + e.CloseEvent(); + + ASSERT_LT(elapsed, kGenerousMS); +} + +// Stop/start cycles close and re-open the event, which has to work. +TEST(SignaledEventLifecycle, InitEventAfterCloseEventWorksAgain) +{ + SignaledEvent e; + + e.InitEvent(); + e.SetEvent(); + e.WaitOnEvent(1000); + e.CloseEvent(); + + e.InitEvent(); + e.SetEvent(); + + const TimeMS start = GetTimeMS(); + e.WaitOnEvent(30000); + const TimeMS elapsed = GetTimeMS() - start; + + e.CloseEvent(); + + ASSERT_LT(elapsed, kGenerousMS) << "the event did not work after being re-initialised"; +} + +// Signalling after a close must be a no-op rather than a broadcast on a destroyed +// condition variable. +TEST(SignaledEventLifecycle, SetEventAfterCloseEventReturns) +{ + SignaledEvent e; + e.InitEvent(); + e.CloseEvent(); + + const TimeMS start = GetTimeMS(); + e.SetEvent(); + e.WaitOnEvent(30000); + const TimeMS elapsed = GetTimeMS() - start; + + ASSERT_LT(elapsed, kGenerousMS) << "SetEvent or WaitOnEvent used a destroyed condition variable"; +} diff --git a/Tests/Unit/StatisticsHistoryStoreTests.cpp b/Tests/Unit/StatisticsHistoryStoreTests.cpp new file mode 100644 index 000000000..96b4550cc --- /dev/null +++ b/Tests/Unit/StatisticsHistoryStoreTests.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + */ + +#include + +#include +#include +#include + +#include "mafianet/statistics_history.h" + +using namespace MafiaNet; + +/* +Description: +Covers StatisticsHistory's two container-backed stores -- the tracked-object set and the per-object +map of key -> sample queue -- written before those containers move to the standard library +(std migration stage 2, #60). The existing StatisticsHistorySampling suite only covers the plugin's +sampling clock and touches neither store. + +The load-bearing property is ORDER. `objects` is a DataStructures::OrderedList keyed by objectId, +and three public methods expose its index directly: GetObjectIndex, GetObjectAtIndex and +RemoveObjectAtIndex. So "index" means "position in ascending objectId order", not insertion order, +and that is a public contract a replacement container must keep -- a std::map would still iterate +ascending but would not give the O(1) indexed access these signatures imply, and an unsorted vector +would pass a naive round-trip test while silently renumbering every index. + +Success conditions: indices follow ascending objectId whatever the insertion order, removal +renumbers the remainder, per-object key lookup round-trips, and the unique key list is a set union +across objects. +*/ + +namespace +{ + const uint64_t kIdA = 700; + const uint64_t kIdB = 100; + const uint64_t kIdC = 400; + + class StatisticsHistoryStore : public ::testing::Test + { + protected: + StatisticsHistory history; + + // Insert deliberately out of order, so any test that passes is not passing by accident + // of insertion order matching sorted order. + void AddThreeOutOfOrder(void) + { + ASSERT_TRUE(history.AddObject(StatisticsHistory::TrackedObjectData(kIdA, 0, (void *) 0xA))); + ASSERT_TRUE(history.AddObject(StatisticsHistory::TrackedObjectData(kIdB, 0, (void *) 0xB))); + ASSERT_TRUE(history.AddObject(StatisticsHistory::TrackedObjectData(kIdC, 0, (void *) 0xC))); + } + + std::vector IdsInIndexOrder(void) const + { + std::vector out; + for (unsigned i = 0; i < history.GetObjectCount(); ++i) + { + StatisticsHistory::TrackedObjectData *tod = history.GetObjectAtIndex(i); + if (tod == 0) + break; + out.push_back(tod->objectId); + } + return out; + } + }; +} + +TEST_F(StatisticsHistoryStore, StartsEmpty) +{ + EXPECT_EQ(history.GetObjectCount(), 0u); +} + +TEST_F(StatisticsHistoryStore, IndexOrderIsAscendingObjectIdNotInsertionOrder) +{ + AddThreeOutOfOrder(); + ASSERT_EQ(history.GetObjectCount(), 3u); + + std::vector ids = IdsInIndexOrder(); + ASSERT_EQ(ids.size(), 3u); + EXPECT_EQ(ids[0], kIdB) << "index 0 must be the lowest objectId, not the first inserted"; + EXPECT_EQ(ids[1], kIdC); + EXPECT_EQ(ids[2], kIdA); +} + +TEST_F(StatisticsHistoryStore, GetObjectIndexAgreesWithGetObjectAtIndex) +{ + AddThreeOutOfOrder(); + for (unsigned i = 0; i < history.GetObjectCount(); ++i) + { + StatisticsHistory::TrackedObjectData *tod = history.GetObjectAtIndex(i); + ASSERT_NE(tod, (StatisticsHistory::TrackedObjectData *) 0); + EXPECT_EQ(history.GetObjectIndex(tod->objectId), i) + << "GetObjectIndex disagrees with GetObjectAtIndex at " << i; + } +} + +TEST_F(StatisticsHistoryStore, UserDataSurvivesTheRoundTrip) +{ + AddThreeOutOfOrder(); + unsigned idx = history.GetObjectIndex(kIdC); + StatisticsHistory::TrackedObjectData *tod = history.GetObjectAtIndex(idx); + ASSERT_NE(tod, (StatisticsHistory::TrackedObjectData *) 0); + EXPECT_EQ(tod->userData, (void *) 0xC); +} + +TEST_F(StatisticsHistoryStore, AddingADuplicateObjectIdIsRefused) +{ + ASSERT_TRUE(history.AddObject(StatisticsHistory::TrackedObjectData(kIdA, 0, (void *) 0xA))); + EXPECT_FALSE(history.AddObject(StatisticsHistory::TrackedObjectData(kIdA, 0, (void *) 0xF))); + EXPECT_EQ(history.GetObjectCount(), 1u); +} + +TEST_F(StatisticsHistoryStore, RemoveObjectHandsBackTheUserDataAndRenumbersTheRest) +{ + AddThreeOutOfOrder(); + + void *userData = 0; + ASSERT_TRUE(history.RemoveObject(kIdB, &userData)); + EXPECT_EQ(userData, (void *) 0xB); + ASSERT_EQ(history.GetObjectCount(), 2u); + + std::vector ids = IdsInIndexOrder(); + ASSERT_EQ(ids.size(), 2u); + EXPECT_EQ(ids[0], kIdC) << "removing the lowest id must shift the remainder down"; + EXPECT_EQ(ids[1], kIdA); +} + +TEST_F(StatisticsHistoryStore, RemovingAnAbsentObjectIdFails) +{ + AddThreeOutOfOrder(); + void *userData = 0; + EXPECT_FALSE(history.RemoveObject(999, &userData)); + EXPECT_EQ(history.GetObjectCount(), 3u); +} + +TEST_F(StatisticsHistoryStore, RemoveObjectAtIndexRemovesThatPositionInSortedOrder) +{ + AddThreeOutOfOrder(); + history.RemoveObjectAtIndex(1); // kIdC, the middle id + ASSERT_EQ(history.GetObjectCount(), 2u); + + std::vector ids = IdsInIndexOrder(); + ASSERT_EQ(ids.size(), 2u); + EXPECT_EQ(ids[0], kIdB); + EXPECT_EQ(ids[1], kIdA); +} + +TEST_F(StatisticsHistoryStore, ClearDropsEveryObject) +{ + AddThreeOutOfOrder(); + history.Clear(); + EXPECT_EQ(history.GetObjectCount(), 0u); +} + +TEST_F(StatisticsHistoryStore, ValuesRoundTripThroughTheKeyedStore) +{ + ASSERT_TRUE(history.AddObject(StatisticsHistory::TrackedObjectData(kIdA, 0, 0))); + + ASSERT_TRUE(history.AddValueByObjectID(kIdA, "ping", 50.0, 1000, false)); + ASSERT_TRUE(history.AddValueByObjectID(kIdA, "ping", 70.0, 2000, false)); + + StatisticsHistory::TimeAndValueQueue *q = 0; + ASSERT_EQ(history.GetHistoryForKey(kIdA, "ping", &q, 3000), StatisticsHistory::SH_OK); + ASSERT_NE(q, (StatisticsHistory::TimeAndValueQueue *) 0); + EXPECT_EQ(q->values.Size(), 2u); +} + +TEST_F(StatisticsHistoryStore, DistinctKeysOnOneObjectAreStoredSeparately) +{ + ASSERT_TRUE(history.AddObject(StatisticsHistory::TrackedObjectData(kIdA, 0, 0))); + ASSERT_TRUE(history.AddValueByObjectID(kIdA, "ping", 50.0, 1000, false)); + ASSERT_TRUE(history.AddValueByObjectID(kIdA, "loss", 1.0, 1000, false)); + + StatisticsHistory::TimeAndValueQueue *ping = 0; + StatisticsHistory::TimeAndValueQueue *loss = 0; + ASSERT_EQ(history.GetHistoryForKey(kIdA, "ping", &ping, 2000), StatisticsHistory::SH_OK); + ASSERT_EQ(history.GetHistoryForKey(kIdA, "loss", &loss, 2000), StatisticsHistory::SH_OK); + ASSERT_NE(ping, (StatisticsHistory::TimeAndValueQueue *) 0); + ASSERT_NE(loss, (StatisticsHistory::TimeAndValueQueue *) 0); + EXPECT_NE(ping, loss) << "two keys on one object must not share a queue"; +} + +TEST_F(StatisticsHistoryStore, AnUnknownKeyIsReportedRatherThanInvented) +{ + ASSERT_TRUE(history.AddObject(StatisticsHistory::TrackedObjectData(kIdA, 0, 0))); + StatisticsHistory::TimeAndValueQueue *q = 0; + EXPECT_NE(history.GetHistoryForKey(kIdA, "absent", &q, 1000), StatisticsHistory::SH_OK); +} + +TEST_F(StatisticsHistoryStore, AnUnknownObjectIsReportedRatherThanInvented) +{ + StatisticsHistory::TimeAndValueQueue *q = 0; + EXPECT_NE(history.GetHistoryForKey(12345, "ping", &q, 1000), StatisticsHistory::SH_OK); +} + +TEST_F(StatisticsHistoryStore, UniqueKeyListIsTheUnionAcrossObjects) +{ + ASSERT_TRUE(history.AddObject(StatisticsHistory::TrackedObjectData(kIdA, 0, 0))); + ASSERT_TRUE(history.AddObject(StatisticsHistory::TrackedObjectData(kIdB, 0, 0))); + + ASSERT_TRUE(history.AddValueByObjectID(kIdA, "ping", 50.0, 1000, false)); + ASSERT_TRUE(history.AddValueByObjectID(kIdA, "loss", 1.0, 1000, false)); + ASSERT_TRUE(history.AddValueByObjectID(kIdB, "ping", 60.0, 1000, false)); // duplicate key + ASSERT_TRUE(history.AddValueByObjectID(kIdB, "jitter", 2.0, 1000, false)); + + DataStructures::List keys; + history.GetUniqueKeyList(keys); + + // The list is a set union; its order is not part of the contract, so compare as a set. + std::set got; + for (unsigned i = 0; i < keys.Size(); ++i) + got.insert(keys[i].C_String()); + + std::set want; + want.insert("ping"); + want.insert("loss"); + want.insert("jitter"); + EXPECT_EQ(got, want); + EXPECT_EQ(keys.Size(), 3u) << "a key present on two objects must appear once"; +} + +TEST_F(StatisticsHistoryStore, HistorySortedReturnsOneEntryPerKeyOnTheObject) +{ + ASSERT_TRUE(history.AddObject(StatisticsHistory::TrackedObjectData(kIdA, 0, 0))); + ASSERT_TRUE(history.AddValueByObjectID(kIdA, "ping", 50.0, 1000, false)); + ASSERT_TRUE(history.AddValueByObjectID(kIdA, "loss", 1.0, 1000, false)); + + DataStructures::List values; + ASSERT_TRUE(history.GetHistorySorted(kIdA, StatisticsHistory::SH_SORT_BY_RECENT_SUM_ASCENDING, values)); + EXPECT_EQ(values.Size(), 2u); +} + +TEST_F(StatisticsHistoryStore, HistorySortedOnAnUnknownObjectFails) +{ + DataStructures::List values; + EXPECT_FALSE(history.GetHistorySorted(999, StatisticsHistory::SH_SORT_BY_RECENT_SUM_ASCENDING, values)); +} diff --git a/Tests/Unit/TCPInterfacePluginTests.cpp b/Tests/Unit/TCPInterfacePluginTests.cpp new file mode 100644 index 000000000..0d74242a2 --- /dev/null +++ b/Tests/Unit/TCPInterfacePluginTests.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + */ + +#include + +#include "mafianet/tcp_interface.h" +#include "mafianet/plugin_interface2.h" + +#if _RAKNET_SUPPORT_TCPInterface == 1 + +using namespace MafiaNet; + +/* +Covers TCPInterface's plugin list, which has no tests. + +The reason these exist: DetachPlugin looks the plugin up and acts on the result, +and the "not found" case is the one that goes wrong quietly. DataStructures::List +::GetIndexOf returned (unsigned)-1 when absent; std::find returns end(), and the +index form of that is size(), not -1. A detach of a plugin that was never attached +then indexes one past the end and swap-removes a plugin that is still attached -- +with no size mismatch and no error to notice. So the cases below detach plugins +that are not in the list, and detach the same plugin twice. + +Hermetic: Start(0, 0) creates no listen socket, and the plugin list is touched +only by the calling thread. +*/ + +namespace +{ + class CountingPlugin : public PluginInterface2 + { + public: + CountingPlugin() : attachCount(0), detachCount(0) {} + + void OnAttach(void) override { attachCount++; } + void OnDetach(void) override { detachCount++; } + + int attachCount; + int detachCount; + }; + + class TCPInterfacePlugins : public ::testing::Test + { + protected: + void SetUp() override { ASSERT_TRUE(tcp.Start(0, 0)); } + void TearDown() override { tcp.Stop(); } + + TCPInterface tcp; + }; +} + +TEST_F(TCPInterfacePlugins, AttachThenDetachCallsEachHookOnce) +{ + CountingPlugin a; + + tcp.AttachPlugin(&a); + ASSERT_EQ(a.attachCount, 1); + ASSERT_EQ(a.detachCount, 0); + + tcp.DetachPlugin(&a); + ASSERT_EQ(a.attachCount, 1); + ASSERT_EQ(a.detachCount, 1); +} + +TEST_F(TCPInterfacePlugins, AttachingTheSamePluginTwiceAttachesItOnce) +{ + CountingPlugin a; + + tcp.AttachPlugin(&a); + tcp.AttachPlugin(&a); + + ASSERT_EQ(a.attachCount, 1) << "the second attach must be ignored"; + + // And one detach is enough to remove it. + tcp.DetachPlugin(&a); + ASSERT_EQ(a.detachCount, 1); + + tcp.DetachPlugin(&a); + ASSERT_EQ(a.detachCount, 1) << "the second detach must be a no-op"; +} + +// The case the index arithmetic gets wrong: detaching a plugin that was never +// attached must not touch the plugin that was. +TEST_F(TCPInterfacePlugins, DetachingAnUnattachedPluginLeavesTheAttachedOneAlone) +{ + CountingPlugin attached; + CountingPlugin never; + + tcp.AttachPlugin(&attached); + + tcp.DetachPlugin(&never); + + ASSERT_EQ(never.detachCount, 0) << "a plugin that was never attached must not be detached"; + ASSERT_EQ(attached.detachCount, 0) << "the attached plugin must not have been detached"; + + // If the previous call had swap-removed the attached plugin, this detach would + // find nothing to do. + tcp.DetachPlugin(&attached); + ASSERT_EQ(attached.detachCount, 1) << "the attached plugin was no longer in the list"; +} + +TEST_F(TCPInterfacePlugins, DetachingFromAnEmptyListIsANoOp) +{ + CountingPlugin never; + + tcp.DetachPlugin(&never); + + ASSERT_EQ(never.attachCount, 0); + ASSERT_EQ(never.detachCount, 0); +} + +TEST_F(TCPInterfacePlugins, DetachingTheMiddleOfThreeKeepsTheOtherTwo) +{ + CountingPlugin a, b, c; + + tcp.AttachPlugin(&a); + tcp.AttachPlugin(&b); + tcp.AttachPlugin(&c); + + tcp.DetachPlugin(&b); + + ASSERT_EQ(b.detachCount, 1); + ASSERT_EQ(a.detachCount, 0); + ASSERT_EQ(c.detachCount, 0); + + // Both survivors must still be reachable -- the swap-remove moves c into b's + // slot, and losing track of it here is the classic off-by-one. + tcp.DetachPlugin(&a); + tcp.DetachPlugin(&c); + ASSERT_EQ(a.detachCount, 1) << "a was lost from the list"; + ASSERT_EQ(c.detachCount, 1) << "c was lost when it was swapped into b's slot"; +} + +TEST_F(TCPInterfacePlugins, DetachingNullIsIgnored) +{ + CountingPlugin a; + tcp.AttachPlugin(&a); + + tcp.DetachPlugin(0); + + ASSERT_EQ(a.detachCount, 0); + + tcp.DetachPlugin(&a); + ASSERT_EQ(a.detachCount, 1); +} + +#endif // _RAKNET_SUPPORT_TCPInterface diff --git a/Tests/Unit/TCPInterfacePushbackTests.cpp b/Tests/Unit/TCPInterfacePushbackTests.cpp new file mode 100644 index 000000000..35eeb16ac --- /dev/null +++ b/Tests/Unit/TCPInterfacePushbackTests.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + */ + +#include + +#include + +#include "mafianet/tcp_interface.h" + +#if _RAKNET_SUPPORT_TCPInterface == 1 + +using namespace MafiaNet; + +/* +Description: +Covers TCPInterface's two pushback queues before they move off DataStructures::Queue +(std migration stage 2, #60). TCPInterface had no tests. + +PushBackPacket(packet, pushAtHead) routes into one of two queues and Receive() drains headPush +before tailPush, so the pair decides the order a caller sees packets it handed back. Both the +queues and Receive() are reachable without starting the interface, so no socket is involved. + +The queues are FIFO within themselves: DataStructures::Queue::Push appends at the tail and Pop +takes from the head. A migration to std::vector with push_back/pop_back would reverse each queue +while still draining head before tail, and would pass any test that only pushed one packet per +queue -- so the cases below push several. +*/ + +namespace +{ + class TCPInterfacePushback : public ::testing::Test + { + protected: + void SetUp() override + { + // ReceiveInt() returns nothing until the interface is started. With + // maxIncomingConnections of 0, Start() creates no listen socket -- it only brings up + // the update thread -- so there is no bind, no port and no traffic. That thread feeds + // incomingMessages; headPush and tailPush are touched only by the calling thread, so + // the assertions below stay synchronous. + ASSERT_TRUE(tcp.Start(0, 0)); + } + void TearDown() override + { + // Drain anything a failed assertion left behind, so no packet leaks. + Packet *p; + while ((p = tcp.Receive()) != 0) + tcp.DeallocatePacket(p); + tcp.Stop(); + } + + // A packet carrying one identifying byte. + Packet *Make(unsigned char tag) + { + Packet *p = tcp.AllocatePacket(1); + p->data[0] = tag; + return p; + } + + std::vector DrainTags(void) + { + std::vector out; + Packet *p; + while ((p = tcp.Receive()) != 0) + { + out.push_back(p->data[0]); + tcp.DeallocatePacket(p); + } + return out; + } + + TCPInterface tcp; + }; +} + +TEST_F(TCPInterfacePushback, NothingPushedReceivesNothing) +{ + EXPECT_FALSE(tcp.ReceiveHasPackets()); + EXPECT_EQ(tcp.Receive(), (Packet *) 0); +} + +TEST_F(TCPInterfacePushback, APushedPacketComesBack) +{ + tcp.PushBackPacket(Make(7), false); + EXPECT_TRUE(tcp.ReceiveHasPackets()); + + std::vector tags = DrainTags(); + ASSERT_EQ(tags.size(), 1u); + EXPECT_EQ(tags[0], 7); +} + +TEST_F(TCPInterfacePushback, TailPushedPacketsKeepTheirOrder) +{ + tcp.PushBackPacket(Make(1), false); + tcp.PushBackPacket(Make(2), false); + tcp.PushBackPacket(Make(3), false); + + std::vector tags = DrainTags(); + ASSERT_EQ(tags.size(), 3u); + EXPECT_EQ(tags[0], 1) << "the tail queue is FIFO, not a stack"; + EXPECT_EQ(tags[1], 2); + EXPECT_EQ(tags[2], 3); +} + +TEST_F(TCPInterfacePushback, HeadPushedPacketsKeepTheirOrderAmongThemselves) +{ + tcp.PushBackPacket(Make(1), true); + tcp.PushBackPacket(Make(2), true); + tcp.PushBackPacket(Make(3), true); + + std::vector tags = DrainTags(); + ASSERT_EQ(tags.size(), 3u); + EXPECT_EQ(tags[0], 1) << "the head queue is FIFO within itself, not a stack"; + EXPECT_EQ(tags[1], 2); + EXPECT_EQ(tags[2], 3); +} + +TEST_F(TCPInterfacePushback, HeadPushedPacketsArriveBeforeTailPushedOnes) +{ + tcp.PushBackPacket(Make(10), false); // tail + tcp.PushBackPacket(Make(11), false); // tail + tcp.PushBackPacket(Make(20), true); // head + tcp.PushBackPacket(Make(21), true); // head + + std::vector tags = DrainTags(); + ASSERT_EQ(tags.size(), 4u); + EXPECT_EQ(tags[0], 20) << "head-pushed packets must be drained first"; + EXPECT_EQ(tags[1], 21); + EXPECT_EQ(tags[2], 10); + EXPECT_EQ(tags[3], 11); +} + +TEST_F(TCPInterfacePushback, ReceiveHasPacketsTracksBothQueues) +{ + EXPECT_FALSE(tcp.ReceiveHasPackets()); + tcp.PushBackPacket(Make(1), true); + EXPECT_TRUE(tcp.ReceiveHasPackets()); + Packet *p = tcp.Receive(); + ASSERT_NE(p, (Packet *) 0); + tcp.DeallocatePacket(p); + EXPECT_FALSE(tcp.ReceiveHasPackets()); + + tcp.PushBackPacket(Make(2), false); + EXPECT_TRUE(tcp.ReceiveHasPackets()); + p = tcp.Receive(); + ASSERT_NE(p, (Packet *) 0); + tcp.DeallocatePacket(p); + EXPECT_FALSE(tcp.ReceiveHasPackets()); +} + +TEST_F(TCPInterfacePushback, ManyPacketsSurviveInOrder) +{ + const int count = 50; + for (int i = 0; i < count; ++i) + tcp.PushBackPacket(Make((unsigned char) i), false); + + std::vector tags = DrainTags(); + ASSERT_EQ(tags.size(), (size_t) count); + for (int i = 0; i < count; ++i) + EXPECT_EQ(tags[i], (unsigned char) i) << "at position " << i; +} + +#endif // _RAKNET_SUPPORT_TCPInterface diff --git a/Tests/Unit/TeamBalancerMembershipTests.cpp b/Tests/Unit/TeamBalancerMembershipTests.cpp new file mode 100644 index 000000000..4ca366684 --- /dev/null +++ b/Tests/Unit/TeamBalancerMembershipTests.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + */ + +#include + +#include "mafianet/team_balancer.h" +#include "mafianet/peer_interface.h" + +using namespace MafiaNet; + +/* +Description: +Covers the client-side membership list TeamBalancer keeps (myTeamMembers), written before it moves +off DataStructures::List (std migration stage 2, #60). TeamBalancer had no tests. + +What is and is not pinned here, stated plainly: + + - RequestSpecificTeam / RequestAnyTeam / CancelRequestSpecificTeam / DeleteMember / GetMyTeam all + read and write myTeamMembers by NetworkID, and that behaviour is covered below. + - DeleteMember uses RemoveAtIndexFast on myTeamMembers, but no public method exposes the index of + that list and every lookup is by NetworkID, so swap-remove and shift-down are indistinguishable + through the public API. The migration reproduces the swap anyway -- it is the cheaper operation + and keeps the behaviour identical -- but no test here can tell the two apart, and this comment + exists so nobody assumes otherwise. + - The host-side teamMembers list, whose order does decide which member is moved when teams are + rebalanced, is driven by received packets and is not reachable from a hermetic test. + +These calls send to hostGuid, which is UNASSIGNED_RAKNET_GUID on an unconnected peer; the send is +a no-op and the local list is still updated, which is what is under test. +*/ + +namespace +{ + class TeamBalancerMembership : public ::testing::Test + { + protected: + void SetUp() override + { + peer = RakPeerInterface::GetInstance(); + peer->AttachPlugin(&balancer); + } + void TearDown() override + { + peer->DetachPlugin(&balancer); + RakPeerInterface::DestroyInstance(peer); + } + + RakPeerInterface *peer = nullptr; + TeamBalancer balancer; + }; + + const NetworkID kMemberA = 501; + const NetworkID kMemberB = 502; + const NetworkID kMemberC = 503; +} + +TEST_F(TeamBalancerMembership, AnUnknownMemberHasNoTeam) +{ + EXPECT_EQ(balancer.GetMyTeam(kMemberA), UNASSIGNED_TEAM_ID); +} + +// A requested team is not an assigned team: currentTeam only changes when the host answers. +TEST_F(TeamBalancerMembership, RequestingATeamRecordsTheMemberWithoutAssigningIt) +{ + balancer.RequestSpecificTeam(kMemberA, 3); + EXPECT_EQ(balancer.GetMyTeam(kMemberA), UNASSIGNED_TEAM_ID) + << "the team is requested, not yet granted by the host"; +} + +TEST_F(TeamBalancerMembership, RequestingTwiceDoesNotCreateASecondEntry) +{ + balancer.RequestSpecificTeam(kMemberA, 3); + balancer.RequestSpecificTeam(kMemberA, 4); + + // If a duplicate entry had been created, deleting once would leave the other behind and + // GetMyTeam would still resolve the member. + balancer.DeleteMember(kMemberA); + EXPECT_EQ(balancer.GetMyTeam(kMemberA), UNASSIGNED_TEAM_ID); +} + +TEST_F(TeamBalancerMembership, SeveralMembersAreTrackedIndependently) +{ + balancer.RequestSpecificTeam(kMemberA, 1); + balancer.RequestSpecificTeam(kMemberB, 2); + balancer.RequestSpecificTeam(kMemberC, 3); + + // None are assigned yet, but all three must be known: deleting the middle one must not + // disturb the others, which is the property a container swap can break. + balancer.DeleteMember(kMemberB); + + EXPECT_EQ(balancer.GetMyTeam(kMemberA), UNASSIGNED_TEAM_ID); + EXPECT_EQ(balancer.GetMyTeam(kMemberB), UNASSIGNED_TEAM_ID); + EXPECT_EQ(balancer.GetMyTeam(kMemberC), UNASSIGNED_TEAM_ID); +} + +TEST_F(TeamBalancerMembership, CancellingARequestKeepsTheMemberKnown) +{ + balancer.RequestSpecificTeam(kMemberA, 3); + balancer.CancelRequestSpecificTeam(kMemberA); + EXPECT_EQ(balancer.GetMyTeam(kMemberA), UNASSIGNED_TEAM_ID); +} + +TEST_F(TeamBalancerMembership, RequestAnyTeamRecordsTheMember) +{ + balancer.RequestAnyTeam(kMemberA); + EXPECT_EQ(balancer.GetMyTeam(kMemberA), UNASSIGNED_TEAM_ID); +} + +TEST_F(TeamBalancerMembership, DeletingAnUnknownMemberIsHarmless) +{ + balancer.RequestSpecificTeam(kMemberA, 1); + balancer.DeleteMember(kMemberC); // never added + EXPECT_EQ(balancer.GetMyTeam(kMemberA), UNASSIGNED_TEAM_ID); +} + +TEST_F(TeamBalancerMembership, ManyMembersSurviveTogether) +{ + const int count = 64; + for (int i = 0; i < count; ++i) + balancer.RequestSpecificTeam((NetworkID) (1000 + i), (TeamId) (i % 4)); + + // Delete every other one; the rest must remain resolvable. With DeleteMember swapping the + // last entry into the hole, this exercises the swap path repeatedly. + for (int i = 0; i < count; i += 2) + balancer.DeleteMember((NetworkID) (1000 + i)); + + for (int i = 0; i < count; ++i) + EXPECT_EQ(balancer.GetMyTeam((NetworkID) (1000 + i)), UNASSIGNED_TEAM_ID); +} + +TEST_F(TeamBalancerMembership, TeamSizeLimitsAreAccepted) +{ + // Exercises teamLimits, which grows to cover the highest team id set. + balancer.SetTeamSizeLimit(0, 4); + balancer.SetTeamSizeLimit(5, 2); + balancer.SetForceEvenTeams(true); + balancer.SetLockTeams(false); + SUCCEED() << "no crash growing teamLimits past the requested team ids"; +} diff --git a/Tests/Unit/TeamManagerRegistryTests.cpp b/Tests/Unit/TeamManagerRegistryTests.cpp new file mode 100644 index 000000000..957b89237 --- /dev/null +++ b/Tests/Unit/TeamManagerRegistryTests.cpp @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + */ + +#include + +#include +#include + +#include "mafianet/team_manager.h" +#include "mafianet/peer_interface.h" + +using namespace MafiaNet; + +/* +Description: +Covers the registries TeamManager keeps -- the world list, and per world the team list, team-member +list, participant list and the two NetworkID hash tables -- written before they move to the +standard library (std migration stage 2, #60). The subsystem had no tests at all. + +Two orderings here are publicly observable and differ from each other, which is what makes a +mechanical container swap risky: + + - TM_World::GetTeamByIndex / GetTeamIndex and the team-member equivalents publish the index of + order-PRESERVING lists: DereferenceTeam and DereferenceTeamMember use RemoveAtIndex, so + removing one shifts the rest down. + - TeamManager::GetWorldAtIndex publishes the index of an order-NOT-preserving list: RemoveWorld + uses RemoveAtIndexFast, which swaps the last world into the hole. + +A migration that used one idiom for both would quietly renumber half of these, and the NetworkID +lookups (teamsHash, teamMembersHash) would still answer correctly, so nothing else would notice. + +Success conditions: index order is ascending-by-insertion for teams and members and survives a +removal in the middle; world removal swaps rather than shifts; NetworkID lookups resolve to the +registered object and report absence otherwise; participants round-trip. +*/ + +namespace +{ + class TestTeam : public TM_Team + { + }; + + class TestTeamMember : public TM_TeamMember + { + }; + + class TeamManagerRegistry : public ::testing::Test + { + protected: + void SetUp() override + { + peer = RakPeerInterface::GetInstance(); + peer->AttachPlugin(&manager); + world = manager.AddWorld(0); + ASSERT_NE(world, (TM_World *) 0); + } + void TearDown() override + { + peer->DetachPlugin(&manager); + RakPeerInterface::DestroyInstance(peer); + } + + std::vector TeamIdsInIndexOrder(void) const + { + std::vector out; + for (unsigned i = 0; i < world->GetTeamCount(); ++i) + { + TM_Team *t = world->GetTeamByIndex(i); + if (t == 0) + break; + out.push_back(t->GetNetworkID()); + } + return out; + } + + RakPeerInterface *peer = nullptr; + TeamManager manager; + TM_World *world = nullptr; + }; +} + +TEST_F(TeamManagerRegistry, AWorldStartsEmpty) +{ + EXPECT_EQ(world->GetTeamCount(), 0u); + EXPECT_EQ(world->GetTeamMemberCount(), 0u); +} + +TEST_F(TeamManagerRegistry, TeamsAreIndexedInRegistrationOrder) +{ + TestTeam a, b, c; + world->ReferenceTeam(&a, 10, false); + world->ReferenceTeam(&b, 20, false); + world->ReferenceTeam(&c, 30, false); + + ASSERT_EQ(world->GetTeamCount(), 3u); + std::vector order = TeamIdsInIndexOrder(); + ASSERT_EQ(order.size(), 3u); + EXPECT_EQ(order[0], static_cast(10)); + EXPECT_EQ(order[1], static_cast(20)); + EXPECT_EQ(order[2], static_cast(30)); + + EXPECT_EQ(world->GetTeamIndex(&a), 0u); + EXPECT_EQ(world->GetTeamIndex(&b), 1u); + EXPECT_EQ(world->GetTeamIndex(&c), 2u); +} + +// DereferenceTeam uses RemoveAtIndex, so the list shifts; it must NOT swap the last into the hole. +TEST_F(TeamManagerRegistry, DereferencingATeamShiftsTheRestDown) +{ + TestTeam a, b, c; + world->ReferenceTeam(&a, 10, false); + world->ReferenceTeam(&b, 20, false); + world->ReferenceTeam(&c, 30, false); + + world->DereferenceTeam(&a, 0); // index 0, with index 2 being last + + ASSERT_EQ(world->GetTeamCount(), 2u); + std::vector order = TeamIdsInIndexOrder(); + ASSERT_EQ(order.size(), 2u); + EXPECT_EQ(order[0], static_cast(20)) << "the remainder must shift down, not be swapped"; + EXPECT_EQ(order[1], static_cast(30)); + EXPECT_EQ(world->GetTeamIndex(&b), 0u); + EXPECT_EQ(world->GetTeamIndex(&c), 1u); +} + +TEST_F(TeamManagerRegistry, TeamsAreFoundByNetworkId) +{ + TestTeam a, b; + world->ReferenceTeam(&a, 10, false); + world->ReferenceTeam(&b, 20, false); + + EXPECT_EQ(world->GetTeamByNetworkID(10), &a); + EXPECT_EQ(world->GetTeamByNetworkID(20), &b); + EXPECT_EQ(world->GetTeamByNetworkID(999), (TM_Team *) 0); +} + +TEST_F(TeamManagerRegistry, ADereferencedTeamIsNoLongerFoundByNetworkId) +{ + TestTeam a; + world->ReferenceTeam(&a, 10, false); + ASSERT_EQ(world->GetTeamByNetworkID(10), &a); + + world->DereferenceTeam(&a, 0); + + EXPECT_EQ(world->GetTeamByNetworkID(10), (TM_Team *) 0); + EXPECT_EQ(world->GetTeamCount(), 0u); +} + +TEST_F(TeamManagerRegistry, ManyTeamsAreAllReachableByNetworkId) +{ + // Enough to spread across hash buckets, so a migration that collapses colliding keys shows up. + const int count = 120; + std::vector teams; + for (int i = 0; i < count; ++i) + { + TestTeam *t = new TestTeam(); + teams.push_back(t); + world->ReferenceTeam(t, (NetworkID) (1000 + i), false); + } + ASSERT_EQ(world->GetTeamCount(), (unsigned) count); + for (int i = 0; i < count; ++i) + EXPECT_EQ(world->GetTeamByNetworkID((NetworkID) (1000 + i)), teams[i]) << "team " << i; + + for (int i = 0; i < count; ++i) + { + world->DereferenceTeam(teams[i], 0); + delete teams[i]; + } +} + +TEST_F(TeamManagerRegistry, TeamMembersAreIndexedInRegistrationOrder) +{ + TestTeamMember a, b, c; + world->ReferenceTeamMember(&a, 11); + world->ReferenceTeamMember(&b, 22); + world->ReferenceTeamMember(&c, 33); + + ASSERT_EQ(world->GetTeamMemberCount(), 3u); + EXPECT_EQ(world->GetTeamMemberByIndex(0), &a); + EXPECT_EQ(world->GetTeamMemberByIndex(1), &b); + EXPECT_EQ(world->GetTeamMemberByIndex(2), &c); + EXPECT_EQ(world->GetTeamMemberIndex(&b), 1u); + EXPECT_EQ(world->GetTeamMemberIDByIndex(2), static_cast(33)); +} + +TEST_F(TeamManagerRegistry, DereferencingATeamMemberShiftsTheRestDown) +{ + TestTeamMember a, b, c; + world->ReferenceTeamMember(&a, 11); + world->ReferenceTeamMember(&b, 22); + world->ReferenceTeamMember(&c, 33); + + world->DereferenceTeamMember(&a); + + ASSERT_EQ(world->GetTeamMemberCount(), 2u); + EXPECT_EQ(world->GetTeamMemberByIndex(0), &b) << "the remainder must shift down, not be swapped"; + EXPECT_EQ(world->GetTeamMemberByIndex(1), &c); +} + +TEST_F(TeamManagerRegistry, TeamMembersAreFoundByNetworkId) +{ + TestTeamMember a, b; + world->ReferenceTeamMember(&a, 11); + world->ReferenceTeamMember(&b, 22); + + EXPECT_EQ(world->GetTeamMemberByNetworkID(11), &a); + EXPECT_EQ(world->GetTeamMemberByNetworkID(22), &b); + EXPECT_EQ(world->GetTeamMemberByNetworkID(999), (TM_TeamMember *) 0); + + world->DereferenceTeamMember(&a); + EXPECT_EQ(world->GetTeamMemberByNetworkID(11), (TM_TeamMember *) 0); +} + +TEST_F(TeamManagerRegistry, ParticipantsRoundTripAndDeduplicate) +{ + const RakNetGUID g1(1001), g2(1002); + world->AddParticipant(g1); + world->AddParticipant(g2); + + DataStructures::List list; + world->GetParticipantList(list); + ASSERT_EQ(list.Size(), 2u); + + std::set got; + for (unsigned i = 0; i < list.Size(); ++i) + got.insert(list[i].g); + EXPECT_EQ(got.count(1001), 1u); + EXPECT_EQ(got.count(1002), 1u); + + world->RemoveParticipant(g1); + DataStructures::List after; + world->GetParticipantList(after); + ASSERT_EQ(after.Size(), 1u); + EXPECT_EQ(after[0].g, static_cast(1002)); +} + +// RemoveWorld uses RemoveAtIndexFast while DereferenceTeam uses RemoveAtIndex. Both indices are +// published, so the two idioms have to stay distinct through the migration. +TEST_F(TeamManagerRegistry, RemoveWorldSwapsTheLastWorldIntoTheHole) +{ + manager.AddWorld(1); + manager.AddWorld(2); + manager.AddWorld(3); + ASSERT_EQ(manager.GetWorldCount(), 4u); + + TM_World *w1 = manager.GetWorldWithId(1); + TM_World *w2 = manager.GetWorldWithId(2); + TM_World *w3 = manager.GetWorldWithId(3); + ASSERT_NE(w1, (TM_World *) 0); + ASSERT_EQ(manager.GetWorldAtIndex(1), w1); + ASSERT_EQ(manager.GetWorldAtIndex(2), w2); + ASSERT_EQ(manager.GetWorldAtIndex(3), w3); + + // Remove index 1 with TWO worlds after it. Removing the second-to-last would be degenerate: + // swapping the last into the hole and shifting the remainder down give the same answer there, + // so such a case cannot tell the two idioms apart. + manager.RemoveWorld(1); + + ASSERT_EQ(manager.GetWorldCount(), 3u); + EXPECT_EQ(manager.GetWorldAtIndex(1), w3) + << "the last world must be swapped into the hole, not shifted up"; + EXPECT_EQ(manager.GetWorldAtIndex(2), w2); + EXPECT_EQ(manager.GetWorldWithId(1), (TM_World *) 0); + EXPECT_EQ(manager.GetWorldWithId(3), w3); +} + +TEST_F(TeamManagerRegistry, WorldsAreFoundById) +{ + manager.AddWorld(7); + ASSERT_EQ(manager.GetWorldCount(), 2u); + EXPECT_NE(manager.GetWorldWithId(7), (TM_World *) 0); + EXPECT_EQ(manager.GetWorldWithId(9), (TM_World *) 0); +} diff --git a/Tests/Unit/ThreadPoolQueueTests.cpp b/Tests/Unit/ThreadPoolQueueTests.cpp new file mode 100644 index 000000000..4ccdb5d00 --- /dev/null +++ b/Tests/Unit/ThreadPoolQueueTests.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2026, MafiaHub + * + * This source code is licensed under the MIT-style license found in the + * license.txt file in the root directory of this source tree. + */ + +#include + +#include "mafianet/thread_pool.h" + +#include + +// Pins ThreadPool's queue semantics before its three protected queues move from +// DataStructures::Queue to std::deque (issue #60): +// +// inputFunctionQueue / inputQueue paired arrays, FIFO, shift-remove by index +// outputQueue FIFO, shift-remove by index +// +// All hermetic: no threads are started, so every queue operation here is the +// synchronous one the public API performs on the calling thread. The pairing +// between inputFunctionQueue and inputQueue is only observable once a worker +// thread drains them, so that lives in Tests/Integration/ThreadPoolWorkerTests.cpp. + +namespace +{ + int AddOneHundred(int in, bool *returnOutput, void *) { *returnOutput = true; return in + 100; } + int AddTwoHundred(int in, bool *returnOutput, void *) { *returnOutput = true; return in + 200; } + + typedef ThreadPool IntPool; +} + +// --- input queue ----------------------------------------------------------- + +TEST(ThreadPoolQueues, AddInputAppendsInFifoOrder) +{ + IntPool pool; + + pool.AddInput(AddOneHundred, 10); + pool.AddInput(AddOneHundred, 20); + pool.AddInput(AddOneHundred, 30); + + ASSERT_EQ(pool.InputSize(), 3u); + ASSERT_EQ(pool.GetInputAtIndex(0), 10); + ASSERT_EQ(pool.GetInputAtIndex(1), 20); + ASSERT_EQ(pool.GetInputAtIndex(2), 30); +} + +// DataStructures::Queue::RemoveAtIndex shifts the later elements down -- it is not +// the swap-remove that DataStructures::List::RemoveAtIndexFast performs. Removing +// from the middle of a four-element queue is what tells the two apart. +TEST(ThreadPoolQueues, RemoveInputAtIndexShiftsLaterInputsDown) +{ + IntPool pool; + + pool.AddInput(AddOneHundred, 10); + pool.AddInput(AddOneHundred, 20); + pool.AddInput(AddOneHundred, 30); + pool.AddInput(AddOneHundred, 40); + + pool.RemoveInputAtIndex(1); + + ASSERT_EQ(pool.InputSize(), 3u); + ASSERT_EQ(pool.GetInputAtIndex(0), 10); + ASSERT_EQ(pool.GetInputAtIndex(1), 30) << "a swap-remove would put 40 here"; + ASSERT_EQ(pool.GetInputAtIndex(2), 40); +} + +TEST(ThreadPoolQueues, RemoveInputAtTheHeadAndAtTheTail) +{ + IntPool pool; + + for (int i = 1; i <= 4; i++) + pool.AddInput(AddOneHundred, i * 10); + + pool.RemoveInputAtIndex(0); + ASSERT_EQ(pool.InputSize(), 3u); + ASSERT_EQ(pool.GetInputAtIndex(0), 20); + + pool.RemoveInputAtIndex(pool.InputSize() - 1); + ASSERT_EQ(pool.InputSize(), 2u); + ASSERT_EQ(pool.GetInputAtIndex(0), 20); + ASSERT_EQ(pool.GetInputAtIndex(1), 30); +} + +TEST(ThreadPoolQueues, HasInputTracksTheInputQueue) +{ + IntPool pool; + + ASSERT_FALSE(pool.HasInput()); + ASSERT_FALSE(pool.HasInputFast()); + + pool.AddInput(AddOneHundred, 1); + ASSERT_TRUE(pool.HasInput()); + ASSERT_TRUE(pool.HasInputFast()); + + pool.RemoveInputAtIndex(0); + ASSERT_FALSE(pool.HasInput()); + ASSERT_FALSE(pool.HasInputFast()); +} + +// --- output queue ---------------------------------------------------------- + +TEST(ThreadPoolQueues, AddOutputAppendsAndGetOutputPopsTheHead) +{ + IntPool pool; + + pool.AddOutput(1); + pool.AddOutput(2); + pool.AddOutput(3); + + ASSERT_EQ(pool.OutputSize(), 3u); + ASSERT_EQ(pool.GetOutputAtIndex(0), 1); + ASSERT_EQ(pool.GetOutputAtIndex(2), 3); + + ASSERT_EQ(pool.GetOutput(), 1) << "GetOutput must take the oldest output, not the newest"; + ASSERT_EQ(pool.GetOutput(), 2); + ASSERT_EQ(pool.GetOutput(), 3); + ASSERT_EQ(pool.OutputSize(), 0u); +} + +TEST(ThreadPoolQueues, RemoveOutputAtIndexShiftsLaterOutputsDown) +{ + IntPool pool; + + for (int i = 1; i <= 4; i++) + pool.AddOutput(i); + + pool.RemoveOutputAtIndex(1); + + ASSERT_EQ(pool.OutputSize(), 3u); + ASSERT_EQ(pool.GetOutputAtIndex(0), 1); + ASSERT_EQ(pool.GetOutputAtIndex(1), 3) << "a swap-remove would put 4 here"; + ASSERT_EQ(pool.GetOutputAtIndex(2), 4); +} + +TEST(ThreadPoolQueues, HasOutputTracksTheOutputQueue) +{ + IntPool pool; + + ASSERT_FALSE(pool.HasOutput()); + ASSERT_FALSE(pool.HasOutputFast()); + + pool.AddOutput(7); + ASSERT_TRUE(pool.HasOutput()); + ASSERT_TRUE(pool.HasOutputFast()); + + ASSERT_EQ(pool.GetOutput(), 7); + ASSERT_FALSE(pool.HasOutput()); + ASSERT_FALSE(pool.HasOutputFast()); +} + +// --- clearing -------------------------------------------------------------- + +TEST(ThreadPoolQueues, ClearInputLeavesTheOutputQueueAlone) +{ + IntPool pool; + + pool.AddInput(AddOneHundred, 1); + pool.AddInput(AddOneHundred, 2); + pool.AddOutput(9); + + pool.ClearInput(); + + ASSERT_EQ(pool.InputSize(), 0u); + ASSERT_EQ(pool.OutputSize(), 1u) << "ClearInput must not touch the output queue"; + ASSERT_EQ(pool.GetOutputAtIndex(0), 9); +} + +TEST(ThreadPoolQueues, ClearOutputLeavesTheInputQueueAlone) +{ + IntPool pool; + + pool.AddInput(AddOneHundred, 1); + pool.AddOutput(9); + pool.AddOutput(8); + + pool.ClearOutput(); + + ASSERT_EQ(pool.OutputSize(), 0u); + ASSERT_EQ(pool.InputSize(), 1u) << "ClearOutput must not touch the input queue"; + ASSERT_EQ(pool.GetInputAtIndex(0), 1); +} + +// Clear() on a pool with no threads running used to return with runThreadsMutex +// still held, so the very next lock of it deadlocked -- including the one the +// destructor performs via StopThreads. This test hangs forever against that bug. +TEST(ThreadPoolQueues, ClearDoesNotLeaveRunThreadsMutexHeld) +{ + IntPool pool; + + pool.AddOutput(1); + pool.Clear(); + + // Each of these locks runThreadsMutex. + ASSERT_FALSE(pool.WasStarted()); + pool.StopThreads(); + pool.Clear(); + ASSERT_FALSE(pool.WasStarted()); + + // And so does ~ThreadPool, when pool goes out of scope. +} + +TEST(ThreadPoolQueues, ClearEmptiesBothQueuesWhenNoThreadsAreRunning) +{ + IntPool pool; + + ASSERT_FALSE(pool.WasStarted()); + + pool.AddInput(AddOneHundred, 1); + pool.AddInput(AddTwoHundred, 2); + pool.AddOutput(9); + + pool.Clear(); + + ASSERT_EQ(pool.InputSize(), 0u); + ASSERT_EQ(pool.OutputSize(), 0u); +} + +TEST(ThreadPoolQueues, QueuesAreReusableAfterBeingCleared) +{ + IntPool pool; + + pool.AddInput(AddOneHundred, 1); + pool.AddOutput(9); + pool.Clear(); + + pool.AddInput(AddOneHundred, 5); + pool.AddOutput(6); + + ASSERT_EQ(pool.InputSize(), 1u); + ASSERT_EQ(pool.GetInputAtIndex(0), 5); + ASSERT_EQ(pool.OutputSize(), 1u); + ASSERT_EQ(pool.GetOutput(), 6); +} + +// --- differential stress --------------------------------------------------- + +// A fixed-seed sequence of adds, indexed removals and head pops, checked against +// a std::deque reference model after every operation. Deterministic, so a +// divergence reproduces exactly; wrap-around in the old ring buffer and +// reallocation in the new deque are both exercised by the mixed load. +TEST(ThreadPoolQueues, OutputQueueMatchesADequeModelUnderMixedOperations) +{ + IntPool pool; + std::deque model; + + uint32_t seed = 0xC0FFEEu; + int nextValue = 0; + + for (int step = 0; step < 4000; step++) + { + seed = seed * 1103515245u + 12345u; + const uint32_t roll = (seed >> 16) % 100u; + + if (roll < 50u || model.empty()) + { + const int v = nextValue++; + pool.AddOutput(v); + model.push_back(v); + } + else if (roll < 80u) + { + ASSERT_EQ(pool.GetOutput(), model.front()) << "step " << step; + model.pop_front(); + } + else + { + seed = seed * 1103515245u + 12345u; + const unsigned index = (unsigned) ((seed >> 16) % (uint32_t) model.size()); + pool.RemoveOutputAtIndex(index); + model.erase(model.begin() + index); + } + + ASSERT_EQ(pool.OutputSize(), (unsigned) model.size()) << "step " << step; + ASSERT_EQ(pool.HasOutput(), model.empty() == false) << "step " << step; + } + + // Drain and compare the tail of the run element by element. + for (size_t i = 0; i < model.size(); i++) + ASSERT_EQ(pool.GetOutputAtIndex((unsigned) i), model[i]) << "index " << i; +} + +TEST(ThreadPoolQueues, InputQueueMatchesADequeModelUnderMixedOperations) +{ + IntPool pool; + std::deque model; + + uint32_t seed = 0x5EED1234u; + int nextValue = 0; + + for (int step = 0; step < 4000; step++) + { + seed = seed * 1103515245u + 12345u; + const uint32_t roll = (seed >> 16) % 100u; + + if (roll < 60u || model.empty()) + { + const int v = nextValue++; + pool.AddInput((v & 1) ? AddOneHundred : AddTwoHundred, v); + model.push_back(v); + } + else + { + seed = seed * 1103515245u + 12345u; + const unsigned index = (unsigned) ((seed >> 16) % (uint32_t) model.size()); + pool.RemoveInputAtIndex(index); + model.erase(model.begin() + index); + } + + ASSERT_EQ(pool.InputSize(), (unsigned) model.size()) << "step " << step; + } + + for (size_t i = 0; i < model.size(); i++) + ASSERT_EQ(pool.GetInputAtIndex((unsigned) i), model[i]) << "index " << i; +} diff --git a/docs/advanced/custom-memory-management.rst b/docs/advanced/custom-memory-management.rst index 1642ca899..2e1f52e8d 100644 --- a/docs/advanced/custom-memory-management.rst +++ b/docs/advanced/custom-memory-management.rst @@ -1,18 +1,28 @@ Custom Memory Management ======================== -MafiaNet supports custom memory allocators for fine-grained control over memory -allocation patterns. This is useful for tracking allocations, reducing fragmentation, -or integrating with game engine memory systems. +MafiaNet lets you replace the allocator it uses for raw buffers, and gives you +object pools for hot allocation paths. This is useful for tracking allocations, +reducing fragmentation, or integrating with a game engine's memory system. + +.. important:: + + Read :ref:`what-the-override-covers` first. Installing ``SetMalloc`` and + friends redirects *some* of the library's allocations by default and the rest + only in a build configured for it, and it never redirects the standard + containers the library uses internally. A plugin that assumes every byte + MafiaNet allocates comes from its own heap will be wrong. Overview -------- -Memory management can be customized at multiple levels: +Memory management can be customized at two levels: -* Global allocation overrides -* Per-system memory pools -* Custom allocators for specific components +* The ``rakMalloc``/``rakRealloc``/``rakFree`` function pointers, used for raw + buffer allocation and -- in a build with ``_USE_RAK_MEMORY_OVERRIDE=1`` -- for + object and array allocation through ``OP_NEW``/``OP_NEW_ARRAY``. +* Per-system memory pools (``DataStructures::MemoryPool`` and friends), which you + can also use for your own fixed-size objects. Basic Usage ----------- @@ -51,23 +61,40 @@ Basic Usage **Allocation tracking:** +The ``_Ex`` variants receive the call site. Note the exact signatures -- ``line`` +is ``unsigned int``, and ``SetRealloc_Ex`` must be installed alongside the other +two or reallocations keep going to the default implementation. + .. code-block:: cpp - void* MyMallocTracked(size_t size, const char* file, int line) { + void* MyMallocTracked(size_t size, const char* file, unsigned int line) { void* ptr = malloc(size); TrackAllocation(ptr, size, file, line); return ptr; } - void MyFreeTracked(void* ptr, const char* file, int line) { + void* MyReallocTracked(void* ptr, size_t size, const char* file, unsigned int line) { + UntrackAllocation(ptr, file, line); + void* out = realloc(ptr, size); + TrackAllocation(out, size, file, line); + return out; + } + + void MyFreeTracked(void* ptr, const char* file, unsigned int line) { UntrackAllocation(ptr, file, line); free(ptr); } // Enable file/line tracking MafiaNet::SetMalloc_Ex(MyMallocTracked); + MafiaNet::SetRealloc_Ex(MyReallocTracked); MafiaNet::SetFree_Ex(MyFreeTracked); +MafiaNet ships no leak reporter of its own; tracking is whatever your callbacks +do with the call site. For leak detection, prefer the sanitizers +(``-fsanitize=address``, and LeakSanitizer on Linux), which see the standard +containers too. + **Using memory pools:** .. code-block:: cpp @@ -83,30 +110,75 @@ Basic Usage // Return to pool packetPool.Release(data, _FILE_AND_LINE_); +.. _what-the-override-covers: + +What the Override Actually Covers +--------------------------------- + +There are three kinds of allocation inside MafiaNet, and ``SetMalloc`` reaches a +different amount of each. + +**1. Raw buffers --- always redirected.** +Library code that allocates a buffer calls ``rakMalloc_Ex`` / +``rakRealloc_Ex`` / ``rakFree_Ex`` directly, so your callbacks are used as soon +as you install them, in every build configuration. Packet payloads, the ban +list's IP strings and ``RakString``'s storage are allocated this way. + +**2. Objects and arrays --- only with ``_USE_RAK_MEMORY_OVERRIDE=1``.** +``OP_NEW``, ``OP_NEW_ARRAY`` and ``OP_DELETE`` are the library's object +allocation macros. They route through your allocator only when the library is +compiled with ``_USE_RAK_MEMORY_OVERRIDE=1``: + +.. code-block:: cmake + + add_compile_definitions(_USE_RAK_MEMORY_OVERRIDE=1) + +**``_USE_RAK_MEMORY_OVERRIDE`` defaults to 0** (see +``Source/include/mafianet/defines.h``), so in a default build every ``OP_NEW`` +is a plain ``new`` and your ``SetMalloc`` never sees it. The macro must be +defined identically for the library and for your own translation units, since it +changes what ``OP_NEW`` expands to in the headers. + +**3. Standard containers --- never redirected.** +MafiaNet's internals use ``std::vector``, ``std::deque``, ``std::map`` and +``std::unordered_map`` with the default ``std::allocator``. Those allocations go +to the global ``operator new``, in every configuration, and no MafiaNet setter +changes that. + +This is not a regression from the old ``DataStructures::List``/``Queue`` +containers: those allocated their backing array with ``OP_NEW_ARRAY``, which in a +default build is already a plain ``new Type[count]``. The only configuration in +which the change is observable is one that sets +``_USE_RAK_MEMORY_OVERRIDE=1`` *and* installs a custom allocator. To route the +standard containers too, replace the global ``operator new``/``operator delete`` +in your process -- that covers all three categories at once. + Key Features ------------ -* Global malloc/free/realloc override -* File and line number tracking variants +* Global malloc/realloc/free override for raw buffers +* File and line number tracking variants (``*_Ex``) * Built-in memory pool templates -* Thread-safe allocation support -* Memory leak detection helpers +* Thread-safe allocating queue Configuration Macros -------------------- -Define in your build configuration: +Define in your build configuration, for the library and your own code alike: -* ``_RAKNET_SUPPORT_FileOperations`` - Enable file I/O -* ``_RAKNET_SUPPORT_PacketizedTCP`` - TCP wrapper support -* ``RAKNET_MEMORY_TRACKING`` - Enable allocation tracking +* ``_USE_RAK_MEMORY_OVERRIDE`` - ``1`` routes ``OP_NEW``/``OP_NEW_ARRAY``/ + ``OP_DELETE`` through the installed allocator. Defaults to ``0``. Memory Pool Classes ------------------- -* ``DS_MemoryPool`` - Fixed-size object pool -* ``DS_BytePool`` - Variable-size byte allocation -* ``DS_ThreadSafeAllocatingQueue`` - Thread-safe allocation +* ``DataStructures::MemoryPool`` (``mafianet/ds_memory_pool.h``) - fixed-size + object pool +* ``DataStructures::BytePool`` (``mafianet/ds_byte_pool.h``) - variable-size byte + allocation, bucketed by size +* ``DataStructures::ThreadsafeAllocatingQueue`` + (``mafianet/ds_threadsafe_allocating_queue.h``) - a queue with a built-in + free pool, used for cross-thread handoff inside ``RakPeer`` See Also --------