9060423592
* Added a StealHandlers function to AZ Event The StealHandlers function is able to take all the handlers from an AZ Event parameter and register them with the current AZ Event This allows stealing handlers from expiring AZ Events, which is useful for a lock and swap algorithm for thread safety. 1. Lock persistent AZ::Event 2. Swap persistent AZ::Event with local AZ::Event 3. Unlock persistent AZ::Event - Other threads can now add to this AZ::Event 4. Invoke handlers from local AZ::Event 5. Relock persistent AZ::Event 5. Swap local AZ::Event with persistent AZ::Event 6. Local AZ::Event now contains handlers that were added when the lock was free 7. Persistent AZ::Event now steals from local AZ::Event 8. Unlock persistent AZ::Event Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Separated SettingRegistry update/query mutex from Notifier update mutex The Settings Registry update/query mutex is also better scoped to reduce the amount of lock time. The Notifier mutex being separate allows the Settings Registry to signal a notification event without being under any mutex, by locking and swapping the notifier event with a local instance Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Renamed StealHandlers function to ClaimHandlers Replaced decltype keywords in ClaimHandlers to auto Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
458 lines
14 KiB
C++
458 lines
14 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/Event.h>
|
|
#include <AzCore/UnitTest/TestTypes.h>
|
|
|
|
namespace UnitTest
|
|
{
|
|
class EventTests
|
|
: public ScopedAllocatorSetupFixture
|
|
{
|
|
};
|
|
|
|
TEST_F(EventTests, TestHasCallback)
|
|
{
|
|
AZ::Event<int32_t> testEvent;
|
|
AZ::Event<int32_t>::Handler testHandler([]([[maybe_unused]] int32_t value) {});
|
|
|
|
EXPECT_TRUE(!testEvent.HasHandlerConnected());
|
|
testHandler.Connect(testEvent);
|
|
EXPECT_TRUE(testEvent.HasHandlerConnected());
|
|
}
|
|
|
|
TEST_F(EventTests, TestScopedConnect)
|
|
{
|
|
AZ::Event<int32_t> testEvent;
|
|
|
|
{
|
|
AZ::Event<int32_t>::Handler testHandler([]([[maybe_unused]] int32_t value) {});
|
|
testHandler.Connect(testEvent);
|
|
EXPECT_TRUE(testEvent.HasHandlerConnected());
|
|
}
|
|
|
|
EXPECT_TRUE(!testEvent.HasHandlerConnected());
|
|
}
|
|
|
|
TEST_F(EventTests, TestEvent)
|
|
{
|
|
int32_t invokedValue = 0;
|
|
|
|
AZ::Event<int32_t> testEvent;
|
|
AZ::Event<int32_t>::Handler testHandler([&invokedValue](int32_t value) { invokedValue = value; });
|
|
|
|
testHandler.Connect(testEvent);
|
|
|
|
EXPECT_TRUE(invokedValue == 0);
|
|
testEvent.Signal(1);
|
|
EXPECT_TRUE(invokedValue == 1);
|
|
testEvent.Signal(-1);
|
|
EXPECT_TRUE(invokedValue == -1);
|
|
}
|
|
|
|
TEST_F(EventTests, TestEventRValueParam)
|
|
{
|
|
int32_t invokedValue = 0;
|
|
|
|
AZ::Event<int32_t> testEvent;
|
|
AZ::Event<int32_t>::Handler testHandler([&invokedValue](int32_t value) { invokedValue = value; });
|
|
|
|
testHandler.Connect(testEvent);
|
|
|
|
int32_t value = 1;
|
|
|
|
EXPECT_TRUE(invokedValue == 0);
|
|
testEvent.Signal(value);
|
|
EXPECT_TRUE(invokedValue == 1);
|
|
}
|
|
|
|
TEST_F(EventTests, TestEventRefParam)
|
|
{
|
|
int32_t invokedValue = 0;
|
|
|
|
AZ::Event<int32_t&> testEvent;
|
|
AZ::Event<int32_t&>::Handler testHandler([&invokedValue](int32_t& value) { invokedValue = value++; });
|
|
|
|
testHandler.Connect(testEvent);
|
|
|
|
int32_t value = 1;
|
|
|
|
EXPECT_TRUE(invokedValue == 0);
|
|
testEvent.Signal(value);
|
|
EXPECT_TRUE(invokedValue == 1);
|
|
EXPECT_TRUE(value == 2);
|
|
testEvent.Signal(value);
|
|
EXPECT_TRUE(invokedValue == 2);
|
|
EXPECT_TRUE(value == 3);
|
|
}
|
|
|
|
TEST_F(EventTests, TestEventConstRefParam)
|
|
{
|
|
int32_t invokedValue = 0;
|
|
|
|
AZ::Event<const int32_t&> testEvent;
|
|
AZ::Event<const int32_t&>::Handler testHandler([&invokedValue](const int32_t& value) { invokedValue = value; });
|
|
|
|
testHandler.Connect(testEvent);
|
|
|
|
int32_t value = 1;
|
|
|
|
EXPECT_TRUE(invokedValue == 0);
|
|
testEvent.Signal(value);
|
|
EXPECT_TRUE(invokedValue == 1);
|
|
}
|
|
|
|
TEST_F(EventTests, TestEventPointerParam)
|
|
{
|
|
int32_t invokedValue = 0;
|
|
|
|
AZ::Event<int32_t*> testEvent;
|
|
AZ::Event<int32_t*>::Handler testHandler([&invokedValue](int32_t* value) { invokedValue = (*value)++; });
|
|
|
|
testHandler.Connect(testEvent);
|
|
|
|
int32_t value = 1;
|
|
|
|
EXPECT_TRUE(invokedValue == 0);
|
|
testEvent.Signal(&value);
|
|
EXPECT_TRUE(invokedValue == 1);
|
|
EXPECT_TRUE(value == 2);
|
|
testEvent.Signal(&value);
|
|
EXPECT_TRUE(invokedValue == 2);
|
|
EXPECT_TRUE(value == 3);
|
|
}
|
|
|
|
TEST_F(EventTests, TestEventConstPointerParam)
|
|
{
|
|
int32_t invokedValue = 0;
|
|
|
|
AZ::Event<const int32_t*> testEvent;
|
|
AZ::Event<const int32_t*>::Handler testHandler([&invokedValue](const int32_t* value) { invokedValue = *value; });
|
|
|
|
testHandler.Connect(testEvent);
|
|
|
|
int32_t value = 1;
|
|
|
|
EXPECT_TRUE(invokedValue == 0);
|
|
testEvent.Signal(&value);
|
|
EXPECT_TRUE(invokedValue == 1);
|
|
}
|
|
|
|
TEST_F(EventTests, TestEventMultiParam)
|
|
{
|
|
int32_t invokedValue1 = 0;
|
|
bool invokedValue2 = false;
|
|
|
|
AZ::Event<int32_t, bool> testEvent;
|
|
AZ::Event<int32_t, bool>::Handler testHandler([&invokedValue1, &invokedValue2](int32_t value1, bool value2) { invokedValue1 = value1; invokedValue2 = value2; });
|
|
|
|
testHandler.Connect(testEvent);
|
|
|
|
EXPECT_TRUE(invokedValue1 == 0);
|
|
EXPECT_TRUE(invokedValue2 == false);
|
|
testEvent.Signal(1, true);
|
|
EXPECT_TRUE(invokedValue1 == 1);
|
|
EXPECT_TRUE(invokedValue2 == true);
|
|
testEvent.Signal(-1, false);
|
|
EXPECT_TRUE(invokedValue1 == -1);
|
|
EXPECT_TRUE(invokedValue2 == false);
|
|
}
|
|
|
|
TEST_F(EventTests, TestConnectDuringEvent)
|
|
{
|
|
AZ::Event<int32_t> testEvent;
|
|
|
|
{
|
|
int32_t testHandler2Data = 0;
|
|
AZ::Event<int32_t>::Handler testHandler2([&testHandler2Data](int32_t value) { testHandler2Data = value; });
|
|
|
|
AZ::Event<int32_t>::Handler testHandler([&testHandler2, &testEvent]([[maybe_unused]] int32_t value) { testHandler2.Connect(testEvent); });
|
|
testHandler.Connect(testEvent);
|
|
|
|
testEvent.Signal(1);
|
|
EXPECT_TRUE(testHandler2Data == 0);
|
|
|
|
testHandler.Disconnect();
|
|
EXPECT_TRUE(testEvent.HasHandlerConnected());
|
|
|
|
testEvent.Signal(2);
|
|
EXPECT_TRUE(testHandler2Data == 2);
|
|
}
|
|
|
|
EXPECT_TRUE(!testEvent.HasHandlerConnected());
|
|
}
|
|
|
|
TEST_F(EventTests, TestDisconnectDuringEvent)
|
|
{
|
|
AZ::Event<int32_t> testEvent;
|
|
|
|
{
|
|
int32_t testHandler2Data = 0;
|
|
AZ::Event<int32_t>::Handler testHandler2([&testHandler2Data](int32_t value) { testHandler2Data = value; });
|
|
AZ::Event<int32_t>::Handler testHandler([&testHandler2]([[maybe_unused]] int32_t value) { testHandler2.Disconnect(); });
|
|
|
|
testHandler2.Connect(testEvent);
|
|
testHandler.Connect(testEvent);
|
|
|
|
testEvent.Signal(1);
|
|
EXPECT_TRUE(testHandler2Data == 1);
|
|
EXPECT_TRUE(testEvent.HasHandlerConnected());
|
|
|
|
testEvent.Signal(2);
|
|
EXPECT_TRUE(testHandler2Data == 1);
|
|
}
|
|
|
|
EXPECT_TRUE(!testEvent.HasHandlerConnected());
|
|
}
|
|
|
|
TEST_F(EventTests, TestDisconnectDuringEventReversed)
|
|
{
|
|
AZ::Event<int32_t> testEvent;
|
|
|
|
// Same test as above, but connected using reversed ordering
|
|
{
|
|
int32_t testHandler2Data = 0;
|
|
AZ::Event<int32_t>::Handler testHandler2([&testHandler2Data](int32_t value) { testHandler2Data = value; });
|
|
AZ::Event<int32_t>::Handler testHandler([&testHandler2]([[maybe_unused]] int32_t value) { testHandler2.Disconnect(); });
|
|
|
|
testHandler.Connect(testEvent);
|
|
testHandler2.Connect(testEvent);
|
|
|
|
testEvent.Signal(1);
|
|
EXPECT_TRUE(testHandler2Data == 0);
|
|
EXPECT_TRUE(testEvent.HasHandlerConnected());
|
|
|
|
testEvent.Signal(2);
|
|
EXPECT_TRUE(testHandler2Data == 0);
|
|
}
|
|
|
|
EXPECT_TRUE(!testEvent.HasHandlerConnected());
|
|
}
|
|
|
|
TEST_F(EventTests, CopyConstructorAndCopyAssignmentOperator_AreNotCallable)
|
|
{
|
|
static_assert(!AZStd::is_copy_constructible_v<AZ::Event<int32_t>>, "AZ Events should not be copy constructible");
|
|
static_assert(!AZStd::is_copy_assignable_v<AZ::Event<int32_t>>, "AZ Events should not be copy assignable");
|
|
}
|
|
|
|
TEST_F(EventTests, TestClaimHandlers_TakesAllSourceHandlers)
|
|
{
|
|
AZ::Event<> testEvent1;
|
|
AZ::Event<> testEvent2;
|
|
|
|
int32_t handlerInvokeCount{};
|
|
auto handlerCallback = [&handlerInvokeCount]()
|
|
{
|
|
++handlerInvokeCount;
|
|
};
|
|
AZ::Event<>::Handler testHandler1(handlerCallback);
|
|
AZ::Event<>::Handler testHandler2(handlerCallback);
|
|
|
|
testHandler1.Connect(testEvent1);
|
|
testHandler2.Connect(testEvent2);
|
|
|
|
EXPECT_TRUE(testEvent1.HasHandlerConnected());
|
|
EXPECT_TRUE(testEvent2.HasHandlerConnected());
|
|
|
|
testEvent1.ClaimHandlers(AZStd::move(testEvent2));
|
|
EXPECT_TRUE(testEvent1.HasHandlerConnected());
|
|
EXPECT_FALSE(testEvent2.HasHandlerConnected());
|
|
|
|
// testEvent1 should have both handlers
|
|
testEvent1.Signal();
|
|
EXPECT_EQ(2, handlerInvokeCount);
|
|
// testEvent2 should have neither of the handlers
|
|
testEvent2.Signal();
|
|
EXPECT_EQ(2, handlerInvokeCount);
|
|
}
|
|
|
|
TEST_F(EventTests, HandlerMoveAssignment_ProperlyDisconnectsFromOldEvent)
|
|
{
|
|
AZ::Event<> testEvent1;
|
|
AZ::Event<> testEvent2;
|
|
|
|
AZ::Event<>::Handler testHandler1([]() {});
|
|
AZ::Event<>::Handler testHandler2([]() {});
|
|
|
|
testHandler1.Connect(testEvent1);
|
|
testHandler2.Connect(testEvent2);
|
|
|
|
EXPECT_TRUE(testEvent1.HasHandlerConnected());
|
|
EXPECT_TRUE(testEvent2.HasHandlerConnected());
|
|
|
|
testHandler1 = AZStd::move(testHandler2);
|
|
EXPECT_FALSE(testEvent1.HasHandlerConnected());
|
|
EXPECT_TRUE(testEvent2.HasHandlerConnected());
|
|
}
|
|
|
|
TEST_F(EventTests, TestHandlerCopyConstructorOperator)
|
|
{
|
|
int32_t invokedCounter = 0;
|
|
|
|
AZ::Event<> testEvent;
|
|
AZ::Event<>::Handler testHandler([&invokedCounter]() { invokedCounter++; });
|
|
|
|
testHandler.Connect(testEvent);
|
|
|
|
AZ::Event<>::Handler testHandler2(testHandler);
|
|
|
|
EXPECT_TRUE(testHandler.IsConnected());
|
|
EXPECT_TRUE(testHandler2.IsConnected());
|
|
|
|
EXPECT_TRUE(invokedCounter == 0);
|
|
testEvent.Signal();
|
|
EXPECT_TRUE(invokedCounter == 2);
|
|
}
|
|
|
|
TEST_F(EventTests, TestHandlerCopyAssignmentOperator)
|
|
{
|
|
int32_t invokedCounter = 0;
|
|
|
|
AZ::Event<> testEvent;
|
|
AZ::Event<>::Handler testHandler([&invokedCounter]() { invokedCounter++; });
|
|
|
|
testHandler.Connect(testEvent);
|
|
|
|
AZ::Event<>::Handler testHandler2;
|
|
|
|
EXPECT_TRUE(testHandler.IsConnected());
|
|
EXPECT_FALSE(testHandler2.IsConnected());
|
|
|
|
testHandler2 = testHandler;
|
|
|
|
EXPECT_TRUE(testHandler2.IsConnected());
|
|
|
|
EXPECT_TRUE(invokedCounter == 0);
|
|
testEvent.Signal();
|
|
EXPECT_TRUE(invokedCounter == 2);
|
|
}
|
|
}
|
|
|
|
#if defined(HAVE_BENCHMARK)
|
|
//-------------------------------------------------------------------------
|
|
// PERF TESTS
|
|
//-------------------------------------------------------------------------
|
|
|
|
#include <benchmark/benchmark.h>
|
|
|
|
namespace Benchmark
|
|
{
|
|
static constexpr int32_t NumHandlers = 10000;
|
|
|
|
static void BM_EventPerf_EventEmpty(benchmark::State& state)
|
|
{
|
|
AZ::Event<int32_t> testEvent;
|
|
AZ::Event<int32_t>::Handler testHandler[NumHandlers];
|
|
|
|
for (int32_t i = 0; i < NumHandlers; ++i)
|
|
{
|
|
testHandler[i] = AZ::Event<int32_t>::Handler([]([[maybe_unused]] int32_t value) {});
|
|
testHandler[i].Connect(testEvent);
|
|
}
|
|
|
|
while (state.KeepRunning())
|
|
{
|
|
testEvent.Signal(1);
|
|
}
|
|
}
|
|
BENCHMARK(BM_EventPerf_EventEmpty);
|
|
|
|
static void BM_EventPerf_EventIncrement(benchmark::State& state)
|
|
{
|
|
AZ::Event<int32_t> testEvent;
|
|
AZ::Event<int32_t>::Handler testHandler[NumHandlers];
|
|
|
|
int32_t incrementCounter = 0;
|
|
|
|
for (int32_t i = 0; i < NumHandlers; ++i)
|
|
{
|
|
testHandler[i] = AZ::Event<int32_t>::Handler([&incrementCounter]([[maybe_unused]] int32_t value) { ++incrementCounter; });
|
|
testHandler[i].Connect(testEvent);
|
|
}
|
|
|
|
while (state.KeepRunning())
|
|
{
|
|
testEvent.Signal(1);
|
|
}
|
|
}
|
|
BENCHMARK(BM_EventPerf_EventIncrement);
|
|
|
|
class EBusPerfBaseline
|
|
: public AZ::EBusTraits
|
|
{
|
|
public:
|
|
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
|
|
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
|
|
|
virtual void OnSignal(int32_t) = 0;
|
|
};
|
|
using EBusPerfBaselineBus = AZ::EBus<EBusPerfBaseline>;
|
|
|
|
class EBusPerfBaselineImplEmpty
|
|
: public EBusPerfBaselineBus::Handler
|
|
{
|
|
public:
|
|
EBusPerfBaselineImplEmpty() { EBusPerfBaselineBus::Handler::BusConnect(); }
|
|
~EBusPerfBaselineImplEmpty() { EBusPerfBaselineBus::Handler::BusDisconnect(); }
|
|
void OnSignal(int32_t) override {}
|
|
};
|
|
|
|
static void BM_EventPerf_EBusEmpty(benchmark::State& state)
|
|
{
|
|
EBusPerfBaselineImplEmpty testHandler[NumHandlers];
|
|
|
|
while (state.KeepRunning())
|
|
{
|
|
EBusPerfBaselineBus::Broadcast(&EBusPerfBaseline::OnSignal, 1);
|
|
}
|
|
}
|
|
BENCHMARK(BM_EventPerf_EBusEmpty);
|
|
|
|
class EBusPerfBaselineImplIncrement
|
|
: public EBusPerfBaselineBus::Handler
|
|
{
|
|
public:
|
|
EBusPerfBaselineImplIncrement() { EBusPerfBaselineBus::Handler::BusConnect(); }
|
|
~EBusPerfBaselineImplIncrement() { EBusPerfBaselineBus::Handler::BusDisconnect(); }
|
|
void SetIncrementCounter(int32_t* incrementCounter) { m_incrementCounter = incrementCounter; }
|
|
void OnSignal(int32_t) override { ++(*m_incrementCounter); }
|
|
int32_t* m_incrementCounter;
|
|
};
|
|
|
|
static void BM_EventPerf_EBusIncrement(benchmark::State& state)
|
|
{
|
|
int32_t incrementCounter = 0;
|
|
EBusPerfBaselineImplIncrement testHandler[NumHandlers];
|
|
|
|
for (int32_t i = 0; i < NumHandlers; ++i)
|
|
{
|
|
testHandler[i].SetIncrementCounter(&incrementCounter);
|
|
}
|
|
|
|
while (state.KeepRunning())
|
|
{
|
|
EBusPerfBaselineBus::Broadcast(&EBusPerfBaseline::OnSignal, 1);
|
|
}
|
|
}
|
|
BENCHMARK(BM_EventPerf_EBusIncrement);
|
|
|
|
static void BM_EventPerf_EBusIncrementLambda(benchmark::State& state)
|
|
{
|
|
int32_t incrementCounter = 0;
|
|
EBusPerfBaselineImplEmpty testHandler[NumHandlers];
|
|
|
|
auto invokeFunc = [&incrementCounter](EBusPerfBaseline*, [[maybe_unused]] int32_t value) { ++incrementCounter; };
|
|
while (state.KeepRunning())
|
|
{
|
|
EBusPerfBaselineBus::Broadcast(invokeFunc, 1);
|
|
}
|
|
}
|
|
BENCHMARK(BM_EventPerf_EBusIncrementLambda);
|
|
}
|
|
#endif // HAVE_BENCHMARK
|