/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include <AzCore/EBus/EBus.h> #include <AzCore/EBus/Results.h> #include <AzCore/std/sort.h> #include <AzCore/std/chrono/chrono.h> #include <AzCore/std/parallel/mutex.h> #include <AzCore/std/parallel/thread.h> #include <AzCore/std/smart_ptr/unique_ptr.h> #include <AzCore/Jobs/JobManager.h> #include <AzCore/Jobs/JobContext.h> #include <AzCore/Jobs/JobCompletion.h> #include <AzCore/Jobs/JobFunction.h> #include <AzCore/Math/Random.h> #include <AzCore/UnitTest/TestTypes.h> #include <Tests/AZTestShared/Utils/Utils.h> #include <gtest/gtest.h> // For GetTypeName<T>() #include <gtest/internal/gtest-type-util.h> using namespace AZ; // TestBus implementation details namespace BusImplementation { // Interface for the benchmark bus class Interface { public: virtual ~Interface() = default; virtual int OnEvent() = 0; virtual void OnWait() = 0; virtual void Release() = 0; virtual bool Compare(const Interface* other) const = 0; }; // Traits for the benchmark bus template <AZ::EBusAddressPolicy addressPolicy, AZ::EBusHandlerPolicy handlerPolicy, bool locklessDispatch = false> class Traits : public AZ::EBusTraits { public: static const AZ::EBusAddressPolicy AddressPolicy = addressPolicy; static const AZ::EBusHandlerPolicy HandlerPolicy = handlerPolicy; static const bool LocklessDispatch = locklessDispatch; // Allow queuing static const bool EnableEventQueue = true; // Force locking using MutexType = AZStd::recursive_mutex; // Only specialize BusIdType if not single address using BusIdType = AZStd::conditional_t<AddressPolicy == AZ::EBusAddressPolicy::Single, AZ::NullBusId, int>; // Only specialize BusIdOrderCompare if addresses are multiple and ordered using BusIdOrderCompare = AZStd::conditional_t<AddressPolicy != EBusAddressPolicy::ByIdAndOrdered, AZ::NullBusIdCompare, AZStd::less<int>>; }; template <typename Bus> class HandlerCommon : public Bus::Handler { public: AZ_CLASS_ALLOCATOR(HandlerCommon, AZ::SystemAllocator, 0); unsigned int m_eventCalls = 0; unsigned int m_expectedOrder = 0; unsigned int m_executedOrder = 0; HandlerCommon() { AZ::BetterPseudoRandom random; random.GetRandom(m_expectedOrder); } HandlerCommon(uint32_t handlerOrder) { m_expectedOrder = handlerOrder; } ~HandlerCommon() override { Bus::Handler::BusDisconnect(); } bool Compare(const Interface* other) const override { return m_expectedOrder < reinterpret_cast<const HandlerCommon*>(other)->m_expectedOrder; } int OnEvent() override { ++m_eventCalls; m_executedOrder = s_nextExecution++; return 0; } void OnWait() override { AZStd::this_thread::yield(); } void Release() override { delete this; } private: static int s_nextExecution; }; template <typename Bus> int HandlerCommon<Bus>::s_nextExecution = 0; template <typename Bus> class MultiHandlerCommon : public Bus::MultiHandler { public: AZ_CLASS_ALLOCATOR(MultiHandlerCommon, AZ::SystemAllocator, 0); MultiHandlerCommon() = default; ~MultiHandlerCommon() override { Bus::MultiHandler::BusDisconnect(); } int OnEvent() override { ++m_eventCalls; return 0; } void OnWait() override { AZStd::this_thread::yield(); } void Release() override { delete this; } bool Compare(const Interface* other) const override { return m_expectedOrder < static_cast<const MultiHandlerCommon*>(other)->m_expectedOrder; } private: uint32_t m_eventCalls{}; uint32_t m_expectedOrder{}; }; class InterfaceWithMutex : public AZ::EBusTraits { public: static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; using BusIdType = uint32_t; // Setting the MutexType to a value other than NullMutex // signals to the EBus system that the Ebus is able to be used in multiple threads // and therefore the EBus must be disconnected prior to the EBus internal handler destructor being invoked using MutexType = AZStd::recursive_mutex; virtual void OnEvent() = 0; }; using InterfaceWithMutexBus = AZ::EBus<InterfaceWithMutex>; } class MutexBusHandler : public BusImplementation::InterfaceWithMutexBus::Handler { void OnEvent() override { ++m_eventCalls; } private: uint32_t m_eventCalls{}; }; // Definition of the benchmark bus, depending on supplied policies template <AZ::EBusAddressPolicy addressPolicy, AZ::EBusHandlerPolicy handlerPolicy, bool locklessDispatch = false> using TestBus = AZ::EBus<BusImplementation::Interface, BusImplementation::Traits<addressPolicy, handlerPolicy, locklessDispatch>>; #define EBUS_TEST_ALIAS(BusType, AddressPolicy, HandlerPolicy) \ using BusType = TestBus<AZ::EBusAddressPolicy::AddressPolicy, AZ::EBusHandlerPolicy::HandlerPolicy>; \ namespace testing { namespace internal { template<> std::string GetTypeName<BusType>() { return #BusType; } } } // Predefined benchmark bus instantiations // Single EBUS_TEST_ALIAS(OneToOne, Single, Single) EBUS_TEST_ALIAS(OneToMany, Single, Multiple) EBUS_TEST_ALIAS(OneToManyOrdered, Single, MultipleAndOrdered) // ById EBUS_TEST_ALIAS(ManyToOne, ById, Single) EBUS_TEST_ALIAS(ManyToMany, ById, Multiple) EBUS_TEST_ALIAS(ManyToManyOrdered, ById, MultipleAndOrdered) // ByIdAndOrdered EBUS_TEST_ALIAS(ManyOrderedToOne, ByIdAndOrdered, Single) EBUS_TEST_ALIAS(ManyOrderedToMany, ByIdAndOrdered, Multiple) EBUS_TEST_ALIAS(ManyOrderedToManyOrdered, ByIdAndOrdered, MultipleAndOrdered) // Handler for multi-address buses template <typename Bus, AZ::EBusAddressPolicy addressPolicy = Bus::Traits::AddressPolicy> class Handler : public BusImplementation::HandlerCommon<Bus> { public: AZ_CLASS_ALLOCATOR(Handler, AZ::SystemAllocator, 0); Handler(int id, bool connectOnConstruct) { m_busId = id; if (connectOnConstruct) { EXPECT_FALSE(this->BusIsConnected()); Connect(); EXPECT_TRUE(this->BusIsConnected()); } } Handler(int id, int handlerOrder, bool connectOnConstruct) : BusImplementation::HandlerCommon<Bus>(handlerOrder) { m_busId = id; if (connectOnConstruct) { EXPECT_FALSE(this->BusIsConnected()); Connect(); EXPECT_TRUE(this->BusIsConnected()); } } ~Handler() override = default; // Helper function for connecting without specifying an id void Connect() { this->BusConnect(m_busId); } void Disconnect() { this->BusDisconnect(m_busId); } int OnEvent() override { BusImplementation::HandlerCommon<Bus>::OnEvent(); return 1; } private: int m_busId = 0; }; // Special handler for single address buses template <typename Bus> class Handler<Bus, AZ::EBusAddressPolicy::Single> : public BusImplementation::HandlerCommon<Bus> { public: AZ_CLASS_ALLOCATOR(Handler, AZ::SystemAllocator, 0); Handler(int, bool connectOnConstruct) { if (connectOnConstruct) { EXPECT_FALSE(this->BusIsConnected()); Connect(); EXPECT_TRUE(this->BusIsConnected()); } } Handler(int, int handlerOrder, bool connectOnConstruct) : BusImplementation::HandlerCommon<Bus>(handlerOrder) { if (connectOnConstruct) { EXPECT_FALSE(this->BusIsConnected()); Connect(); EXPECT_TRUE(this->BusIsConnected()); } } // Helper function for connecting without specifying an id void Connect() { this->BusConnect(); } void Disconnect() { this->BusDisconnect(); } int OnEvent() override { BusImplementation::HandlerCommon<Bus>::OnEvent(); return 2; } }; // Handler for multi-address buses template <typename Bus> class MultiHandlerById : public BusImplementation::MultiHandlerCommon<Bus> { public: AZ_CLASS_ALLOCATOR(MultiHandlerById, AZ::SystemAllocator, 0); MultiHandlerById(std::initializer_list<int> busIdList) { // We will bind at construction time to the bus. Disconnect is automatic when the object is // destroyed or we can call BusDisconnect() EXPECT_FALSE(this->BusIsConnected()); Connect(busIdList); EXPECT_TRUE(this->BusIsConnected()); } // Helper function for connecting on multiple ids void Connect(std::initializer_list<int> busIdList) { for (int busId : busIdList) { this->BusConnect(busId); } } void Disconnect(std::initializer_list<int> busIdList) { for (int busId : busIdList) { this->BusDisconnect(busId); } } int OnEvent() override { return BusImplementation::MultiHandlerCommon<Bus>::OnEvent(); } }; namespace UnitTest { using BusTypesId = ::testing::Types< ManyToOne, ManyToMany, ManyToManyOrdered, ManyOrderedToOne, ManyOrderedToMany, ManyOrderedToManyOrdered>; using BusTypesAll = ::testing::Types< OneToOne, OneToMany, OneToManyOrdered, ManyToOne, ManyToMany, ManyToManyOrdered, ManyOrderedToOne, ManyOrderedToMany, ManyOrderedToManyOrdered>; template <typename Bus> class EBusTestAll : public AllocatorsFixture { public: using Handler = Handler<Bus>; using MultiHandlerById = MultiHandlerById<Bus>; EBusTestAll() { Bus::GetOrCreateContext(); } void TearDown() override { DestroyHandlers(); AllocatorsFixture::TearDown(); } ////////////////////////////////////////////////////////////////////////// // Handler Helpers // Create an appropriate number of handlers for testing void CreateHandlers() { int numAddresses = HasMultipleAddresses() ? 3 : 1; int numHandlersPerAddress = HasMultipleHandlersPerAddress() ? 3 : 1; constexpr bool connectOnConstruct{ true }; for (int address = 0; address < numAddresses; ++address) { for (int handler = 0; handler < numHandlersPerAddress; ++handler) { m_handlers[address].emplace_back(aznew Handler(address, connectOnConstruct)); ++m_numHandlers; } } ValidateCalls(0); } // Gets the total number of handlers active int GetNumHandlers() { return m_numHandlers; } // Clears the handlers list without deleting them (useful for Release tests) void ClearHandlers() { m_handlers.clear(); m_handlers.rehash(0); m_numHandlers = 0; } // Destroy all handlers void DestroyHandlers() { for (const auto& handlerPair : m_handlers) { for (Handler* handler : handlerPair.second) { delete handler; } } ClearHandlers(); EXPECT_FALSE(Bus::HasHandlers()); } // Ensure that all active handlers have the expected call count, in the correct order // This should only be called after Broadcast() void ValidateCalls(int expected, bool isForward = true) { for (const auto& handlerPair : m_handlers) { ValidateCalls(expected, handlerPair.first, isForward); } // Validate address execution order if (AddressesAreOrdered()) { // Collect the first handler from each address using PairType = AZStd::pair<int, Handler*>; AZStd::vector<PairType> sortedHandlers; for (const auto& handlerPair : m_handlers) { PairType pair(handlerPair.first, handlerPair.second.front()); auto insertPos = AZStd::lower_bound( sortedHandlers.begin(), sortedHandlers.end(), pair, [](const PairType& lhs, const PairType& rhs) { return lhs.first < rhs.first; } ); sortedHandlers.emplace(insertPos, pair); } // Iterate over the list, and validate that they were called in the correct order unsigned int lastExecuted = 0; for (const PairType& pair : sortedHandlers) { if (lastExecuted > 0) { if (isForward) { EXPECT_LT(lastExecuted, pair.second->m_executedOrder); } else { EXPECT_GT(lastExecuted, pair.second->m_executedOrder); } } lastExecuted = pair.second->m_executedOrder; } } } // Ensure that all active handlers have the expected call count, and were called in the correct order void ValidateCalls(int expected, int id, bool isForward = true) { auto& handlers = m_handlers[id]; for (Handler* handler : handlers) { EXPECT_EQ(expected, handler->m_eventCalls); } // Validate handler execution order if (HandlersAreOrdered()) { // Sort the handlers the same way we expect the bus to sort them auto sortedHandlers = handlers; AZStd::sort(sortedHandlers.begin(), sortedHandlers.end(), AZStd::bind(&Handler::Compare, AZStd::placeholders::_1, AZStd::placeholders::_2)); // Iterate over the list, and validate that they were called in the correct order unsigned int lastExecuted = 0; for (const Handler* handler : sortedHandlers) { if (lastExecuted > 0) { if (isForward) { EXPECT_LT(lastExecuted, handler->m_executedOrder); } else { EXPECT_GT(lastExecuted, handler->m_executedOrder); } } lastExecuted = handler->m_executedOrder; } } } ////////////////////////////////////////////////////////////////////////// // Metadata helpers bool HasMultipleAddresses() { return Bus::HasId; } bool AddressesAreOrdered() { return Bus::Traits::AddressPolicy == EBusAddressPolicy::ByIdAndOrdered; } bool HasMultipleHandlersPerAddress() { return Bus::Traits::HandlerPolicy != EBusHandlerPolicy::Single; } bool HandlersAreOrdered() { return Bus::Traits::HandlerPolicy == EBusHandlerPolicy::MultipleAndOrdered; } protected: AZStd::unordered_map<int, AZStd::vector<Handler*>> m_handlers; int m_numHandlers = 0; }; TYPED_TEST_CASE(EBusTestAll, BusTypesAll); template <typename Bus> class EBusTestId : public EBusTestAll<Bus> { }; TYPED_TEST_CASE(EBusTestId, BusTypesId); using BusTypesIdMultiHandlers = ::testing::Types< ManyToMany, ManyToManyOrdered, ManyOrderedToMany, ManyOrderedToManyOrdered>; template <typename Bus> class EBusTestIdMultiHandlers : public EBusTestAll<Bus> { }; TYPED_TEST_CASE(EBusTestIdMultiHandlers, BusTypesIdMultiHandlers); ////////////////////////////////////////////////////////////////////////// // Non-event functions TYPED_TEST(EBusTestAll, ConnectDisconnect) { using Bus = TypeParam; using Handler = typename EBusTestAll<Bus>::Handler; constexpr bool connectOnConstruct{ true }; Handler meh(0, connectOnConstruct); EXPECT_EQ(0, meh.m_eventCalls); EXPECT_TRUE(Bus::HasHandlers()); Bus::Broadcast(&Bus::Events::OnEvent); EXPECT_EQ(1, meh.m_eventCalls); EXPECT_TRUE(meh.BusIsConnected()); meh.BusDisconnect(); // we disconnect from receiving events. EXPECT_FALSE(meh.BusIsConnected()); EXPECT_FALSE(Bus::HasHandlers()); // this signal will NOT trigger any calls. Bus::Broadcast(&Bus::Events::OnEvent); EXPECT_EQ(1, meh.m_eventCalls); } TYPED_TEST(EBusTestIdMultiHandlers, EnumerateHandlers_MultiHandler) { using Bus = TypeParam; using MultiHandlerById = typename EBusTestAll<Bus>::MultiHandlerById; MultiHandlerById sourceMultiHandler{ 0, 1, 2 }; MultiHandlerById multiHandlerWithOverlappingIds{ 1, 3, 5 }; // Test handlers' enumeration functionality Bus::EnumerateHandlers([](typename MultiHandlerById::Interface* interfaceInst) -> bool { interfaceInst->OnEvent(); return true; }); } TYPED_TEST(EBusTestId, FindFirstHandler) { using Bus = TypeParam; using Handler = typename EBusTestAll<Bus>::Handler; constexpr bool connectOnConstruct{ true }; Handler meh0(0, connectOnConstruct); /// <-- Bind to bus 0 Handler meh1(1, connectOnConstruct); /// <-- Bind to bus 1 // Test handlers' enumeration functionality EXPECT_EQ(&meh0, Bus::FindFirstHandler(0)); EXPECT_EQ(&meh1, Bus::FindFirstHandler(1)); EXPECT_EQ(nullptr, Bus::FindFirstHandler(3)); } ////////////////////////////////////////////////////////////////////////// // Immediate calls TYPED_TEST(EBusTestAll, Broadcast) { using Bus = TypeParam; this->CreateHandlers(); Bus::Broadcast(&Bus::Events::OnEvent); this->ValidateCalls(1); EBUS_EVENT(Bus, OnEvent); this->ValidateCalls(2); } TYPED_TEST(EBusTestAll, Broadcast_Release) { using Bus = TypeParam; this->CreateHandlers(); EXPECT_EQ(this->GetNumHandlers(), Bus::GetTotalNumOfEventHandlers()); Bus::Broadcast(&Bus::Events::Release); EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } TYPED_TEST(EBusTestAll, BroadcastReverse) { using Bus = TypeParam; this->CreateHandlers(); Bus::BroadcastReverse(&Bus::Events::OnEvent); this->ValidateCalls(1, false); EBUS_EVENT_REVERSE(Bus, OnEvent); this->ValidateCalls(2, false); } TYPED_TEST(EBusTestAll, BroadcastReverse_Release) { using Bus = TypeParam; this->CreateHandlers(); EXPECT_EQ(this->GetNumHandlers(), Bus::GetTotalNumOfEventHandlers()); Bus::BroadcastReverse(&Bus::Events::Release); EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } TYPED_TEST(EBusTestAll, BroadcastResult) { using Bus = TypeParam; this->CreateHandlers(); int result = -1; Bus::BroadcastResult(result, &Bus::Events::OnEvent); EXPECT_LT(0, result); this->ValidateCalls(1); result = -1; EBUS_EVENT_RESULT(result, Bus, OnEvent); EXPECT_LT(0, result); this->ValidateCalls(2); this->DestroyHandlers(); result = -1; Bus::BroadcastResult(result, &Bus::Events::OnEvent); EXPECT_EQ(-1, result); } TYPED_TEST(EBusTestAll, BroadcastResultReverse) { using Bus = TypeParam; this->CreateHandlers(); int result = -1; Bus::BroadcastResultReverse(result, &Bus::Events::OnEvent); EXPECT_LT(0, result); this->ValidateCalls(1, false); result = -1; EBUS_EVENT_RESULT_REVERSE(result, Bus, OnEvent); EXPECT_LT(0, result); this->ValidateCalls(2, false); this->DestroyHandlers(); result = -1; Bus::BroadcastResultReverse(result, &Bus::Events::OnEvent); EXPECT_EQ(-1, result); } // Test sending events on an address TYPED_TEST(EBusTestId, Event) { using Bus = TypeParam; this->CreateHandlers(); // Signal OnEvent event on bus 1 Bus::Event(1, &Bus::Events::OnEvent); // Validate bus 1 has 2 calls, all others have 1 this->ValidateCalls(1, 1); this->ValidateCalls(0, 2); this->ValidateCalls(0, 3); } // Test sending events on an address TYPED_TEST(EBusTestId, Event_Release) { using Bus = TypeParam; this->CreateHandlers(); for (const auto& handlerPair : this->m_handlers) { Bus::Event(handlerPair.first, &Bus::Events::Release); } EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } // Test sending events on an address TYPED_TEST(EBusTestId, EventReverse) { using Bus = TypeParam; this->CreateHandlers(); // Signal OnEvent event on bus 1 Bus::EventReverse(1, &Bus::Events::OnEvent); // Validate bus 1 has 2 calls, all others have 1 this->ValidateCalls(1, 1, false); this->ValidateCalls(0, 2, false); this->ValidateCalls(0, 3, false); } // Test sending events (that delete this) on an address, backwards TYPED_TEST(EBusTestId, EventReverse_Release) { using Bus = TypeParam; this->CreateHandlers(); for (const auto& handlerPair : this->m_handlers) { Bus::EventReverse(handlerPair.first, &Bus::Events::Release); } EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } // Test sending events with results on an address TYPED_TEST(EBusTestId, EventResult) { using Bus = TypeParam; this->CreateHandlers(); // Signal OnEvent event on bus 1 int result = -1; Bus::EventResult(result, 1, &Bus::Events::OnEvent); EXPECT_LT(0, result); // Validate bus 1 has 2 calls, all others have 1 this->ValidateCalls(1, 1); this->ValidateCalls(0, 2); this->ValidateCalls(0, 3); this->DestroyHandlers(); result = -1; Bus::EventResult(result, 1, &Bus::Events::OnEvent); EXPECT_EQ(-1, result); } // Test sending events with results on an address TYPED_TEST(EBusTestId, EventResultReverse) { using Bus = TypeParam; this->CreateHandlers(); // Signal OnEvent event on bus 1 int result = -1; Bus::EventResultReverse(result, 1, &Bus::Events::OnEvent); EXPECT_LT(0, result); // Validate bus 1 has 2 calls, all others have 1 this->ValidateCalls(1, 1, false); this->ValidateCalls(0, 2, false); this->ValidateCalls(0, 3, false); this->DestroyHandlers(); result = -1; Bus::EventResultReverse(result, 1, &Bus::Events::OnEvent); EXPECT_EQ(-1, result); } // Test sending events on a bound bus ptr TYPED_TEST(EBusTestId, BindEvent) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; Bus::Bind(ptr, 1); EXPECT_NE(nullptr, ptr); // Signal OnEvent event on bus 1 Bus::Event(ptr, &Bus::Events::OnEvent); // Validate bus 1 has 2 calls, all others have 1 this->ValidateCalls(1, 1); this->ValidateCalls(0, 2); this->ValidateCalls(0, 3); this->DestroyHandlers(); // Validate that broadcasting/eventing after binding disconnecting all doesn't crash Bus::Broadcast(&Bus::Events::OnEvent); Bus::Event(ptr, &Bus::Events::OnEvent); Bus::Event(1, &Bus::Events::OnEvent); } // Test sending events (that delete this) on a bound bus ptr TYPED_TEST(EBusTestId, BindEvent_Release) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; for (const auto& handlerPair : this->m_handlers) { Bus::Bind(ptr, handlerPair.first); EXPECT_NE(nullptr, ptr); Bus::Event(ptr, &Bus::Events::Release); } EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } // Test sending events on a bound bus ptr, backwards TYPED_TEST(EBusTestId, BindEventReverse) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; Bus::Bind(ptr, 1); EXPECT_NE(nullptr, ptr); // Signal OnEvent event on bus 1 Bus::EventReverse(ptr, &Bus::Events::OnEvent); // Validate bus 1 has 2 calls, all others have 1 this->ValidateCalls(1, 1, false); this->ValidateCalls(0, 2, false); this->ValidateCalls(0, 3, false); this->DestroyHandlers(); // Validate that broadcasting/eventing after binding disconnecting all doesn't crash Bus::BroadcastReverse(&Bus::Events::OnEvent); Bus::EventReverse(ptr, &Bus::Events::OnEvent); Bus::EventReverse(1, &Bus::Events::OnEvent); } // Test sending events (that delete this) on a bound bus ptr, backwards TYPED_TEST(EBusTestId, BindEventReverse_Release) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; for (const auto& handlerPair : this->m_handlers) { Bus::Bind(ptr, handlerPair.first); EXPECT_NE(nullptr, ptr); Bus::EventReverse(ptr, &Bus::Events::Release); } EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } // Test sending events on a bound bus ptr TYPED_TEST(EBusTestId, BindEventResult) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; Bus::Bind(ptr, 1); EXPECT_NE(nullptr, ptr); // Signal OnEvent event on bus 1 int result = -1; Bus::EventResult(result, ptr, &Bus::Events::OnEvent); EXPECT_LT(0, result); // Validate bus 1 has 2 calls, all others have 1 this->ValidateCalls(1, 1); this->ValidateCalls(0, 2); this->ValidateCalls(0, 3); this->DestroyHandlers(); // Validate that broadcasting/eventing after binding disconnecting all doesn't crash Bus::Broadcast(&Bus::Events::OnEvent); Bus::Event(ptr, &Bus::Events::OnEvent); Bus::Event(1, &Bus::Events::OnEvent); } // Test sending events on a bound bus ptr, backwards TYPED_TEST(EBusTestId, BindEventResultReverse) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; Bus::Bind(ptr, 1); EXPECT_NE(nullptr, ptr); // Signal OnEvent event on bus 1 int result = -1; Bus::EventResultReverse(result, ptr, &Bus::Events::OnEvent); EXPECT_LT(0, result); // Validate bus 1 has 2 calls, all others have 1 this->ValidateCalls(1, 1, false); this->ValidateCalls(0, 2, false); this->ValidateCalls(0, 3, false); this->DestroyHandlers(); // Validate that broadcasting/eventing after binding disconnecting all doesn't crash Bus::BroadcastReverse(&Bus::Events::OnEvent); Bus::EventReverse(ptr, &Bus::Events::OnEvent); Bus::EventReverse(1, &Bus::Events::OnEvent); } ////////////////////////////////////////////////////////////////////////// // Queued calls TYPED_TEST(EBusTestAll, QueueBroadcast) { using Bus = TypeParam; this->CreateHandlers(); Bus::QueueBroadcast(&Bus::Events::OnEvent); this->ValidateCalls(0); Bus::ExecuteQueuedEvents(); this->ValidateCalls(1); EBUS_QUEUE_EVENT(Bus, OnEvent); this->ValidateCalls(1); Bus::ExecuteQueuedEvents(); this->ValidateCalls(2); } TYPED_TEST(EBusTestAll, QueueBroadcast_Release) { using Bus = TypeParam; this->CreateHandlers(); EXPECT_EQ(this->GetNumHandlers(), Bus::GetTotalNumOfEventHandlers()); Bus::QueueBroadcast(&Bus::Events::Release); EXPECT_EQ(this->GetNumHandlers(), Bus::GetTotalNumOfEventHandlers()); Bus::ExecuteQueuedEvents(); EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } TYPED_TEST(EBusTestAll, QueueBroadcastReverse) { using Bus = TypeParam; this->CreateHandlers(); Bus::QueueBroadcastReverse(&Bus::Events::OnEvent); this->ValidateCalls(0, false); Bus::ExecuteQueuedEvents(); this->ValidateCalls(1, false); EBUS_QUEUE_EVENT_REVERSE(Bus, OnEvent); this->ValidateCalls(1, false); Bus::ExecuteQueuedEvents(); this->ValidateCalls(2, false); } TYPED_TEST(EBusTestAll, QueueBroadcastReverse_Release) { using Bus = TypeParam; this->CreateHandlers(); EXPECT_EQ(this->GetNumHandlers(), Bus::GetTotalNumOfEventHandlers()); Bus::QueueBroadcastReverse(&Bus::Events::Release); EXPECT_EQ(this->GetNumHandlers(), Bus::GetTotalNumOfEventHandlers()); Bus::ExecuteQueuedEvents(); EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } // Test sending events on an address TYPED_TEST(EBusTestId, QueueEvent) { using Bus = TypeParam; this->CreateHandlers(); // Signal OnEvent event on bus 1 Bus::QueueEvent(1, &Bus::Events::OnEvent); this->ValidateCalls(0); Bus::ExecuteQueuedEvents(); // Validate bus 1 has 1 calls, all others have 0 this->ValidateCalls(1, 1); this->ValidateCalls(0, 2); this->ValidateCalls(0, 3); } // Test sending events on an address TYPED_TEST(EBusTestId, QueueEvent_Release) { using Bus = TypeParam; this->CreateHandlers(); for (const auto& handlerPair : this->m_handlers) { Bus::QueueEvent(handlerPair.first, &Bus::Events::Release); } EXPECT_EQ(this->GetNumHandlers(), Bus::GetTotalNumOfEventHandlers()); Bus::ExecuteQueuedEvents(); EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } // Test sending events on an address TYPED_TEST(EBusTestId, QueueEventReverse) { using Bus = TypeParam; this->CreateHandlers(); // Signal OnEvent event on bus 1 Bus::QueueEventReverse(1, &Bus::Events::OnEvent); this->ValidateCalls(0); Bus::ExecuteQueuedEvents(); // Validate bus 1 has 2 calls, all others have 1 this->ValidateCalls(1, 1, false); this->ValidateCalls(0, 2, false); this->ValidateCalls(0, 3, false); } // Test sending events (that delete this) on an address, backwards TYPED_TEST(EBusTestId, QueueEventReverse_Release) { using Bus = TypeParam; this->CreateHandlers(); for (const auto& handlerPair : this->m_handlers) { Bus::QueueEventReverse(handlerPair.first, &Bus::Events::Release); } EXPECT_EQ(this->GetNumHandlers(), Bus::GetTotalNumOfEventHandlers()); Bus::ExecuteQueuedEvents(); EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } // Test sending events on a bound bus ptr TYPED_TEST(EBusTestId, QueueBindEvent) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; Bus::Bind(ptr, 1); EXPECT_NE(nullptr, ptr); // Signal OnEvent event on bus 1 Bus::QueueEvent(ptr, &Bus::Events::OnEvent); this->ValidateCalls(0); Bus::ExecuteQueuedEvents(); // Validate bus 1 has 2 calls, all others have 1 this->ValidateCalls(1, 1); this->ValidateCalls(0, 2); this->ValidateCalls(0, 3); } // Test sending events (that delete this) on a bound bus ptr TYPED_TEST(EBusTestId, QueueBindEvent_Release) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; for (const auto& handlerPair : this->m_handlers) { Bus::Bind(ptr, handlerPair.first); EXPECT_NE(nullptr, ptr); Bus::QueueEvent(ptr, &Bus::Events::Release); } EXPECT_EQ(this->GetNumHandlers(), Bus::GetTotalNumOfEventHandlers()); Bus::ExecuteQueuedEvents(); EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } // Test sending events on a bound bus ptr, backwards TYPED_TEST(EBusTestId, QueueBindEventReverse) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; Bus::Bind(ptr, 1); EXPECT_NE(nullptr, ptr); // Signal OnEvent event on bus 1 Bus::QueueEventReverse(ptr, &Bus::Events::OnEvent); this->ValidateCalls(0); Bus::ExecuteQueuedEvents(); // Validate bus 1 has 2 calls, all others have 1 this->ValidateCalls(1, 1, false); this->ValidateCalls(0, 2, false); this->ValidateCalls(0, 3, false); } // Test sending events (that delete this) on a bound bus ptr, backwards TYPED_TEST(EBusTestId, QueueBindEventReverse_Release) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; for (const auto& handlerPair : this->m_handlers) { Bus::Bind(ptr, handlerPair.first); EXPECT_NE(nullptr, ptr); Bus::QueueEventReverse(ptr, &Bus::Events::Release); } EXPECT_EQ(this->GetNumHandlers(), Bus::GetTotalNumOfEventHandlers()); Bus::ExecuteQueuedEvents(); EXPECT_FALSE(Bus::HasHandlers()); this->ClearHandlers(); } ////////////////////////////////////////////////////////////////////////// // GetCurrentBusId calls TYPED_TEST(EBusTestId, Event_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); auto busCallback = [](typename Bus::InterfaceType*) { const int* busId = Bus::GetCurrentBusId(); ASSERT_NE(nullptr, busId); EXPECT_EQ(1, *busId); }; Bus::Event(1, busCallback); this->DestroyHandlers(); } TYPED_TEST(EBusTestId, EventResult_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); auto busCallback = [](typename Bus::InterfaceType*) -> const char* { const int* busId = Bus::GetCurrentBusId(); EXPECT_NE(nullptr, busId); if (busId) { EXPECT_EQ(1, *busId); } return "BusType"; }; const char* result{}; Bus::EventResult(result, 1, busCallback); EXPECT_STREQ("BusType", result); this->DestroyHandlers(); } TYPED_TEST(EBusTestId, EventReverse_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); auto busCallback = [](typename Bus::InterfaceType*) { const int* busId = Bus::GetCurrentBusId(); ASSERT_NE(nullptr, busId); EXPECT_EQ(1, *busId); }; Bus::EventReverse(1, busCallback); this->DestroyHandlers(); } TYPED_TEST(EBusTestId, EventResultReverse_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); auto busCallback = [](typename Bus::InterfaceType*) { const int* busId = Bus::GetCurrentBusId(); EXPECT_NE(nullptr, busId); if (busId) { EXPECT_EQ(1, *busId); } return 7; }; int32_t result{}; Bus::EventResultReverse(result, 1, busCallback); EXPECT_EQ(7, result); this->DestroyHandlers(); } TYPED_TEST(EBusTestId, BindEvent_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; auto busCallback = [](typename Bus::InterfaceType*) { EXPECT_NE(nullptr, Bus::GetCurrentBusId()); }; for (const auto& handlerPair : this->m_handlers) { Bus::Bind(ptr, handlerPair.first); EXPECT_NE(nullptr, ptr); Bus::Event(ptr, busCallback); } this->DestroyHandlers(); } TYPED_TEST(EBusTestId, BindEventResult_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; auto busCallback = [](typename Bus::InterfaceType*) { EXPECT_NE(nullptr, Bus::GetCurrentBusId()); return true; }; for (const auto& handlerPair : this->m_handlers) { Bus::Bind(ptr, handlerPair.first); EXPECT_NE(nullptr, ptr); bool result{}; Bus::EventResult(result, ptr, busCallback); EXPECT_TRUE(result); } this->DestroyHandlers(); } TYPED_TEST(EBusTestId, BindEventReverse_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; auto busCallback = [](typename Bus::InterfaceType*) { EXPECT_NE(nullptr, Bus::GetCurrentBusId()); }; for (const auto& handlerPair : this->m_handlers) { Bus::Bind(ptr, handlerPair.first); EXPECT_NE(nullptr, ptr); Bus::EventReverse(ptr, busCallback); } this->DestroyHandlers(); } TYPED_TEST(EBusTestId, BindEventResultReverse_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); typename Bus::BusPtr ptr; auto busCallback = [](typename Bus::InterfaceType*) { EXPECT_NE(nullptr, Bus::GetCurrentBusId()); return true; }; for (const auto& handlerPair : this->m_handlers) { Bus::Bind(ptr, handlerPair.first); EXPECT_NE(nullptr, ptr); bool result{}; Bus::EventResultReverse(result, ptr, busCallback); EXPECT_TRUE(result); } this->DestroyHandlers(); } TYPED_TEST(EBusTestId, Broadcast_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); auto busCallback = [](typename Bus::InterfaceType*) { const int* busId = Bus::GetCurrentBusId(); EXPECT_NE(nullptr, busId); }; Bus::Broadcast(busCallback); this->DestroyHandlers(); } TYPED_TEST(EBusTestId, BroadcastResult_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); auto busCallback = [](typename Bus::InterfaceType*) { const int* busId = Bus::GetCurrentBusId(); EXPECT_NE(nullptr, busId); return 16.0f; }; float result{}; Bus::BroadcastResult(result, busCallback); EXPECT_FLOAT_EQ(16.0f, result); this->DestroyHandlers(); } TYPED_TEST(EBusTestId, BroadcastReverse_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); auto busCallback = [](typename Bus::InterfaceType*) { const int* busId = Bus::GetCurrentBusId(); EXPECT_NE(nullptr, busId); }; Bus::BroadcastReverse(busCallback); this->DestroyHandlers(); } TYPED_TEST(EBusTestId, BroadcastResultReverse_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); auto busCallback = [](typename Bus::InterfaceType*) { const int* busId = Bus::GetCurrentBusId(); EXPECT_NE(nullptr, busId); return 8.0; }; double result{}; Bus::BroadcastResultReverse(result, busCallback); EXPECT_DOUBLE_EQ(8.0, result); this->DestroyHandlers(); } TYPED_TEST(EBusTestId, EnumerateHandlers_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); auto busCallback = [](typename Bus::InterfaceType*) { const int* busId = Bus::GetCurrentBusId(); EXPECT_NE(nullptr, busId); return true; }; Bus::EnumerateHandlers(busCallback); this->DestroyHandlers(); } TYPED_TEST(EBusTestId, EnumerateHandlersId_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); auto busCallback = [](typename Bus::InterfaceType*) { const int* busId = Bus::GetCurrentBusId(); EXPECT_NE(nullptr, busId); if (busId) { EXPECT_EQ(1, *busId); } return true; }; Bus::EnumerateHandlersId(1, busCallback); this->DestroyHandlers(); } TYPED_TEST(EBusTestId, EnumerateHandlersPtr_GetCurrentBusId_ReturnsNonNullptr) { using Bus = TypeParam; this->CreateHandlers(); auto busCallback = [](typename Bus::InterfaceType*) { const int* busId = Bus::GetCurrentBusId(); EXPECT_NE(nullptr, busId); if(busId) { EXPECT_EQ(1, *busId); } return true; }; typename Bus::BusPtr busPtr; Bus::Bind(busPtr, 1); EXPECT_NE(nullptr, busPtr); Bus::EnumerateHandlersPtr(busPtr, busCallback); this->DestroyHandlers(); } class EBus : public AllocatorsFixture {}; TEST_F(EBus, DISABLED_CopyConstructorOfEBusHandlerDoesNotAssertInInternalDestructorOfHandler) { AZ_TEST_START_TRACE_SUPPRESSION; { MutexBusHandler sourceHandler; // Connect source handler to InterfaceWithMutexBus and then copy it over to a new instance // Afterwards disconnect the source handler from the InterfaceWithMutexBus sourceHandler.BusConnect(1); MutexBusHandler targetHandler(sourceHandler); sourceHandler.BusDisconnect(); } AZ_TEST_STOP_TRACE_SUPPRESSION(0); } TEST_F(EBus, DISABLED_CopyAssignmentOfEBusHandlerDoesNotAssertInInternalDestructorOfHandler) { AZ_TEST_START_TRACE_SUPPRESSION; { MutexBusHandler sourceHandler; MutexBusHandler targetHandler; // Connect source handler to InterfaceWithMutexBus and then copy it over to a new instance // Afterwards disconnect the source handler from the InterfaceWithMutexBus sourceHandler.BusConnect(1); targetHandler = sourceHandler; sourceHandler.BusDisconnect(); } AZ_TEST_STOP_TRACE_SUPPRESSION(0); } TEST_F(EBus, CopyConstructorOfEBusHandler_CopyFromConnected_DoesNotAssert) { AZ_TEST_START_TRACE_SUPPRESSION; { MutexBusHandler sourceHandler; // Connect source handler to InterfaceWithMutexBus and then copy it over to a new instance // Afterwards disconnect the source handler from the InterfaceWithMutexBus sourceHandler.BusConnect(1); // Copy behavior which connects to source handler's bus if // Source handler was connected may be unexpected but it should not assert MutexBusHandler targetHandler(sourceHandler); sourceHandler.BusDisconnect(); targetHandler.BusDisconnect(); } AZ_TEST_STOP_TRACE_SUPPRESSION(0); } TEST_F(EBus, CopyOperatorOfEBusHandler_CopyToConnected_DoesNotAssert) { AZ_TEST_START_TRACE_SUPPRESSION; { MutexBusHandler targetHandler; // Connect source handler to InterfaceWithMutexBus and then copy it over to a new instance // Afterwards disconnect the source handler from the InterfaceWithMutexBus targetHandler.BusConnect(1); // Copy behavior which connects to source handler's bus if // Source handler was connected may be unexpected but it should not assert MutexBusHandler sourceHandler; targetHandler = sourceHandler; sourceHandler.BusDisconnect(); targetHandler.BusDisconnect(); } AZ_TEST_STOP_TRACE_SUPPRESSION(0); } /** * Tests multi-bus handler (a singe ebus instance that can connect to multiple buses) */ namespace MultBusHandler { /** * Create event that allows MULTI buses. By default we already allow multiple handlers per bus. */ class MyEventGroup : public AZ::EBusTraits { public: ////////////////////////////////////////////////////////////////////////// // EBus interface settings static const EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Multiple; static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::ById; typedef int BusIdType; ////////////////////////////////////////////////////////////////////////// virtual ~MyEventGroup() {} ////////////////////////////////////////////////////////////////////////// // Define the events in this event group! virtual void OnAction(float x, float y) = 0; virtual float OnSum(float x, float y) = 0; ////////////////////////////////////////////////////////////////////////// }; typedef AZ::EBus< MyEventGroup > MyEventGroupBus; /** * Now implement our event handler. */ class MyEventHandler : public MyEventGroupBus::MultiHandler { public: int actionCalls; int sumCalls; MyEventHandler(MyEventGroupBus::BusIdType busId0, MyEventGroupBus::BusIdType busId1) : actionCalls(0) , sumCalls(0) { BusConnect(busId0); // connect to the specific bus BusConnect(busId1); // connect to the specific bus } ////////////////////////////////////////////////////////////////////////// // Implement some action on the events... void OnAction(float x, float y) override { AZ_Printf("UnitTest", "OnAction1(%.2f,%.2f) called\n", x, y); ++actionCalls; } float OnSum(float x, float y) override { float sum = x + y; AZ_Printf("UnitTest", "%.2f OnAction1(%.2f,%.2f) on called\n", sum, x, y); ++sumCalls; return sum; } ////////////////////////////////////////////////////////////////////////// }; } TEST_F(EBus, MultBusHandler) { using namespace MultBusHandler; { MyEventHandler meh0(0, 1); /// <-- Bind to bus 0 and 1 // Signal OnAction event on all buses EBUS_EVENT(MyEventGroupBus, OnAction, 1.0f, 2.0f); EXPECT_EQ(2, meh0.actionCalls); // Signal OnSum event EBUS_EVENT(MyEventGroupBus, OnSum, 2.0f, 5.0f); EXPECT_EQ(2, meh0.sumCalls); // Signal OnAction event on bus 0 EBUS_EVENT_ID(0, MyEventGroupBus, OnAction, 1.0f, 2.0f); EXPECT_EQ(3, meh0.actionCalls); // Signal OnAction event on bus 1 EBUS_EVENT_ID(1, MyEventGroupBus, OnAction, 1.0f, 2.0f); EXPECT_EQ(4, meh0.actionCalls); meh0.BusDisconnect(1); // we disconnect from receiving events on bus 1 EBUS_EVENT(MyEventGroupBus, OnAction, 1.0f, 2.0f); // this signal will NOT trigger only one call EXPECT_EQ(5, meh0.actionCalls); } } /** * */ namespace QueueMessageTest { class QueueTestEventsMultiBus : public EBusTraits { public: ////////////////////////////////////////////////////////////////////////// // EBusTraits overrides static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; typedef AZStd::mutex MutexType; typedef int BusIdType; static const bool EnableEventQueue = true; ////////////////////////////////////////////////////////////////////////// QueueTestEventsMultiBus() : m_callCount(0) {} virtual ~QueueTestEventsMultiBus() {} virtual void OnMessage() { m_callCount++; } int m_callCount; }; typedef AZ::EBus<QueueTestEventsMultiBus> QueueTestMultiBus; class QueueTestEventsSingleBus : public EBusTraits { public: ////////////////////////////////////////////////////////////////////////// // EBusTraits overrides typedef AZStd::mutex MutexType; static const bool EnableEventQueue = true; ////////////////////////////////////////////////////////////////////////// QueueTestEventsSingleBus() : m_callCount(0) {} virtual ~QueueTestEventsSingleBus() {} virtual void OnMessage() { m_callCount++; } int m_callCount; }; typedef AZ::EBus<QueueTestEventsSingleBus> QueueTestSingleBus; JobManager* m_jobManager = nullptr; JobContext* m_jobContext = nullptr; QueueTestMultiBus::Handler* m_multiHandler = nullptr; QueueTestSingleBus::Handler* m_singleHandler = nullptr; QueueTestMultiBus::BusPtr m_multiPtr = nullptr; void QueueMessage() { EBUS_QUEUE_EVENT_ID(0, QueueTestMultiBus, OnMessage); EBUS_QUEUE_EVENT(QueueTestSingleBus, OnMessage); } void QueueMessagePtr() { EBUS_QUEUE_EVENT_PTR(m_multiPtr, QueueTestMultiBus, OnMessage); EBUS_QUEUE_EVENT(QueueTestSingleBus, OnMessage); } } TEST_F(EBus, QueueMessage) { using namespace QueueMessageTest; // Setup AllocatorInstance<PoolAllocator>::Create(); AllocatorInstance<ThreadPoolAllocator>::Create(); JobManagerDesc jobDesc; JobManagerThreadDesc threadDesc; jobDesc.m_workerThreads.push_back(threadDesc); jobDesc.m_workerThreads.push_back(threadDesc); jobDesc.m_workerThreads.push_back(threadDesc); m_jobManager = aznew JobManager(jobDesc); m_jobContext = aznew JobContext(*m_jobManager); JobContext::SetGlobalContext(m_jobContext); m_multiHandler = new QueueTestMultiBus::Handler(); m_singleHandler = new QueueTestSingleBus::Handler(); m_singleHandler->m_callCount = 0; m_multiHandler->m_callCount = 0; const int NumCalls = 5000; QueueTestMultiBus::Bind(m_multiPtr, 0); m_multiHandler->BusConnect(0); m_singleHandler->BusConnect(); for (int i = 0; i < NumCalls; ++i) { Job* job = CreateJobFunction(&QueueMessageTest::QueueMessage, true); job->Start(); job = CreateJobFunction(&QueueMessageTest::QueueMessagePtr, true); job->Start(); } while (m_singleHandler->m_callCount < NumCalls * 2 || m_multiHandler->m_callCount < NumCalls * 2) { QueueTestMultiBus::ExecuteQueuedEvents(); QueueTestSingleBus::ExecuteQueuedEvents(); AZStd::this_thread::yield(); } // use queuing generic functions to disconnect from the bus // the same as m_singleHandler.BusDisconnect(); but delayed until QueueTestSingleBus::ExecuteQueuedEvents() QueueTestSingleBus::QueueFunction(&QueueTestSingleBus::Handler::BusDisconnect, m_singleHandler); // the same as m_multiHandler.BusDisconnect(); but dalayed until QueueTestMultiBus::ExecuteQueuedEvents(); EBUS_QUEUE_FUNCTION(QueueTestMultiBus, static_cast<void(QueueTestMultiBus::Handler::*)()>(&QueueTestMultiBus::Handler::BusDisconnect), m_multiHandler); EXPECT_EQ(1, QueueTestSingleBus::GetTotalNumOfEventHandlers()); EXPECT_EQ(1, QueueTestMultiBus::GetTotalNumOfEventHandlers()); QueueTestSingleBus::ExecuteQueuedEvents(); QueueTestMultiBus::ExecuteQueuedEvents(); EXPECT_EQ(0, QueueTestSingleBus::GetTotalNumOfEventHandlers()); EXPECT_EQ(0, QueueTestMultiBus::GetTotalNumOfEventHandlers()); // Cleanup delete m_singleHandler; delete m_multiHandler; m_multiPtr = nullptr; JobContext::SetGlobalContext(nullptr); delete m_jobContext; delete m_jobManager; AllocatorInstance<ThreadPoolAllocator>::Destroy(); AllocatorInstance<PoolAllocator>::Destroy(); } struct EBusRecursiveTest : public EBus { void RecursiveQueueFunction() { // This prevents the test from running forever in the failure case if (m_callCount < 10) { QueueMessageTest::QueueTestSingleBus::QueueFunction([this]() {RecursiveQueueFunction(); }); } ++m_callCount; } int m_callCount = 0; }; TEST_F(EBusRecursiveTest, QueueRecursiveMessage_OnlyExecutesOncePerCall) { using namespace QueueMessageTest; QueueTestSingleBus::ExecuteQueuedEvents(); QueueTestSingleBus::AllowFunctionQueuing(true); RecursiveQueueFunction(); QueueTestSingleBus::ExecuteQueuedEvents(); QueueTestSingleBus::ClearQueuedEvents(); ASSERT_EQ(m_callCount, 2); // 1 initial call + the queued call } class QueueEbusTest : public ScopedAllocatorSetupFixture { }; TEST_F(QueueEbusTest, QueueMessageNoQueueing_QueueMessage_Warning) { using namespace QueueMessageTest; { AZ::Test::AssertAbsorber assertAbsorber; QueueMessage(); EXPECT_EQ(assertAbsorber.m_warningCount, 0); } QueueTestSingleBus::ExecuteQueuedEvents(); QueueTestSingleBus::AllowFunctionQueuing(false); { AZ::Test::AssertAbsorber assertAbsorber; QueueMessage(); EXPECT_EQ(assertAbsorber.m_warningCount, 1); } QueueTestMultiBus::ExecuteQueuedEvents(); QueueTestSingleBus::AllowFunctionQueuing(true); } class ConnectDisconnectInterface : public EBusTraits { public: virtual ~ConnectDisconnectInterface() {} virtual void OnConnectChild() = 0; virtual void OnDisconnectMe() = 0; virtual void OnDisconnectAll() = 0; }; typedef AZ::EBus<ConnectDisconnectInterface> ConnectDisconnectBus; class ConnectDisconnectHandler : public ConnectDisconnectBus::Handler { ConnectDisconnectHandler* m_child; public: ConnectDisconnectHandler(ConnectDisconnectHandler* child) : m_child(child) { s_handlers.push_back(this); if (child != nullptr) // if we are the child don't connect yet { BusConnect(); } } ~ConnectDisconnectHandler() override { s_handlers.erase(AZStd::find(s_handlers.begin(), s_handlers.end(), this)); } void OnConnectChild() override { if (m_child) { m_child->BusConnect(); } } void OnDisconnectMe() override { BusDisconnect(); } void OnDisconnectAll() override { for (size_t i = 0; i < s_handlers.size(); ++i) { s_handlers[i]->BusDisconnect(); } } static AZStd::fixed_vector<ConnectDisconnectHandler*, 5> s_handlers; }; AZStd::fixed_vector<ConnectDisconnectHandler*, 5> ConnectDisconnectHandler::s_handlers; class ConnectDisconnectIdOrderedInterface : public EBusTraits { public: ////////////////////////////////////////////////////////////////////////// // EBus interface settings static const EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::MultipleAndOrdered; static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::ById; typedef int BusIdType; ////////////////////////////////////////////////////////////////////////// ConnectDisconnectIdOrderedInterface() : m_order(0) {} virtual ~ConnectDisconnectIdOrderedInterface() {} virtual void OnConnectChild() = 0; virtual void OnDisconnectMe() = 0; virtual void OnDisconnectAll(int busId) = 0; virtual bool Compare(const ConnectDisconnectIdOrderedInterface* rhs) const { return m_order < rhs->m_order; } int m_order; }; typedef AZ::EBus<ConnectDisconnectIdOrderedInterface> ConnectDisconnectIdOrderedBus; class ConnectDisconnectIdOrderedHandler : public ConnectDisconnectIdOrderedBus::Handler { public: ConnectDisconnectIdOrderedHandler(int id, int order, ConnectDisconnectIdOrderedHandler* child) : ConnectDisconnectIdOrderedBus::Handler() , m_child(child) , m_busId(id) { m_order = order; s_handlers.push_back(this); if (child != nullptr) // if we are the child don't connect yet { BusConnect(m_busId); } } ~ConnectDisconnectIdOrderedHandler() override { s_handlers.erase(AZStd::find(s_handlers.begin(), s_handlers.end(), this)); } void OnConnectChild() override { if (m_child) { m_child->BusConnect(m_busId); } } void OnDisconnectMe() override { BusDisconnect(); } void OnDisconnectAll(int busId) override { for (size_t i = 0; i < s_handlers.size(); ++i) { if (busId == -1 || busId == s_handlers[i]->m_busId) { s_handlers[i]->BusDisconnect(); } } } static AZStd::fixed_vector<ConnectDisconnectIdOrderedHandler*, 5> s_handlers; protected: ConnectDisconnectIdOrderedHandler* m_child; int m_busId; }; AZStd::fixed_vector<ConnectDisconnectIdOrderedHandler*, 5> ConnectDisconnectIdOrderedHandler::s_handlers; /** * Tests a bus when we allow to disconnect while executing messages. */ TEST_F(EBus, DisconnectInDispatch) { ConnectDisconnectHandler child(nullptr); EXPECT_EQ(0, ConnectDisconnectBus::GetTotalNumOfEventHandlers()); ConnectDisconnectHandler l(&child); EXPECT_EQ(1, ConnectDisconnectBus::GetTotalNumOfEventHandlers()); // Test connect in the during the message call EBUS_EVENT(ConnectDisconnectBus, OnConnectChild); // connect the child object EXPECT_EQ(2, ConnectDisconnectBus::GetTotalNumOfEventHandlers()); EBUS_EVENT(ConnectDisconnectBus, OnDisconnectAll); // Disconnect all members during a message EXPECT_EQ(0, ConnectDisconnectBus::GetTotalNumOfEventHandlers()); ConnectDisconnectIdOrderedHandler ch10(10, 1, nullptr); ConnectDisconnectIdOrderedHandler ch5(5, 20, nullptr); EXPECT_EQ(0, ConnectDisconnectIdOrderedBus::GetTotalNumOfEventHandlers()); ConnectDisconnectIdOrderedHandler pa10(10, 10, &ch10); ConnectDisconnectIdOrderedHandler pa20(20, 20, &ch5); EXPECT_EQ(2, ConnectDisconnectIdOrderedBus::GetTotalNumOfEventHandlers()); EBUS_EVENT(ConnectDisconnectIdOrderedBus, OnConnectChild); // connect the child object EXPECT_EQ(4, ConnectDisconnectIdOrderedBus::GetTotalNumOfEventHandlers()); // Disconnect all members from bus 10 (it will be sorted first) // This we we can test a bus removal while traversing EBUS_EVENT(ConnectDisconnectIdOrderedBus, OnDisconnectAll, 10); EXPECT_EQ(2, ConnectDisconnectIdOrderedBus::GetTotalNumOfEventHandlers()); // Now disconnect all buses EBUS_EVENT(ConnectDisconnectIdOrderedBus, OnDisconnectAll, -1); EXPECT_EQ(0, ConnectDisconnectIdOrderedBus::GetTotalNumOfEventHandlers()); } class DisconnectNextHandlerInterface : public AZ::EBusTraits { public: static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::MultipleAndOrdered; using BusIdType = int32_t; // Comparison function which always sorts to the end struct DisconnectNextHandlerLess { // Intrusive_multiset requires the first_argument_type parameter for it's comparison function, but it is deprecated in C++17 // This should be removed when C++17 support is added using first_argument_type = DisconnectNextHandlerInterface*; constexpr bool operator()(const DisconnectNextHandlerInterface*, const DisconnectNextHandlerInterface*) const { return false; } }; using BusHandlerOrderCompare = DisconnectNextHandlerLess; virtual void DisconnectNextHandler() = 0; }; using DisconnectNextHandlerBus = AZ::EBus<DisconnectNextHandlerInterface>; class DisconnectNextHandlerByIdImpl : public DisconnectNextHandlerBus::MultiHandler { public: void DisconnectNextHandler() override { if (m_nextHandler) { m_nextHandler->BusDisconnect(*DisconnectNextHandlerBus::GetCurrentBusId()); ++m_handlerDisconnectCounter; } } static constexpr int32_t firstBusAddress = 1; static constexpr int32_t secondBusAddress = 2; DisconnectNextHandlerByIdImpl* m_nextHandler{}; int32_t m_handlerDisconnectCounter{}; }; constexpr int32_t DisconnectNextHandlerByIdImpl::firstBusAddress; constexpr int32_t DisconnectNextHandlerByIdImpl::secondBusAddress; /** * Tests disconnecting the next handler within a bus during a dispatch */ TEST_F(EBus, DisconnectNextHandlerDuringDispatch_DoesNotCrash) { DisconnectNextHandlerByIdImpl multiHandler1; multiHandler1.BusConnect(DisconnectNextHandlerByIdImpl::firstBusAddress); multiHandler1.BusConnect(DisconnectNextHandlerByIdImpl::secondBusAddress); DisconnectNextHandlerByIdImpl multiHandler2; multiHandler2.BusConnect(DisconnectNextHandlerByIdImpl::firstBusAddress); multiHandler2.BusConnect(DisconnectNextHandlerByIdImpl::secondBusAddress); // Set the first handler m_nextHandler field to point to the second handler multiHandler1.m_nextHandler = &multiHandler2; // Disconnect the next handlers from the second bus address to catch any issues with the address hash_table iterators becoming invalidated DisconnectNextHandlerBus::Event(DisconnectNextHandlerByIdImpl::secondBusAddress, &DisconnectNextHandlerInterface::DisconnectNextHandler); EXPECT_EQ(1, multiHandler1.m_handlerDisconnectCounter); EXPECT_EQ(0, multiHandler2.m_handlerDisconnectCounter); DisconnectNextHandlerBus::Event(DisconnectNextHandlerByIdImpl::firstBusAddress, &DisconnectNextHandlerInterface::DisconnectNextHandler); EXPECT_EQ(2, multiHandler1.m_handlerDisconnectCounter); EXPECT_EQ(0, multiHandler2.m_handlerDisconnectCounter); } class DisconnectNextAddressInterface : public AZ::EBusTraits { public: static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ByIdAndOrdered; static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; using BusIdType = int32_t; struct BusIdOrderLess { constexpr bool operator()(BusIdType lhs, BusIdType rhs) const { return lhs < rhs; } }; using BusIdOrderCompare = BusIdOrderLess; virtual void DisconnectNextAddress() = 0; }; using DisconnectNextAddressBus = AZ::EBus<DisconnectNextAddressInterface>; class DisconnectNextAddressImpl : public DisconnectNextAddressBus::Handler { public: void DisconnectNextAddress() override { if (m_nextAddressHandler) { m_nextAddressHandler->BusDisconnect(); ++m_addressDisconnectCounter; } } static constexpr int32_t firstBusAddress = 1; static constexpr int32_t nextBusAddress = 2; DisconnectNextAddressImpl* m_nextAddressHandler{}; int32_t m_addressDisconnectCounter{}; }; constexpr int32_t DisconnectNextAddressImpl::firstBusAddress; constexpr int32_t DisconnectNextAddressImpl::nextBusAddress; /** * Tests disconnecting the next address within a bus during a dispatch */ TEST_F(EBus, DisconnectNextAddressDuringDispatch_DoesNotCrash) { DisconnectNextAddressImpl addressHandler1; addressHandler1.BusConnect(DisconnectNextAddressImpl::firstBusAddress); DisconnectNextAddressImpl addressHandler2; addressHandler2.BusConnect(DisconnectNextAddressImpl::nextBusAddress); addressHandler1.m_nextAddressHandler = &addressHandler2; // Disconnect the second address handler using the first address handler DisconnectNextAddressBus::Event(DisconnectNextAddressImpl::firstBusAddress, &DisconnectNextAddressInterface::DisconnectNextAddress); EXPECT_EQ(1, addressHandler1.m_addressDisconnectCounter); EXPECT_EQ(0, addressHandler2.m_addressDisconnectCounter); } /** * Test multiple handler. */ namespace MultiHandlerTest { class MyEventGroup : public AZ::EBusTraits { public: ////////////////////////////////////////////////////////////////////////// // EBus Settings static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::ById; typedef unsigned int BusIdType; ////////////////////////////////////////////////////////////////////////// virtual ~MyEventGroup() {} ////////////////////////////////////////////////////////////////////////// // Define the events in this event group! virtual void OnAction() = 0; ////////////////////////////////////////////////////////////////////////// }; typedef AZ::EBus<MyEventGroup> MyEventBus; class MultiHandler : public MyEventBus::MultiHandler { public: MultiHandler() : m_expectedCurrentId(0) , m_numCalls(0) {} void OnAction() override { const unsigned int* currentIdPtr = MyEventBus::GetCurrentBusId(); ASSERT_NE(nullptr, currentIdPtr); EXPECT_EQ(*currentIdPtr, m_expectedCurrentId); ++m_numCalls; } unsigned int m_expectedCurrentId; unsigned int m_numCalls; }; } TEST_F(EBus, MultiHandler) { using namespace MultiHandlerTest; MultiHandler ml; ml.BusConnect(10); ml.BusConnect(12); ml.BusConnect(13); // test copy handlers and make sure they attached to the same bus MultiHandler mlCopy = ml; EXPECT_EQ(0, mlCopy.m_numCalls); // Called outside of an even it should always return nullptr EXPECT_EQ(nullptr, MyEventBus::GetCurrentBusId()); EBUS_EVENT_ID(1, MyEventBus, OnAction); // this should not trigger a call EXPECT_EQ(0, ml.m_numCalls); // Issues calls which we listen for ml.m_expectedCurrentId = 10; mlCopy.m_expectedCurrentId = 10; EBUS_EVENT_ID(10, MyEventBus, OnAction); EXPECT_EQ(1, ml.m_numCalls); EXPECT_EQ(1, mlCopy.m_numCalls); // make sure the handler copy is connected mlCopy.BusDisconnect(); ml.m_expectedCurrentId = 12; EBUS_EVENT_ID(12, MyEventBus, OnAction); EXPECT_EQ(2, ml.m_numCalls); ml.m_expectedCurrentId = 13; EBUS_EVENT_ID(13, MyEventBus, OnAction); EXPECT_EQ(3, ml.m_numCalls); } // Non intrusive EBusTraits struct MyCustomTraits : public AZ::EBusTraits { // ... custom traits here }; /** * Interface that we don't own and we can't inherit traits */ class My3rdPartyInterface { public: virtual void SomeEvent(int a) = 0; }; // 3rd party interface (which is compliant with EBus requirements) typedef AZ::EBus<My3rdPartyInterface, MyCustomTraits> My3rdPartyBus1; // 3rd party interface that we want to wrap class My3rdPartyInterfaceWrapped : public My3rdPartyInterface , public AZ::EBusTraits { }; typedef AZ::EBus<My3rdPartyInterfaceWrapped> My3rdPartyBus2; // regular interface trough traits inheritance, please look at the all the samples above // combine an ebus and an interface, so you don't need any typedefs. You will need to specialize a template so the bus can get it's traits // Keep in mind that this type will not allow for interfaces to be extended, but it's ok for final interfaces class MyEBusInterface : public AZ::EBus<MyEBusInterface, MyCustomTraits> { public: virtual void Event(int a) const = 0; }; /** * Test and demonstrate different EBus implementations */ namespace ImplementationTest { class Handler1 : public My3rdPartyBus1::Handler { public: Handler1() : m_calls(0) { My3rdPartyBus1::Handler::BusConnect(); } int m_calls; private: void SomeEvent(int a) override { (void)a; ++m_calls; } }; class Handler2 : public My3rdPartyBus2::Handler { public: Handler2() : m_calls(0) { My3rdPartyBus2::Handler::BusConnect(); } int m_calls; private: void SomeEvent(int a) override { (void)a; ++m_calls; } }; class Handler3 : public MyEBusInterface::Handler { public: Handler3() : m_calls(0) { MyEBusInterface::Handler::BusConnect(); } mutable int m_calls; private: void Event(int a) const override { (void)a; ++m_calls; } }; } TEST_F(EBus, ExternalInterface) { using namespace ImplementationTest; Handler1 h1; Handler2 h2; Handler3 h3; // test copy of handler Handler1 h1Copy = h1; EXPECT_EQ(0, h1Copy.m_calls); EBUS_EVENT(My3rdPartyBus1, SomeEvent, 1); EXPECT_EQ(1, h1.m_calls); EXPECT_EQ(1, h1Copy.m_calls); // check that the copy works too EBUS_EVENT(My3rdPartyBus2, SomeEvent, 2); EXPECT_EQ(1, h2.m_calls); EBUS_EVENT(MyEBusInterface, Event, 3); EXPECT_EQ(1, h3.m_calls); } /** * */ TEST_F(EBus, Results) { // Test the result logical aggregator for OR { AZ::EBusLogicalResult<bool, AZStd::logical_or<bool> > or_false_false(false); or_false_false = false; or_false_false = false; EXPECT_FALSE(or_false_false.value); } { AZ::EBusLogicalResult<bool, AZStd::logical_or<bool> > or_true_false(false); or_true_false = true; or_true_false = false; EXPECT_TRUE(or_true_false.value); } // Test the result logical aggregator for AND { AZ::EBusLogicalResult<bool, AZStd::logical_and<bool> > and_true_false(true); and_true_false = true; and_true_false = false; EXPECT_FALSE(and_true_false.value); } { AZ::EBusLogicalResult<bool, AZStd::logical_and<bool> > and_true_true(true); and_true_true = true; and_true_true = true; EXPECT_TRUE(and_true_true.value); } } // Routers, Bridging and Versioning /** * EBusInterfaceV1, since we want to keep binary compatibility (we don't need to recompile) * when we are implementing the version messaging we should not change the V1 EBus, all code * should be triggered from the new version that is not compiled is customer's code yet. */ class EBusInterfaceV1 : public AZ::EBusTraits { public: virtual void OnEvent(int a) { (void)a; } }; using EBusVersion1 = AZ::EBus<EBusInterfaceV1>; /** * Version 2 of the interface which communicates with Version 1 of the bus bidirectionally. */ class EBusInterfaceV2 : public AZ::EBusTraits { public: /** * Router policy implementation that bridges two EBuses by default. * It this case we use it to implement versioning between V1 and V2 * of a specific EBus version. */ template<typename Bus> struct RouterPolicy : public EBusRouterPolicy<Bus> { struct V2toV1Router : public Bus::NestedVersionRouter { void OnEvent(int a, int b) override { if (!m_policy->m_isOnEventRouting) { m_policy->m_isOnEventRouting = true; this->template ForwardEvent<EBusVersion1>(&EBusVersion1::Events::OnEvent, a + b); m_policy->m_isOnEventRouting = false; } } typename Bus::RouterPolicy* m_policy = nullptr; }; struct V1toV2Router : public EBusVersion1::Router { void OnEvent(int a) override { if(!m_policy->m_isOnEventRouting) { m_policy->m_isOnEventRouting = true; this->template ForwardEvent<Bus>(&Bus::Events::OnEvent, a, 0); m_policy->m_isOnEventRouting = false; } } typename Bus::RouterPolicy* m_policy = nullptr; }; RouterPolicy() { m_v2toV1Router.m_policy = this; m_v1toV2Router.m_policy = this; m_v2toV1Router.BusRouterConnect(this->m_routers); m_v1toV2Router.BusRouterConnect(); } ~RouterPolicy() { m_v2toV1Router.BusRouterDisconnect(this->m_routers); m_v1toV2Router.BusRouterDisconnect(); } // State of current routed events to avoid loopbacks // this is NOT needed if we route only one way V2->V1 or V1->V2 bool m_isOnEventRouting = false; // Possible optimization, When we are dealing with version we usually don't expect to have active use of the old version, // it's just for compatibility. Having routers trying to route to old version busses that rarely // have listeners will have it's overhead. To reduct that we can add m_onDemandRouters list that // have a pointer to a router and oder, so we can automatically connect that router only when // listeners are attached to the old version of the bus. We are talking only about NewVersion->OldVersion // bridge (the opposite can be always connected as the overhead will be on the OldVersion bus which we don't expect to use much anyway). V2toV1Router m_v2toV1Router; V1toV2Router m_v1toV2Router; }; virtual void OnEvent(int a, int b) { (void)a; (void)b; } }; using EBusVersion2 = AZ::EBus<EBusInterfaceV2>; namespace RoutingTest { class DrillerInterceptor : public EBusVersion1::Router { public: void OnEvent(int a) override { EXPECT_EQ(1020, a); m_numOnEvent++; } int m_numOnEvent = 0; }; class V1EventRouter : public EBusVersion1::Router { public: void OnEvent(int a) override { (void)a; m_numOnEvent++; EBusVersion1::SetRouterProcessingState(m_processingState); } int m_numOnEvent = 0; EBusVersion1::RouterProcessingState m_processingState = EBusVersion1::RouterProcessingState::SkipListeners; }; class EBusVersion1Handler : public EBusVersion1::Handler { public: void OnEvent(int a) override { (void)a; m_numOnEvent++; } int m_numOnEvent = 0; }; class EBusVersion2Handler : public EBusVersion2::Handler { public: void OnEvent(int a, int b) override { (void)a; (void)b; m_numOnEvent++; } int m_numOnEvent = 0; }; } TEST_F(EBus, Routing) { using namespace RoutingTest; DrillerInterceptor driller; EBusVersion1Handler v1Handler; v1Handler.BusConnect(); driller.BusRouterConnect(); EBusVersion1::Broadcast(&EBusVersion1::Events::OnEvent, 1020); EXPECT_EQ(1, driller.m_numOnEvent); EXPECT_EQ(1, v1Handler.m_numOnEvent); driller.BusRouterDisconnect(); EBusVersion1::Broadcast(&EBusVersion1::Events::OnEvent, 1020); EXPECT_EQ(1, driller.m_numOnEvent); EXPECT_EQ(2, v1Handler.m_numOnEvent); // routing events { // reset counter v1Handler.m_numOnEvent = 0; V1EventRouter v1Router; v1Router.BusRouterConnect(); EBusVersion1::Broadcast(&EBusVersion1::Events::OnEvent, 1020); EXPECT_EQ(1, v1Router.m_numOnEvent); EXPECT_EQ(0, v1Handler.m_numOnEvent); v1Router.BusRouterDisconnect(); } // routing events and skipping further routing { // reset counter v1Handler.m_numOnEvent = 0; V1EventRouter v1RouterFirst, v1RouterSecond; v1RouterFirst.BusRouterConnect(-1); v1RouterSecond.BusRouterConnect(); EBusVersion1::Broadcast(&EBusVersion1::Events::OnEvent, 1020); EXPECT_EQ(1, v1RouterFirst.m_numOnEvent); EXPECT_EQ(1, v1RouterSecond.m_numOnEvent); EXPECT_EQ(0, v1Handler.m_numOnEvent); // now instruct router 1 to block any further event processing v1RouterFirst.m_processingState = EBusVersion1::RouterProcessingState::SkipListenersAndRouters; EBusVersion1::Broadcast(&EBusVersion1::Events::OnEvent, 1020); EXPECT_EQ(2, v1RouterFirst.m_numOnEvent); EXPECT_EQ(1, v1RouterSecond.m_numOnEvent); EXPECT_EQ(0, v1Handler.m_numOnEvent); } // test bridging two EBus by using routers. This can be used to handle different bus versions. { EBusVersion2Handler v2Handler; v2Handler.BusConnect(); // reset counter v1Handler.m_numOnEvent = 0; EBusVersion2::Broadcast(&EBusVersion2::Events::OnEvent, 10, 20); EXPECT_EQ(1, v1Handler.m_numOnEvent); EXPECT_EQ(1, v2Handler.m_numOnEvent); EBusVersion1::Broadcast(&EBusVersion1::Events::OnEvent, 30); EXPECT_EQ(2, v1Handler.m_numOnEvent); EXPECT_EQ(2, v2Handler.m_numOnEvent); } // We can test Queue and Event routing separately, // however they do use the same code path (as we don't queue routing and we just use the ID to differentiate between Broadcast and Event) } struct LocklessEvents : public AZ::EBusTraits { using MutexType = AZStd::mutex; static const bool LocklessDispatch = true; virtual ~LocklessEvents() = default; virtual void RemoveMe() = 0; virtual void DeleteMe() = 0; virtual void Calculate(int x, int y, int z) = 0; }; using LocklessBus = AZ::EBus<LocklessEvents>; struct LocklessImpl : public LocklessBus::Handler { uint32_t m_val; uint32_t m_maxSleep; LocklessImpl(uint32_t maxSleep = 0) : m_val(0) , m_maxSleep(maxSleep) { BusConnect(); } ~LocklessImpl() override { BusDisconnect(); } void RemoveMe() override { BusDisconnect(); } void DeleteMe() override { delete this; } void Calculate(int x, int y, int z) override { m_val = x + (y * z); if (m_maxSleep) { AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(m_val % m_maxSleep)); } } }; void ThrashLocklessDispatch(uint32_t maxSleep = 0) { const size_t threadCount = 8; enum : size_t { cycleCount = 1000 }; AZStd::thread threads[threadCount]; AZStd::vector<int> results[threadCount]; LocklessImpl handler(maxSleep); auto work = [maxSleep]() { char sentinel[64] = { 0 }; char* end = sentinel + AZ_ARRAY_SIZE(sentinel); for (int i = 1; i < cycleCount; ++i) { uint32_t ms = maxSleep ? rand() % maxSleep : 0; if (ms % 3) { AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(ms)); } LocklessBus::Broadcast(&LocklessBus::Events::Calculate, i, i * 2, i << 4); bool failed = (AZStd::find_if(&sentinel[0], end, [](char s) { return s != 0; }) != end); EXPECT_FALSE(failed); } }; for (AZStd::thread& thread : threads) { thread = AZStd::thread(work); } for (AZStd::thread& thread : threads) { thread.join(); } } TEST_F(EBus, ThrashLocklessDispatchYOLO) { ThrashLocklessDispatch(); } TEST_F(EBus, ThrashLocklessDispatchSimulateWork) { ThrashLocklessDispatch(4); } TEST_F(EBus, DisconnectInLocklessDispatch) { LocklessImpl handler; AZ_TEST_START_TRACE_SUPPRESSION; LocklessBus::Broadcast(&LocklessBus::Events::RemoveMe); AZ_TEST_STOP_TRACE_SUPPRESSION(1); } TEST_F(EBus, DeleteInLocklessDispatch) { LocklessImpl* handler = new LocklessImpl(); AZ_UNUSED(handler); AZ_TEST_START_TRACE_SUPPRESSION; LocklessBus::Broadcast(&LocklessBus::Events::DeleteMe); AZ_TEST_STOP_TRACE_SUPPRESSION(1); } namespace LocklessTest { struct LocklessConnectorEvents : public AZ::EBusTraits { using MutexType = AZStd::recursive_mutex; static const bool LocklessDispatch = true; static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::ById; typedef uint32_t BusIdType; virtual ~LocklessConnectorEvents() = default; virtual void DoConnect() = 0; virtual void DoDisconnect() = 0; }; using LocklessConnectorBus = AZ::EBus<LocklessConnectorEvents>; class MyEventGroup : public AZ::EBusTraits { public: using MutexType = AZStd::recursive_mutex; static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::ById; typedef uint32_t BusIdType; virtual void Calculate(int x, int y, int z) = 0; virtual ~MyEventGroup() {} }; using MyEventGroupBus = AZ::EBus< MyEventGroup >; struct DoubleEbusImpl : public LocklessConnectorBus::Handler, MyEventGroupBus::Handler { uint32_t m_id; uint32_t m_val; uint32_t m_maxSleep; DoubleEbusImpl(uint32_t id, uint32_t maxSleep) : m_id(id) , m_val(0) , m_maxSleep(maxSleep) { LocklessConnectorBus::Handler::BusConnect(m_id); } ~DoubleEbusImpl() override { MyEventGroupBus::Handler::BusDisconnect(); LocklessConnectorBus::Handler::BusDisconnect(); } void Calculate(int x, int y, int z) override { m_val = x + (y * z); if (m_maxSleep) { AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(m_val % m_maxSleep)); } } void DoConnect() override { MyEventGroupBus::Handler::BusConnect(m_id); } void DoDisconnect() override { MyEventGroupBus::Handler::BusDisconnect(); } }; } TEST_F(EBus, MixedLocklessTest) { using namespace LocklessTest; const int maxSleep = 5; const size_t threadCount = 8; enum : size_t { cycleCount = 500 }; AZStd::thread threads[threadCount]; AZStd::vector<int> results[threadCount]; AZStd::vector<DoubleEbusImpl> handlerList; for (int i = 0; i < threadCount; i++) { handlerList.emplace_back(i, maxSleep); } auto work = [maxSleep, threadCount]() { char sentinel[64] = { 0 }; char* end = sentinel + AZ_ARRAY_SIZE(sentinel); for (int i = 1; i < cycleCount; ++i) { uint32_t id = rand() % threadCount; LocklessConnectorBus::Event(id, &LocklessConnectorBus::Events::DoConnect); uint32_t ms = maxSleep ? rand() % maxSleep : 0; if (ms % 3) { AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(ms)); } MyEventGroupBus::Event(id, &MyEventGroupBus::Events::Calculate, i, i * 2, i << 4); LocklessConnectorBus::Event(id, &LocklessConnectorBus::Events::DoDisconnect); bool failed = (AZStd::find_if(&sentinel[0], end, [](char s) { return s != 0; }) != end); EXPECT_FALSE(failed); } }; for (AZStd::thread& thread : threads) { thread = AZStd::thread(work); } for (AZStd::thread& thread : threads) { thread.join(); } } namespace MultithreadConnect { class MyEventGroup : public AZ::EBusTraits { public: using MutexType = AZStd::recursive_mutex; virtual ~MyEventGroup() {} }; typedef AZ::EBus< MyEventGroup > MyEventGroupBus; struct MyEventGroupImpl : MyEventGroupBus::Handler { MyEventGroupImpl() { } ~MyEventGroupImpl() override { MyEventGroupBus::Handler::BusDisconnect(); } virtual void DoConnect() { MyEventGroupBus::Handler::BusConnect(); } virtual void DoDisconnect() { MyEventGroupBus::Handler::BusDisconnect(); } }; } TEST_F(EBus, MultithreadConnectTest) { using namespace MultithreadConnect; const int maxSleep = 5; const size_t threadCount = 8; enum : size_t { cycleCount = 1000 }; AZStd::thread threads[threadCount]; AZStd::vector<int> results[threadCount]; MyEventGroupImpl handler; auto work = [maxSleep, &handler]() { for (int i = 1; i < cycleCount; ++i) { handler.DoConnect(); uint32_t ms = maxSleep ? rand() % maxSleep : 0; if (ms % 3) { AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(ms)); } handler.DoDisconnect(); } }; for (AZStd::thread& thread : threads) { thread = AZStd::thread(work); } for (AZStd::thread& thread : threads) { thread.join(); } } struct LocklessNullMutexEvents : public AZ::EBusTraits { using MutexType = AZ::NullMutex; static const bool LocklessDispatch = true; virtual ~LocklessNullMutexEvents() = default; virtual void AtomicIncrement() = 0; }; using LocklessNullMutexBus = AZ::EBus<LocklessNullMutexEvents>; struct LocklessNullMutexImpl : public LocklessNullMutexBus::Handler { AZStd::atomic<uint64_t> m_val{}; LocklessNullMutexImpl() { BusConnect(); } ~LocklessNullMutexImpl() override { BusDisconnect(); } void AtomicIncrement() override { ++m_val; } }; void ThrashLocklessDispatchNullMutex() { constexpr size_t threadCount = 8; enum : size_t { cycleCount = 1000 }; constexpr uint64_t expectedAtomicCount = threadCount * cycleCount; AZStd::thread threads[threadCount]; LocklessNullMutexImpl handler; auto work = []() { for (int i = 0; i < cycleCount; ++i) { constexpr int maxSleep = 3; uint32_t ms = rand() % maxSleep; if (ms != 0) { AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(ms)); } LocklessNullMutexBus::Broadcast(&LocklessNullMutexBus::Events::AtomicIncrement); } }; for (AZStd::thread& thread : threads) { thread = AZStd::thread(work); } for (AZStd::thread& thread : threads) { thread.join(); } EXPECT_EQ(expectedAtomicCount, static_cast<uint64_t>(handler.m_val)); } TEST_F(EBus, LocklessDispatchWithNullMutex_Multithread_Thrash) { ThrashLocklessDispatchNullMutex(); } namespace EBusResultsTest { class ResultClass { public: int m_value1 = 0; int m_value2 = 0; bool m_operator_called_const = false; bool m_operator_called_rvalue_ref = false; ResultClass() = default; ResultClass(const ResultClass&) = default; bool operator==(const ResultClass& b) const { return m_value1 == b.m_value1 && m_value2 == b.m_value2; } ResultClass& operator=(const ResultClass& b) { m_value1 = b.m_value1 + m_value1; m_value2 = b.m_value2 + m_value2; m_operator_called_const = true; m_operator_called_rvalue_ref = b.m_operator_called_rvalue_ref; return *this; } ResultClass& operator=(ResultClass&& b) { // combine together to prove its not just an assignment m_value1 = b.m_value1 + m_value1; m_value2 = b.m_value2 + m_value2; // but destroy the original value (emulating move op) b.m_value1 = 0; b.m_value2 = 0; m_operator_called_rvalue_ref = true; m_operator_called_const = b.m_operator_called_const; return *this; } }; class ResultReducerClass { public: bool m_operator_called_const = false; bool m_operator_called_rvalue_ref = false; ResultClass operator()(const ResultClass& a, const ResultClass& b) { ResultClass newValue; newValue.m_value1 = a.m_value1 + b.m_value1; newValue.m_value2 = a.m_value2 + b.m_value2; m_operator_called_const = true; return newValue; } ResultClass operator()(const ResultClass& a, ResultClass&& b) { m_operator_called_rvalue_ref = true; ResultClass newValue; newValue.m_value1 = a.m_value1 + b.m_value1; newValue.m_value2 = a.m_value2 + b.m_value2; return newValue; } }; class MyInterface { public: virtual ResultClass EventX() = 0; virtual const ResultClass& EventY() = 0; }; using MyInterfaceBus = AZ::EBus<MyInterface, AZ::EBusTraits>; class MyListener : public MyInterfaceBus::Handler { public: MyListener(int value1, int value2) { m_result.m_value1 = value1; m_result.m_value2 = value2; } ~MyListener() override { } ResultClass EventX() override { return m_result; } const ResultClass& EventY() override { return m_result; } ResultClass m_result; }; } // EBusResultsTest TEST_F(EBus, ResultsTest) { using namespace EBusResultsTest; MyListener val1(1, 2); MyListener val2(3, 4); val1.BusConnect(); val2.BusConnect(); { ResultClass results; MyInterfaceBus::BroadcastResult(results, &MyInterfaceBus::Events::EventX); // ensure that the RVALUE-REF op was called: EXPECT_FALSE(results.m_operator_called_const); EXPECT_TRUE(results.m_operator_called_rvalue_ref); EXPECT_EQ(results.m_value1, 4); // 1 + 3 EXPECT_EQ(results.m_value2, 6); // 2 + 4 // make sure originals are not destroyed EXPECT_EQ(val1.m_result.m_value1, 1); EXPECT_EQ(val1.m_result.m_value2, 2); EXPECT_EQ(val2.m_result.m_value1, 3); EXPECT_EQ(val2.m_result.m_value2, 4); } { ResultClass results; MyInterfaceBus::BroadcastResult(results, &MyInterfaceBus::Events::EventY); // ensure that the const version of operator= was called. EXPECT_TRUE(results.m_operator_called_const); EXPECT_FALSE(results.m_operator_called_rvalue_ref); EXPECT_EQ(results.m_value1, 4); // 1 + 3 EXPECT_EQ(results.m_value2, 6); // 2 + 4 // make sure originals are not destroyed EXPECT_EQ(val1.m_result.m_value1, 1); EXPECT_EQ(val1.m_result.m_value2, 2); EXPECT_EQ(val2.m_result.m_value1, 3); EXPECT_EQ(val2.m_result.m_value2, 4); } val1.BusDisconnect(); val2.BusDisconnect(); } // ensure RVALUE-REF move on RHS does not corrupt existing values. TEST_F(EBus, ResultsTest_ReducerCorruption) { using namespace EBusResultsTest; MyListener val1(1, 2); MyListener val2(3, 4); val1.BusConnect(); val2.BusConnect(); { EBusReduceResult<ResultClass, ResultReducerClass> resultreducer; MyInterfaceBus::BroadcastResult(resultreducer, &MyInterfaceBus::Events::EventX); EXPECT_FALSE(resultreducer.unary.m_operator_called_const); EXPECT_TRUE(resultreducer.unary.m_operator_called_rvalue_ref); // note that operator= is called TWICE here. one on (val1+val2) // because the ebus results is defined as "value = unary(a, b)" // and in this case both operator = as well as the unary operate here. // meaning that the addition is actually run multiple times // once for (a+b) and then again, during value = unary(...) for a second time EXPECT_EQ(resultreducer.value.m_value1, 7); // (3 + 1) + 3 EXPECT_EQ(resultreducer.value.m_value2, 10); // (4 + 2) + 4 // make sure originals are not destroyed in the move EXPECT_EQ(val1.m_result.m_value1, 1); EXPECT_EQ(val1.m_result.m_value2, 2); EXPECT_EQ(val2.m_result.m_value1, 3); EXPECT_EQ(val2.m_result.m_value2, 4); } { EBusReduceResult<ResultClass, ResultReducerClass> resultreducer; MyInterfaceBus::BroadcastResult(resultreducer, &MyInterfaceBus::Events::EventY); EXPECT_TRUE(resultreducer.unary.m_operator_called_const); // we expect the const version to have been called this time EXPECT_FALSE(resultreducer.unary.m_operator_called_rvalue_ref); EXPECT_EQ(resultreducer.value.m_value1, 7); // (3 + 1) + 3 EXPECT_EQ(resultreducer.value.m_value2, 10); // (4 + 2) + 4 // make sure originals are not destroyed in the move EXPECT_EQ(val1.m_result.m_value1, 1); EXPECT_EQ(val1.m_result.m_value2, 2); EXPECT_EQ(val2.m_result.m_value1, 3); EXPECT_EQ(val2.m_result.m_value2, 4); } val1.BusDisconnect(); val2.BusDisconnect(); } // ensure RVALUE-REF move on RHS does not corrupt existing values and operates correctly // even if the other form is used (where T is T&) TEST_F(EBus, ResultsTest_ReducerCorruption_Ref) { using namespace EBusResultsTest; MyListener val1(1, 2); MyListener val2(3, 4); val1.BusConnect(); val2.BusConnect(); { ResultClass finalResult; EBusReduceResult<ResultClass&, ResultReducerClass> resultreducer(finalResult); MyInterfaceBus::BroadcastResult(resultreducer, &MyInterfaceBus::Events::EventX); EXPECT_FALSE(resultreducer.unary.m_operator_called_const); EXPECT_TRUE(resultreducer.unary.m_operator_called_rvalue_ref); EXPECT_FALSE(finalResult.m_operator_called_const); EXPECT_TRUE(finalResult.m_operator_called_rvalue_ref); EXPECT_EQ(resultreducer.value.m_value1, 7); // (3 + 1) + 3 EXPECT_EQ(resultreducer.value.m_value2, 10); // (4 + 2) + 4 // make sure originals are not destroyed in the move EXPECT_EQ(val1.m_result.m_value1, 1); EXPECT_EQ(val1.m_result.m_value2, 2); EXPECT_EQ(val2.m_result.m_value1, 3); EXPECT_EQ(val2.m_result.m_value2, 4); } { ResultClass finalResult; EBusReduceResult<ResultClass&, ResultReducerClass> resultreducer(finalResult); MyInterfaceBus::BroadcastResult(resultreducer, &MyInterfaceBus::Events::EventY); EXPECT_TRUE(resultreducer.unary.m_operator_called_const); // EventY is const, so we expect this to have happened again EXPECT_FALSE(resultreducer.unary.m_operator_called_rvalue_ref); // we still expect the actual finalresult to have been populated via RVALUE REF MOVE EXPECT_FALSE(finalResult.m_operator_called_const); EXPECT_TRUE(finalResult.m_operator_called_rvalue_ref); EXPECT_EQ(resultreducer.value.m_value1, 7); // (3 + 1) + 3 EXPECT_EQ(resultreducer.value.m_value2, 10); // (4 + 2) + 4 // make sure originals are not destroyed in the move EXPECT_EQ(val1.m_result.m_value1, 1); EXPECT_EQ(val1.m_result.m_value2, 2); EXPECT_EQ(val2.m_result.m_value1, 3); EXPECT_EQ(val2.m_result.m_value2, 4); } val1.BusDisconnect(); val2.BusDisconnect(); } // ensure RVALUE-REF move on RHS does not corrupt existing values. TEST_F(EBus, ResultsTest_AggregatorCorruption) { using namespace EBusResultsTest; MyListener val1(1, 2); MyListener val2(3, 4); val1.BusConnect(); val2.BusConnect(); { EBusAggregateResults<ResultClass> resultarray; MyInterfaceBus::BroadcastResult(resultarray, &MyInterfaceBus::Events::EventX); EXPECT_EQ(resultarray.values.size(), 2); // bus connection is unordered, so we just have to find the two values on it, can't assume they're in same order. EXPECT_TRUE(resultarray.values[0] == val1.m_result || resultarray.values[1] == val1.m_result); EXPECT_TRUE(resultarray.values[0] == val2.m_result || resultarray.values[1] == val2.m_result); if (resultarray.values[0] == val1.m_result) { EXPECT_EQ(resultarray.values[1], val2.m_result); } if (resultarray.values[0] == val2.m_result) { EXPECT_EQ(resultarray.values[1], val1.m_result); } // make sure originals are not destroyed in the move EXPECT_EQ(val1.m_result.m_value1, 1); EXPECT_EQ(val1.m_result.m_value2, 2); EXPECT_EQ(val2.m_result.m_value1, 3); EXPECT_EQ(val2.m_result.m_value2, 4); } { EBusAggregateResults<ResultClass> resultarray; MyInterfaceBus::BroadcastResult(resultarray, &MyInterfaceBus::Events::EventY); // bus connection is unordered, so we just have to find the two values on it, can't assume they're in same order. EXPECT_TRUE(resultarray.values[0] == val1.m_result || resultarray.values[1] == val1.m_result); EXPECT_TRUE(resultarray.values[0] == val2.m_result || resultarray.values[1] == val2.m_result); if (resultarray.values[0] == val1.m_result) { EXPECT_EQ(resultarray.values[1], val2.m_result); } if (resultarray.values[0] == val2.m_result) { EXPECT_EQ(resultarray.values[1], val1.m_result); } // make sure originals are not destroyed EXPECT_EQ(val1.m_result.m_value1, 1); EXPECT_EQ(val1.m_result.m_value2, 2); EXPECT_EQ(val2.m_result.m_value1, 3); EXPECT_EQ(val2.m_result.m_value2, 4); } val1.BusDisconnect(); val2.BusDisconnect(); } namespace EBusEnvironmentTest { class MyInterface { public: virtual void EventX() = 0; }; using MyInterfaceBus = AZ::EBus<MyInterface, AZ::EBusTraits>; class MyInterfaceListener : public MyInterfaceBus::Handler { public: MyInterfaceListener(int environmentId = -1) : m_environmentId(environmentId) , m_numEventsX(0) { } void EventX() override { ++m_numEventsX; } int m_environmentId; ///< EBus environment id. -1 is global, otherwise index in the environment array. int m_numEventsX; }; class ParallelSeparateEBusEnvironmentProcessor { public: using JobaToProcessArray = AZStd::vector<ParallelSeparateEBusEnvironmentProcessor, AZ::OSStdAllocator>; ParallelSeparateEBusEnvironmentProcessor() { m_busEvironment = AZ::EBusEnvironment::Create(); } ~ParallelSeparateEBusEnvironmentProcessor() { AZ::EBusEnvironment::Destroy(m_busEvironment); } void ProcessSomethingInParallel(size_t jobId) { m_busEvironment->ActivateOnCurrentThread(); EXPECT_EQ(0, MyInterfaceBus::GetTotalNumOfEventHandlers()); // If environments are properly separated we should have no listeners!" MyInterfaceListener uniqueListener((int)jobId); uniqueListener.BusConnect(); const int numEventsToBroadcast = 100; for (int i = 0; i < numEventsToBroadcast; ++i) { // from now on all EBus calls happen in unique environment MyInterfaceBus::Broadcast(&MyInterfaceBus::Events::EventX); } uniqueListener.BusDisconnect(); // Test that we have only X num events EXPECT_EQ(uniqueListener.m_numEventsX, numEventsToBroadcast); // If environments are properly separated we should get only the events from our environment! m_busEvironment->DeactivateOnCurrentThread(); } static void ProcessJobsRange(JobaToProcessArray* jobs, size_t startIndex, size_t endIndex) { for (size_t i = startIndex; i <= endIndex; ++i) { (*jobs)[i].ProcessSomethingInParallel(i); } } AZ::EBusEnvironment* m_busEvironment; }; } // EBusEnvironmentTest TEST_F(EBus, EBusEnvironment) { using namespace EBusEnvironmentTest; ParallelSeparateEBusEnvironmentProcessor::JobaToProcessArray jobsToProcess; jobsToProcess.resize(10000); MyInterfaceListener globalListener; globalListener.BusConnect(); // broadcast on global bus MyInterfaceBus::Broadcast(&MyInterfaceBus::Events::EventX); // spawn a few threads to process those jobs AZStd::thread thread1(AZStd::bind(&ParallelSeparateEBusEnvironmentProcessor::ProcessJobsRange, &jobsToProcess, 0, 1999)); AZStd::thread thread2(AZStd::bind(&ParallelSeparateEBusEnvironmentProcessor::ProcessJobsRange, &jobsToProcess, 2000, 3999)); AZStd::thread thread3(AZStd::bind(&ParallelSeparateEBusEnvironmentProcessor::ProcessJobsRange, &jobsToProcess, 4000, 5999)); AZStd::thread thread4(AZStd::bind(&ParallelSeparateEBusEnvironmentProcessor::ProcessJobsRange, &jobsToProcess, 6000, 7999)); AZStd::thread thread5(AZStd::bind(&ParallelSeparateEBusEnvironmentProcessor::ProcessJobsRange, &jobsToProcess, 8000, 9999)); thread5.join(); thread4.join(); thread3.join(); thread2.join(); thread1.join(); globalListener.BusDisconnect(); EXPECT_EQ(1, globalListener.m_numEventsX); // If environments are properly separated we should get only the events the global/default Environment! } // Test disconnecting while in ConnectionPolicy class BusWithConnectionPolicy : public AZ::EBusTraits { public: virtual ~BusWithConnectionPolicy() = default; virtual void MessageWhichOccursDuringConnect() = 0; template<class Bus> struct ConnectionPolicy : public AZ::EBusConnectionPolicy<Bus> { static void Connect(typename Bus::BusPtr&, typename Bus::Context&, typename Bus::HandlerNode& handler, typename Bus::Context::ConnectLockGuard& , const typename Bus::BusIdType&) { handler->MessageWhichOccursDuringConnect(); } }; }; using BusWithConnectionPolicyBus = AZ::EBus<BusWithConnectionPolicy>; class HandlerWhichDisconnectsDuringDelivery : public BusWithConnectionPolicyBus::Handler { void MessageWhichOccursDuringConnect() override { BusDisconnect(); } }; TEST_F(EBus, ConnectionPolicy_DisconnectDuringDelivery) { HandlerWhichDisconnectsDuringDelivery handlerTest; handlerTest.BusConnect(); } class BusWithConnectionPolicyUnlocksBeforeHandler : public AZ::EBusTraits { public: using MutexType = AZStd::recursive_mutex; virtual ~BusWithConnectionPolicyUnlocksBeforeHandler() = default; virtual int GetPreUnlockDelay() const { return 0; } virtual int GetPostUnlockDelay() const { return 0; } virtual bool ShouldUnlock() const { return true; } virtual void MessageWhichOccursDuringConnect() = 0; template<class Bus> struct ConnectionPolicy : public AZ::EBusConnectionPolicy<Bus> { static void Connect(typename Bus::BusPtr&, typename Bus::Context&, typename Bus::HandlerNode& handler, typename Bus::Context::ConnectLockGuard& connectLock, const typename Bus::BusIdType&) { AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(handler->GetPreUnlockDelay())); if (handler->ShouldUnlock()) { connectLock.unlock(); } AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(handler->GetPostUnlockDelay())); handler->MessageWhichOccursDuringConnect(); } }; }; using BusWithConnectionPolicyUnlocksBus = AZ::EBus<BusWithConnectionPolicyUnlocksBeforeHandler>; class DelayUnlockHandler : public BusWithConnectionPolicyUnlocksBus::Handler { public: DelayUnlockHandler() = default; DelayUnlockHandler(int preDelay, int postDelay) : m_preDelay(preDelay), m_postDelay(postDelay) { } void MessageWhichOccursDuringConnect() override { if (m_connectMethod) { m_connectMethod(); } m_didConnect = true; } int GetPreUnlockDelay() const override { return m_preDelay; } int GetPostUnlockDelay() const override { return m_postDelay; } bool ShouldUnlock() const override { return m_shouldUnlock; } bool m_shouldUnlock{ true }; AZStd::atomic_bool m_didConnect{ false }; int m_preDelay{ 0 }; int m_postDelay{ 0 }; AZStd::function<void()> m_connectMethod; }; TEST_F(EBus, ConnectionPolicy_DisconnectDuringDeliveryUnlocked_Success) { DelayUnlockHandler handlerTest; handlerTest.m_connectMethod = [&handlerTest]() { handlerTest.BusDisconnect(); }; handlerTest.BusConnect(); EXPECT_EQ(handlerTest.m_didConnect, true); } TEST_F(EBus, ConnectionPolicy_DisconnectDuringDeliveryDelayUnlocked_Success) { constexpr int numTests = 100; for (int disconnectTest = 0; disconnectTest < numTests; ++disconnectTest) { DelayUnlockHandler handlerTest(0, 1); handlerTest.BusConnect(); AZStd::thread disconnectThread([&handlerTest]() { handlerTest.BusDisconnect(); } ); disconnectThread.join(); EXPECT_EQ(handlerTest.m_didConnect, true); } } TEST_F(EBus, ConnectionPolicy_DisconnectDuringDeliveryPreDelayUnlocked_Success) { constexpr int numTests = 100; for (int disconnectTest = 0; disconnectTest < numTests; ++disconnectTest) { DelayUnlockHandler handlerTest(1, 0); handlerTest.BusConnect(); AZStd::thread disconnectThread([&handlerTest]() { handlerTest.BusDisconnect(); } ); disconnectThread.join(); EXPECT_EQ(handlerTest.m_didConnect, true); } } TEST_F(EBus, ConnectionPolicy_WaitOnSecondHandlerWhileStillLocked_CantComplete) { DelayUnlockHandler waitHandler; // Test without releasing the lock - this is expected to prevent our second handler from connecting // so will block this thread waitHandler.m_shouldUnlock = false; DelayUnlockHandler connectHandler; waitHandler.m_connectMethod = [&connectHandler]() { constexpr int waitMsMax = 100; auto startTime = AZStd::chrono::system_clock::now(); auto endTime = startTime + AZStd::chrono::milliseconds(waitMsMax); // The other bus should not be able to complete because we're still holding the connect lock while (AZStd::chrono::system_clock::now() < endTime && !connectHandler.BusIsConnected()) { AZStd::this_thread::yield(); } EXPECT_GE(AZStd::chrono::system_clock::now(), endTime); }; AZStd::thread connectThread([&connectHandler, &waitHandler]() { constexpr int waitMsMax = 100; auto startTime = AZStd::chrono::system_clock::now(); auto endTime = startTime + AZStd::chrono::milliseconds(waitMsMax); while (AZStd::chrono::system_clock::now() < endTime && !waitHandler.m_didConnect) { AZStd::this_thread::yield(); } connectHandler.BusConnect(); } ); waitHandler.BusConnect(); connectThread.join(); EXPECT_EQ(connectHandler.m_didConnect, true); EXPECT_EQ(waitHandler.m_didConnect, true); waitHandler.BusDisconnect(); connectHandler.BusDisconnect(); } TEST_F(EBus, ConnectionPolicy_WaitOnSecondHandlerWhileUnlocked_CanComplete) { constexpr int numTests = 20; for (int connectTest = 0; connectTest < numTests; ++connectTest) { DelayUnlockHandler waitHandler; DelayUnlockHandler connectHandler; waitHandler.m_connectMethod = [&connectHandler]() { constexpr int waitMsMax = 100; auto startTime = AZStd::chrono::system_clock::now(); auto endTime = startTime + AZStd::chrono::milliseconds(waitMsMax); // The other bus should be able to connect while (AZStd::chrono::system_clock::now() < endTime && !connectHandler.m_didConnect) { AZStd::this_thread::yield(); } EXPECT_EQ(connectHandler.BusIsConnected(), true); EXPECT_LE(AZStd::chrono::system_clock::now(), endTime); }; AZStd::thread connectThread([&connectHandler]() { connectHandler.BusConnect(); } ); waitHandler.BusConnect(); connectThread.join(); EXPECT_EQ(connectHandler.m_didConnect, true); EXPECT_EQ(waitHandler.m_didConnect, true); waitHandler.BusDisconnect(); connectHandler.BusDisconnect(); } } class IdBusWithConnectionPolicy : public AZ::EBusTraits { public: static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; using BusIdType = int64_t; virtual ~IdBusWithConnectionPolicy() = default; virtual void MessageWhichOccursDuringConnect() = 0; virtual void MessageWhichOccursDuringDisconnect() = 0; template<class Bus> struct ConnectionPolicy : public AZ::EBusConnectionPolicy<Bus> { static void Connect(typename Bus::BusPtr&, typename Bus::Context&, typename Bus::HandlerNode& handler, typename Bus::Context::ConnectLockGuard& , const typename Bus::BusIdType&) { handler->MessageWhichOccursDuringConnect(); } static void Disconnect(typename Bus::Context& context, typename Bus::HandlerNode& handler, typename Bus::BusPtr& ptr) { handler->MessageWhichOccursDuringDisconnect(); } }; }; using IdBusWithConnectionPolicyBus = AZ::EBus<IdBusWithConnectionPolicy>; class MultiHandlerWhichDisconnectsDuringDelivery : public IdBusWithConnectionPolicyBus::MultiHandler { void MessageWhichOccursDuringConnect() override { auto busIdRef = IdBusWithConnectionPolicyBus::GetCurrentBusId(); EXPECT_NE(nullptr, busIdRef); BusDisconnect(*busIdRef); } void MessageWhichOccursDuringDisconnect() override { auto busIdRef = IdBusWithConnectionPolicyBus::GetCurrentBusId(); EXPECT_NE(nullptr, busIdRef); } }; static constexpr int64_t multiHandlerTestBusId = 42; TEST_F(EBus, MultiHandlerConnectionPolicy_DisconnectDuringDelivery) { MultiHandlerWhichDisconnectsDuringDelivery multiHandlerTest; multiHandlerTest.BusConnect(multiHandlerTestBusId); EXPECT_EQ(0U, IdBusWithConnectionPolicyBus::GetTotalNumOfEventHandlers()); } class BusWithConnectionAndDisconnectPolicy : public AZ::EBusTraits { public: static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; using BusIdType = int32_t; virtual ~BusWithConnectionAndDisconnectPolicy() = default; virtual void MessageWhichOccursDuringConnect(int32_t id) = 0; virtual void MessageWhichOccursDuringDisconnect(int32_t id) = 0; template<class Bus> struct ConnectionPolicy : public AZ::EBusConnectionPolicy<Bus> { static void Connect(typename Bus::BusPtr& ptr, typename Bus::Context& context, typename Bus::HandlerNode& handler, typename Bus::Context::ConnectLockGuard& , const typename Bus::BusIdType& id) { EXPECT_EQ(handler.GetBusId(), id); EXPECT_EQ(handler.m_holder, ptr); handler->MessageWhichOccursDuringConnect(handler.GetBusId()); } static void Disconnect(typename Bus::Context& context, typename Bus::HandlerNode& handler, typename Bus::BusPtr& ptr) { EXPECT_EQ(handler.m_holder, ptr); handler->MessageWhichOccursDuringDisconnect(handler.GetBusId()); } }; }; using BusWithConnectionAndDisconnectPolicyBus = AZ::EBus<BusWithConnectionAndDisconnectPolicy>; struct HandlerTrackingConnectionDisconnectionIds : public BusWithConnectionAndDisconnectPolicyBus::Handler { void MessageWhichOccursDuringConnect(int32_t id) override { m_lastConnectId = id; } void MessageWhichOccursDuringDisconnect(int32_t id) override { m_lastDisconnectId = id; } int32_t m_lastConnectId = 0; int32_t m_lastDisconnectId = 0; }; TEST_F(EBus, ConnectionPolicy_ConnectDisconnect_CorrectIds) { HandlerTrackingConnectionDisconnectionIds idsHandler; EXPECT_EQ(idsHandler.m_lastConnectId, 0); EXPECT_EQ(idsHandler.m_lastDisconnectId, 0); idsHandler.BusConnect(123); EXPECT_TRUE(idsHandler.BusIsConnectedId(123)); EXPECT_EQ(idsHandler.m_lastConnectId, 123); idsHandler.BusDisconnect(123); EXPECT_FALSE(idsHandler.BusIsConnectedId(123)); EXPECT_EQ(idsHandler.m_lastDisconnectId, 123); idsHandler.BusConnect(234); EXPECT_TRUE(idsHandler.BusIsConnectedId(234)); EXPECT_EQ(idsHandler.m_lastConnectId, 234); idsHandler.BusDisconnect(); EXPECT_FALSE(idsHandler.BusIsConnectedId(234)); EXPECT_EQ(idsHandler.m_lastDisconnectId, 234); } struct LastHandlerDisconnectInterface : public AZ::EBusTraits { static const EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Multiple; static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::ById; typedef size_t BusIdType; virtual void OnEvent() = 0; }; using LastHandlerDisconnectBus = AZ::EBus<LastHandlerDisconnectInterface>; struct LastHandlerDisconnectHandler : public LastHandlerDisconnectBus::Handler { void OnEvent() override { ++m_numOnEvents; BusDisconnect(); } unsigned int m_numOnEvents = 0; }; TEST_F(EBus, LastHandlerDisconnectForward) { LastHandlerDisconnectHandler lastHandler; lastHandler.BusConnect(0); EBUS_EVENT_ID(0, LastHandlerDisconnectBus, OnEvent); EXPECT_FALSE(lastHandler.BusIsConnected()); EXPECT_EQ(1, lastHandler.m_numOnEvents); } TEST_F(EBus, LastHandlerDisconnectReverse) { LastHandlerDisconnectHandler lastHandler; lastHandler.BusConnect(0); EBUS_EVENT_ID_REVERSE(0, LastHandlerDisconnectBus, OnEvent); EXPECT_FALSE(lastHandler.BusIsConnected()); EXPECT_EQ(1, lastHandler.m_numOnEvents); } struct DisconnectAssertInterface : public AZ::EBusTraits { using MutexType = AZStd::recursive_mutex; virtual ~DisconnectAssertInterface() = default; virtual void OnEvent() {}; }; using DisconnectAssertBus = AZ::EBus<DisconnectAssertInterface>; struct DisconnectAssertHandler : public DisconnectAssertBus::Handler { }; TEST_F(EBus, HandlerDestroyedWithoutDisconnect_Asserts) { // EBus handlers with a non-NullMutex assert on disconnect if they have not been explicitly disconnected before the internal EBus::Handler destructor is invoked. // The reason for the assert is because the BusDisconnect call will lock the EBus context mutex to safely disconnect the handler, but if the handler is still // connected to the EBus, another thread could access it after the vtable for the derived class has been reset. AZ_TEST_START_TRACE_SUPPRESSION; { DisconnectAssertHandler handler; handler.BusConnect(); } AZ_TEST_STOP_TRACE_SUPPRESSION(1); } TEST_F(EBus, HandlerDestroyedAfterDisconnect_DoesNotAssert) { { DisconnectAssertHandler handler; handler.BusConnect(); handler.BusDisconnect(); } } } #if defined(HAVE_BENCHMARK) //------------------------------------------------------------------------- // PERF TESTS //------------------------------------------------------------------------- namespace Benchmark { namespace BenchmarkSettings { namespace { // How many addresses/handlers count as "many" static const int Many = 1000; } void Common(::benchmark::internal::Benchmark* benchmark) { benchmark ->Unit(::benchmark::kNanosecond) ; } void OneToOne(::benchmark::internal::Benchmark* benchmark) { Common(benchmark); benchmark ->ArgNames({ { "Addresses" },{ "Handlers" } }) ->Args({ 0, 0 }) ->Args({ 1, 1 }) ; } void OneToMany(::benchmark::internal::Benchmark* benchmark) { OneToOne(benchmark); benchmark ->Args({ 1, Many }) ; } void ManyToOne(::benchmark::internal::Benchmark* benchmark) { OneToOne(benchmark); benchmark ->Args({ Many, 1 }) ; } void ManyToMany(::benchmark::internal::Benchmark* benchmark) { OneToOne(benchmark); benchmark ->Args({ 1, Many }) ->Args({ Many, 1 }) ->Args({ Many, Many }) ; } // Expected that this will be called after one of the above, so Common not called void Multithreaded(::benchmark::internal::Benchmark* benchmark) { benchmark ->ThreadRange(1, 8) ->ThreadPerCpu(); ; } } // AZ Benchmark environment used to initialize all EBus Handlers and then shared them with each benchmark test template<typename Bus> class BM_EBusEnvironment : public AZ::Test::BenchmarkEnvironmentBase { public: using BusType = Bus; using HandlerT = Handler<Bus>; BM_EBusEnvironment() { } void SetUp() { // Create the SystemAllocator if not available if(!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady()) { AZ::AllocatorInstance<AZ::SystemAllocator>::Create(); m_ownsSystemAllocator = true; } // Created the container for the EBusHandlers m_handlers = std::make_unique<std::vector<HandlerT>>(); // Connect handlers constexpr bool multiAddress = Bus::Traits::AddressPolicy != AZ::EBusAddressPolicy::Single; constexpr bool multiHandler = Bus::Traits::HandlerPolicy != AZ::EBusHandlerPolicy::Single; constexpr int64_t numAddresses{ multiAddress ? BenchmarkSettings::Many : 1 }; constexpr int64_t numHandlers{ multiHandler ? BenchmarkSettings::Many : 1 }; constexpr bool connectOnConstruct{ false }; AZ::BetterPseudoRandom random; m_handlers->reserve(numAddresses * numHandlers); for (int64_t address = 0; address < numAddresses; ++address) { for (int64_t handler = 0; handler < numHandlers; ++handler) { int handlerOrder{}; random.GetRandom(handlerOrder); m_handlers->emplace_back(HandlerT(static_cast<int>(address), handlerOrder, connectOnConstruct)); } } } void TearDown() { // Deallocate the memory associated with the EBusHandlers m_handlers.reset(); // Destroy system allocator only if it was created by this environment if (m_ownsSystemAllocator) { AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy(); } } void Connect(::benchmark::State& state) { int64_t numAddresses = state.range(0); int64_t numHandlers = state.range(1); const size_t totalHandlers = static_cast<size_t>(numAddresses * numHandlers); // Connect handlers for (size_t handlerIndex = 0; handlerIndex < totalHandlers; ++handlerIndex) { if(handlerIndex < m_handlers->size()) { (*m_handlers)[handlerIndex].Connect(); } } } void Disconnect(::benchmark::State& state) { int64_t numAddresses = state.range(0); int64_t numHandlers = state.range(1); const size_t totalHandlers = static_cast<size_t>(numAddresses * numHandlers); // Disconnect handlers for (size_t handlerIndex = 0; handlerIndex < totalHandlers; ++handlerIndex) { if (handlerIndex < m_handlers->size()) { (*m_handlers)[handlerIndex].Disconnect(); } } } protected: std::unique_ptr<std::vector<HandlerT>> m_handlers; bool m_ownsSystemAllocator{ false }; }; // Using a variable template to initialize the benchmark EBus_Environment on template instantiation template<typename Bus> static BM_EBusEnvironment<Bus>& s_benchmarkEBusEnv = AZ::Test::RegisterBenchmarkEnvironment<BM_EBusEnvironment<Bus>>(); // Internal macro callback for listing all buses requiring ids #define BUS_BENCHMARK_PRIVATE_LIST_ID(cb, fn) \ cb(fn, ManyToOne, ManyToOne) \ cb(fn, ManyToMany, ManyToMany) \ cb(fn, ManyToManyOrdered, ManyToMany) \ cb(fn, ManyOrderedToOne, ManyToOne) \ cb(fn, ManyOrderedToMany, ManyToMany) \ cb(fn, ManyOrderedToManyOrdered, ManyToMany) // Internal macro callback for listing all buses #define BUS_BENCHMARK_PRIVATE_LIST_ALL(cb, fn) \ cb(fn, OneToOne, OneToOne) \ cb(fn, OneToMany, OneToMany) \ cb(fn, OneToManyOrdered, OneToMany) \ BUS_BENCHMARK_PRIVATE_LIST_ID(cb, fn) // Internal macro callback for registering a benchmark #define BUS_BENCHMARK_PRIVATE_REGISTER(fn, BusDef, SettingsFn) BENCHMARK_TEMPLATE(fn, BusDef)->Apply(&BenchmarkSettings::SettingsFn); // Register a benchmark for all bus permutations requiring ids #define BUS_BENCHMARK_REGISTER_ID(fn) BUS_BENCHMARK_PRIVATE_LIST_ID(BUS_BENCHMARK_PRIVATE_REGISTER, fn) // Register a benchmark for all bus permutations #define BUS_BENCHMARK_REGISTER_ALL(fn) BUS_BENCHMARK_PRIVATE_LIST_ALL(BUS_BENCHMARK_PRIVATE_REGISTER, fn) ////////////////////////////////////////////////////////////////////////// // Single Threaded Events/Broadcasts ////////////////////////////////////////////////////////////////////////// // Baseline benchmark for raw vtable call static void BM_EBus_RawCall(::benchmark::State& state) { constexpr bool connectOnConstruct{ true }; AZStd::unique_ptr<Handler<OneToOne>> handler = AZStd::make_unique<Handler<OneToOne>>(0, connectOnConstruct); while (state.KeepRunning()) { handler->OnEvent(); } } BENCHMARK(BM_EBus_RawCall)->Apply(&BenchmarkSettings::Common); #define BUS_BENCHMARK_PRIVATE_REGISTER_CONNECTION(fn, BusDef, _) BENCHMARK_TEMPLATE(fn, BusDef)->Apply(&BenchmarkSettings::Common); template <typename Bus> static void BM_EBus_BusConnect(::benchmark::State& state) { constexpr bool connectOnConstruct{ false }; Handler<Bus> handler{ 0, connectOnConstruct }; while (state.KeepRunning()) { handler.Connect(); // Pause timing, and disconnect state.PauseTiming(); handler.BusDisconnect(); state.ResumeTiming(); } } BUS_BENCHMARK_PRIVATE_LIST_ALL(BUS_BENCHMARK_PRIVATE_REGISTER_CONNECTION, BM_EBus_BusConnect); template <typename Bus> static void BM_EBus_BusDisconnect(::benchmark::State& state) { constexpr bool connectOnConstruct{ true }; Handler<Bus> handler{ 0, connectOnConstruct }; while (state.KeepRunning()) { handler.BusDisconnect(); // Pause timing, and reconnect state.PauseTiming(); handler.Connect(); state.ResumeTiming(); } } BUS_BENCHMARK_PRIVATE_LIST_ALL(BUS_BENCHMARK_PRIVATE_REGISTER_CONNECTION, BM_EBus_BusDisconnect); #undef BUS_BENCHMARK_PRIVATE_REGISTER_CONNECTION template <typename Bus> static void BM_EBus_EnumerateHandlers(::benchmark::State& state) { auto OnEventVisitor = [](typename Bus::InterfaceType* interfaceInst) -> bool { interfaceInst->OnEvent(); return true; }; s_benchmarkEBusEnv<Bus>.Connect(state); while (state.KeepRunning()) { Bus::EnumerateHandlers(OnEventVisitor); } s_benchmarkEBusEnv<Bus>.Disconnect(state); } BUS_BENCHMARK_REGISTER_ALL(BM_EBus_EnumerateHandlers); template <typename Bus> static void BM_EBus_Broadcast(::benchmark::State& state) { s_benchmarkEBusEnv<Bus>.Connect(state); while (state.KeepRunning()) { Bus::Broadcast(&Bus::Events::OnEvent); } s_benchmarkEBusEnv<Bus>.Disconnect(state); } BUS_BENCHMARK_REGISTER_ALL(BM_EBus_Broadcast); template <typename Bus> static void BM_EBus_BroadcastResult(::benchmark::State& state) { s_benchmarkEBusEnv<Bus>.Connect(state); while (state.KeepRunning()) { int result = 0; Bus::BroadcastResult(result, &Bus::Events::OnEvent); ::benchmark::DoNotOptimize(result); } s_benchmarkEBusEnv<Bus>.Disconnect(state); } BUS_BENCHMARK_REGISTER_ALL(BM_EBus_BroadcastResult); template <typename Bus> static void BM_EBus_Event(::benchmark::State& state) { s_benchmarkEBusEnv<Bus>.Connect(state); while (state.KeepRunning()) { Bus::Event(1, &Bus::Events::OnEvent); } s_benchmarkEBusEnv<Bus>.Disconnect(state); } BUS_BENCHMARK_REGISTER_ID(BM_EBus_Event); template <typename Bus> static void BM_EBus_EventResult(::benchmark::State& state) { s_benchmarkEBusEnv<Bus>.Connect(state); while (state.KeepRunning()) { int result = 0; Bus::EventResult(result, 1, &Bus::Events::OnEvent); ::benchmark::DoNotOptimize(result); } s_benchmarkEBusEnv<Bus>.Disconnect(state); } BUS_BENCHMARK_REGISTER_ID(BM_EBus_EventResult); template <typename Bus> static void BM_EBus_EventCached(::benchmark::State& state) { s_benchmarkEBusEnv<Bus>.Connect(state); typename Bus::BusPtr cachedPtr; constexpr typename Bus::BusIdType firstConnectedAddressId{ 0 }; Bus::Bind(cachedPtr, firstConnectedAddressId); while (state.KeepRunning()) { Bus::Event(cachedPtr, &Bus::Events::OnEvent); } s_benchmarkEBusEnv<Bus>.Disconnect(state); } BUS_BENCHMARK_REGISTER_ID(BM_EBus_EventCached); template <typename Bus> static void BM_EBus_EventCachedResult(::benchmark::State& state) { s_benchmarkEBusEnv<Bus>.Connect(state); typename Bus::BusPtr cachedPtr; constexpr typename Bus::BusIdType firstConnectedAddressId{ 0 }; Bus::Bind(cachedPtr, firstConnectedAddressId); while (state.KeepRunning()) { int result = 0; Bus::EventResult(result, cachedPtr, &Bus::Events::OnEvent); ::benchmark::DoNotOptimize(result); } s_benchmarkEBusEnv<Bus>.Disconnect(state); } BUS_BENCHMARK_REGISTER_ID(BM_EBus_EventCachedResult); ////////////////////////////////////////////////////////////////////////// // Broadcast/Event Queuing ////////////////////////////////////////////////////////////////////////// // Broadcast template <typename Bus> static void BM_EBus_QueueBroadcast(::benchmark::State& state) { while (state.KeepRunning()) { Bus::QueueBroadcast(&Bus::Events::OnEvent); // Pause timing, and reset the queue state.PauseTiming(); Bus::ClearQueuedEvents(); state.ResumeTiming(); } } BUS_BENCHMARK_REGISTER_ALL(BM_EBus_QueueBroadcast); template <typename Bus> static void BM_EBus_ExecuteBroadcast(::benchmark::State& state) { s_benchmarkEBusEnv<Bus>.Connect(state); while (state.KeepRunning()) { // Push an event to the queue to run state.PauseTiming(); Bus::QueueBroadcast(&Bus::Events::OnEvent); state.ResumeTiming(); Bus::ExecuteQueuedEvents(); } s_benchmarkEBusEnv<Bus>.Disconnect(state); } BUS_BENCHMARK_REGISTER_ALL(BM_EBus_ExecuteBroadcast); // Event template <typename Bus> static void BM_EBus_QueueEvent(::benchmark::State& state) { while (state.KeepRunning()) { Bus::QueueEvent(1, &Bus::Events::OnEvent); // Pause timing, and reset the queue state.PauseTiming(); Bus::ClearQueuedEvents(); state.ResumeTiming(); } } BUS_BENCHMARK_REGISTER_ID(BM_EBus_QueueEvent); template <typename Bus> static void BM_EBus_ExecuteEvent(::benchmark::State& state) { s_benchmarkEBusEnv<Bus>.Connect(state); while (state.KeepRunning()) { // Push an event to the queue to run state.PauseTiming(); Bus::QueueEvent(1, &Bus::Events::OnEvent); state.ResumeTiming(); Bus::ExecuteQueuedEvents(); } s_benchmarkEBusEnv<Bus>.Disconnect(state); } BUS_BENCHMARK_REGISTER_ID(BM_EBus_ExecuteEvent); // Event Cached template <typename Bus> static void BM_EBus_QueueEventCached(::benchmark::State& state) { typename Bus::BusPtr cachedPtr; constexpr typename Bus::BusIdType firstConnectedAddressId{ 0 }; Bus::Bind(cachedPtr, firstConnectedAddressId); while (state.KeepRunning()) { Bus::QueueEvent(cachedPtr, &Bus::Events::OnEvent); // Pause timing, and reset the queue state.PauseTiming(); Bus::ClearQueuedEvents(); state.ResumeTiming(); } } BUS_BENCHMARK_REGISTER_ID(BM_EBus_QueueEventCached); template <typename Bus> static void BM_EBus_ExecuteQueueCached(::benchmark::State& state) { s_benchmarkEBusEnv<Bus>.Connect(state); typename Bus::BusPtr cachedPtr; constexpr typename Bus::BusIdType firstConnectedAddressId{ 0 }; Bus::Bind(cachedPtr, firstConnectedAddressId); while (state.KeepRunning()) { // Push an event to the queue to run state.PauseTiming(); Bus::QueueEvent(cachedPtr, &Bus::Events::OnEvent); state.ResumeTiming(); Bus::ExecuteQueuedEvents(); } s_benchmarkEBusEnv<Bus>.Disconnect(state); } BUS_BENCHMARK_REGISTER_ID(BM_EBus_ExecuteQueueCached); ////////////////////////////////////////////////////////////////////////// // Multithreaded Broadcasts ////////////////////////////////////////////////////////////////////////// static void BM_EBus_Multithreaded_Locks(::benchmark::State& state) { using Bus = TestBus<AZ::EBusAddressPolicy::Single, AZ::EBusHandlerPolicy::Multiple, false>; AZStd::unique_ptr<BM_EBusEnvironment<Bus>> ebusBenchmarkEnv; if (state.thread_index == 0) { ebusBenchmarkEnv = AZStd::make_unique<BM_EBusEnvironment<Bus>>(); ebusBenchmarkEnv->SetUp(); ebusBenchmarkEnv->Connect(state); } while (state.KeepRunning()) { Bus::Broadcast(&Bus::Events::OnWait); }; if (state.thread_index == 0) { ebusBenchmarkEnv->Disconnect(state); ebusBenchmarkEnv->TearDown(); } } BENCHMARK(BM_EBus_Multithreaded_Locks)->Apply(&BenchmarkSettings::OneToMany)->Apply(&BenchmarkSettings::Multithreaded); static void BM_EBus_Multithreaded_Lockless(::benchmark::State& state) { using Bus = TestBus<AZ::EBusAddressPolicy::Single, AZ::EBusHandlerPolicy::Multiple, true>; AZStd::unique_ptr<BM_EBusEnvironment<Bus>> ebusBenchmarkEnv; if (state.thread_index == 0) { ebusBenchmarkEnv = AZStd::make_unique<BM_EBusEnvironment<Bus>>(); ebusBenchmarkEnv->SetUp(); ebusBenchmarkEnv->Connect(state); } while (state.KeepRunning()) { Bus::Broadcast(&Bus::Events::OnWait); }; if (state.thread_index == 0) { ebusBenchmarkEnv->Disconnect(state); ebusBenchmarkEnv->TearDown(); } } BENCHMARK(BM_EBus_Multithreaded_Lockless)->Apply(&BenchmarkSettings::OneToMany)->Apply(&BenchmarkSettings::Multithreaded); } #endif // HAVE_BENCHMARK