You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Framework/AzCore/Tests/EBus.cpp

4693 lines
148 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#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 BusHandler = Handler<Bus>;
using BusMultiHandlerById = 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 BusHandler(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 (BusHandler* 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, BusHandler*>;
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 (BusHandler* 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(&BusHandler::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 BusHandler* 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<BusHandler*>> 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>::BusHandler;
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 BusMultiHandlerById = typename EBusTestAll<Bus>::BusMultiHandlerById;
BusMultiHandlerById sourceMultiHandler{ 0, 1, 2 };
BusMultiHandlerById multiHandlerWithOverlappingIds{ 1, 3, 5 };
// Test handlers' enumeration functionality
Bus::EnumerateHandlers([](typename BusMultiHandlerById::Interface* interfaceInst) -> bool
{
interfaceInst->OnEvent();
return true;
});
}
TYPED_TEST(EBusTestId, FindFirstHandler)
{
using Bus = TypeParam;
using Handler = typename EBusTestAll<Bus>::BusHandler;
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();
}
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 EBusInterceptor : 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;
};
}
#if !AZ_TRAIT_DISABLE_FAILED_EBUS_ROUTING_TEST
TEST_F(EBus, Routing)
{
using namespace RoutingTest;
EBusInterceptor interceptor;
EBusVersion1Handler v1Handler;
v1Handler.BusConnect();
interceptor.BusRouterConnect();
EBusVersion1::Broadcast(&EBusVersion1::Events::OnEvent, 1020);
EXPECT_EQ(1, interceptor.m_numOnEvent);
EXPECT_EQ(1, v1Handler.m_numOnEvent);
interceptor.BusRouterDisconnect();
EBusVersion1::Broadcast(&EBusVersion1::Events::OnEvent, 1020);
EXPECT_EQ(1, interceptor.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)
}
#endif // !AZ_TRAIT_DISABLE_FAILED_EBUS_ROUTING_TEST
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 = []()
{
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 = [&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([[maybe_unused]] typename Bus::Context& context, typename Bus::HandlerNode& handler, [[maybe_unused]] 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, [[maybe_unused]] 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([[maybe_unused]] 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();
}
}
struct SingleHandlerPerIdTestRequests
: public AZ::EBusTraits
{
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
using BusIdType = int32_t;
};
using SingleHandlerPerIdTestRequestBus = AZ::EBus<SingleHandlerPerIdTestRequests>;
struct SingleHandlerPerIdTestImpl : public SingleHandlerPerIdTestRequestBus::Handler
{
void Connect(SingleHandlerPerIdTestRequestBus::BusIdType busId)
{
SingleHandlerPerIdTestRequestBus::Handler::BusConnect(busId);
}
void Disconnect()
{
SingleHandlerPerIdTestRequestBus::Handler::BusDisconnect();
}
};
struct MultipleHandlersPerIdTestRequests
: public AZ::EBusTraits
{
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
using BusIdType = int32_t;
};
using MultipleHandlersPerIdTestRequestBus = AZ::EBus<MultipleHandlersPerIdTestRequests>;
struct MultipleHandlersPerIdTestImpl
: public MultipleHandlersPerIdTestRequestBus::MultiHandler
{
void Connect(MultipleHandlersPerIdTestRequestBus::BusIdType busId)
{
MultipleHandlersPerIdTestRequestBus::MultiHandler::BusConnect(busId);
}
void Disconnect()
{
MultipleHandlersPerIdTestRequestBus::MultiHandler::BusDisconnect();
}
};
TEST_F(EBus, HasHandlersAddressId)
{
SingleHandlerPerIdTestImpl idTestRequest;
idTestRequest.Connect(4);
// note: arbitrary numbers selected
EXPECT_TRUE(SingleHandlerPerIdTestRequestBus::HasHandlers(4));
EXPECT_FALSE(SingleHandlerPerIdTestRequestBus::HasHandlers(10));
idTestRequest.Disconnect();
}
TEST_F(EBus, HasHandlersWithSingleHandlerListeningToMultipleIds)
{
MultipleHandlersPerIdTestImpl idTestRequest;
idTestRequest.Connect(4);
idTestRequest.Connect(7);
idTestRequest.Connect(10);
// note: arbitrary numbers selected
EXPECT_TRUE(MultipleHandlersPerIdTestRequestBus::HasHandlers(4));
EXPECT_TRUE(MultipleHandlersPerIdTestRequestBus::HasHandlers(7));
EXPECT_TRUE(MultipleHandlersPerIdTestRequestBus::HasHandlers(10));
EXPECT_FALSE(MultipleHandlersPerIdTestRequestBus::HasHandlers(15));
idTestRequest.Disconnect();
}
TEST_F(EBus, HasHandlersWithMultipleHandlersOnSameId)
{
MultipleHandlersPerIdTestImpl id1, id2;
id1.Connect(4);
id2.Connect(4);
EXPECT_TRUE(MultipleHandlersPerIdTestRequestBus::HasHandlers(4));
EXPECT_FALSE(MultipleHandlersPerIdTestRequestBus::HasHandlers(7));
}
TEST_F(EBus, HasHandlersWithTwoSingleHandlersOnDifferentIds)
{
SingleHandlerPerIdTestImpl id1, id2;
id1.Connect(4);
id2.Connect(5);
EXPECT_TRUE(SingleHandlerPerIdTestRequestBus::HasHandlers(4));
EXPECT_TRUE(SingleHandlerPerIdTestRequestBus::HasHandlers(5));
EXPECT_FALSE(SingleHandlerPerIdTestRequestBus::HasHandlers(7));
}
TEST_F(EBus, HasHandlersAddressPtr)
{
// note: arbitrary numbers selected
constexpr SingleHandlerPerIdTestRequestBus::BusIdType validBusId = 4;
constexpr SingleHandlerPerIdTestRequestBus::BusIdType invalidBusId = 10;
SingleHandlerPerIdTestImpl idTestRequest;
idTestRequest.Connect(validBusId);
SingleHandlerPerIdTestRequestBus::BusPtr validBusIdPtr;
SingleHandlerPerIdTestRequestBus::Bind(validBusIdPtr, validBusId);
SingleHandlerPerIdTestRequestBus::BusPtr invalidBusIdPtr;
SingleHandlerPerIdTestRequestBus::Bind(invalidBusIdPtr, invalidBusId);
EXPECT_TRUE(SingleHandlerPerIdTestRequestBus::HasHandlers(validBusIdPtr));
EXPECT_FALSE(SingleHandlerPerIdTestRequestBus::HasHandlers(invalidBusIdPtr));
idTestRequest.Disconnect();
}
// IsInDispatchThisThread
struct IsInThreadDispatchRequests
: AZ::EBusTraits
{
using MutexType = AZStd::recursive_mutex;
};
using IsInThreadDispatchBus = AZ::EBus<IsInThreadDispatchRequests>;
class IsInThreadDispatchHandler
: public IsInThreadDispatchBus::Handler
{};
TEST_F(EBus, InvokingIsInThisThread_ReturnsSuccess_OnlyIfThreadIsInDispatch)
{
IsInThreadDispatchHandler handler;
handler.BusConnect();
auto ThreadDispatcher = [](IsInThreadDispatchRequests*)
{
EXPECT_TRUE(IsInThreadDispatchBus::IsInDispatchThisThread());
auto PerThreadBusDispatch = []()
{
EXPECT_FALSE(IsInThreadDispatchBus::IsInDispatchThisThread());
};
AZStd::array threads{ AZStd::thread(PerThreadBusDispatch), AZStd::thread(PerThreadBusDispatch) };
for (AZStd::thread& thread : threads)
{
thread.join();
}
};
static constexpr size_t ThreadDispatcherIterations = 4;
for (size_t iteration = 0; iteration < ThreadDispatcherIterations; ++iteration)
{
EXPECT_FALSE(IsInThreadDispatchBus::IsInDispatchThisThread());
IsInThreadDispatchBus::Broadcast(ThreadDispatcher);
EXPECT_FALSE(IsInThreadDispatchBus::IsInDispatchThisThread());
}
}
// Thread Dispatch Policy
struct ThreadDispatchTestBusTraits
: AZ::EBusTraits
{
using MutexType = AZStd::recursive_mutex;
struct PostThreadDispatchTestInvoker
{
~PostThreadDispatchTestInvoker();
};
template <typename DispatchMutex>
struct ThreadDispatchTestLockGuard
{
ThreadDispatchTestLockGuard(DispatchMutex& contextMutex)
: m_lock{ contextMutex }
{}
ThreadDispatchTestLockGuard(DispatchMutex& contextMutex, AZStd::adopt_lock_t adopt_lock)
: m_lock{ contextMutex, adopt_lock }
{}
ThreadDispatchTestLockGuard(const ThreadDispatchTestLockGuard&) = delete;
ThreadDispatchTestLockGuard& operator=(const ThreadDispatchTestLockGuard&) = delete;
private:
PostThreadDispatchTestInvoker m_threadPolicyInvoker;
using LockType = AZStd::conditional_t<LocklessDispatch, AZ::Internal::NullLockGuard<DispatchMutex>, AZStd::scoped_lock<DispatchMutex>>;
LockType m_lock;
};
template <typename DispatchMutex, bool IsLocklessDispatch>
using DispatchLockGuard = ThreadDispatchTestLockGuard<DispatchMutex>;
static inline AZStd::atomic<int32_t> s_threadPostDispatchCalls;
};
class ThreadDispatchTestRequests
{
public:
virtual void FirstCall() = 0;
virtual void SecondCall() = 0;
virtual void ThirdCall() = 0;
};
using ThreadDispatchTestBus = AZ::EBus<ThreadDispatchTestRequests, ThreadDispatchTestBusTraits>;
ThreadDispatchTestBusTraits::PostThreadDispatchTestInvoker::~PostThreadDispatchTestInvoker()
{
if (!ThreadDispatchTestBus::IsInDispatchThisThread())
{
++s_threadPostDispatchCalls;
}
}
class ThreadDispatchTestHandler
: public ThreadDispatchTestBus::Handler
{
public:
void Connect()
{
ThreadDispatchTestBus::Handler::BusConnect();
}
void Disconnect()
{
ThreadDispatchTestBus::Handler::BusDisconnect();
}
void FirstCall() override
{
ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::SecondCall);
}
void SecondCall() override
{
ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::ThirdCall);
}
void ThirdCall() override
{
}
};
template <typename ParamType>
class EBusParamFixture
: public ScopedAllocatorSetupFixture
, public ::testing::WithParamInterface<ParamType>
{};
struct ThreadDispatchParams
{
size_t m_threadCount{};
size_t m_handlerCount{};
};
using ThreadDispatchParamFixture = EBusParamFixture<ThreadDispatchParams>;
INSTANTIATE_TEST_CASE_P(
ThreadDispatch,
ThreadDispatchParamFixture,
::testing::Values(
ThreadDispatchParams{ 1, 1 },
ThreadDispatchParams{ 2, 1 },
ThreadDispatchParams{ 1, 2 },
ThreadDispatchParams{ 2, 2 },
ThreadDispatchParams{ 16, 8 }
)
);
TEST_P(ThreadDispatchParamFixture, CustomDispatchLockGuard_InvokesPostDispatchFunction_AfterThreadHasFinishedDispatch)
{
ThreadDispatchTestBusTraits::s_threadPostDispatchCalls = 0;
ThreadDispatchParams threadDispatchParams = GetParam();
AZStd::vector<AZStd::thread> testThreads;
AZStd::vector<ThreadDispatchTestHandler> testHandlers(threadDispatchParams.m_handlerCount);
for (ThreadDispatchTestHandler& testHandler : testHandlers)
{
testHandler.Connect();
}
static constexpr size_t DispatchThreadCalls = 3;
const size_t totalThreadDispatchCalls = threadDispatchParams.m_threadCount * DispatchThreadCalls;
auto DispatchThreadWorker = []()
{
ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::FirstCall);
ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::SecondCall);
ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::ThirdCall);
};
for (size_t threadIndex = 0; threadIndex < threadDispatchParams.m_threadCount; ++threadIndex)
{
testThreads.emplace_back(DispatchThreadWorker);
}
for (AZStd::thread& thread : testThreads)
{
thread.join();
}
for (ThreadDispatchTestHandler& testHandler : testHandlers)
{
testHandler.Disconnect();
}
EXPECT_EQ(totalThreadDispatchCalls, ThreadDispatchTestBusTraits::s_threadPostDispatchCalls);
ThreadDispatchTestBusTraits::s_threadPostDispatchCalls = 0;
}
} // namespace UnitTest
#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 SetUpBenchmark() override
{
// 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 TearDownBenchmark() override
{
// Deallocate the memory associated with the EBusHandlers
m_handlers.reset();
}
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;
};
// 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->SetUpBenchmark();
ebusBenchmarkEnv->Connect(state);
}
while (state.KeepRunning())
{
Bus::Broadcast(&Bus::Events::OnWait);
};
if (state.thread_index == 0)
{
ebusBenchmarkEnv->Disconnect(state);
ebusBenchmarkEnv->TearDownBenchmark();
}
}
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->SetUpBenchmark();
ebusBenchmarkEnv->Connect(state);
}
while (state.KeepRunning())
{
Bus::Broadcast(&Bus::Events::OnWait);
};
if (state.thread_index == 0)
{
ebusBenchmarkEnv->Disconnect(state);
ebusBenchmarkEnv->TearDownBenchmark();
}
}
BENCHMARK(BM_EBus_Multithreaded_Lockless)->Apply(&BenchmarkSettings::OneToMany)->Apply(&BenchmarkSettings::Multithreaded);
}
#endif // HAVE_BENCHMARK