Merge branch 'development' of https://github.com/o3de/o3de into daimini/FocusMode/breadcrumbs

This commit is contained in:
Danilo Aimini
2021-09-29 19:03:57 -07:00
45 changed files with 608 additions and 125 deletions
@@ -89,6 +89,9 @@ namespace AZ
// Tracks the asset type used to create the instance.
AssetType m_assetType;
// Boolean to indicate if the instance has been orphaned from the instance database
bool m_isOrphaned = false;
};
/// @cond EXCLUDE_DOCS
@@ -203,6 +203,16 @@ namespace AZ
//! Calls FindOrCreate using a random InstanceId
Data::Instance<Type> Create(const Asset<AssetData>& asset, const AZStd::any* param = nullptr);
/**
* Removes the instance data from the database. Does not release it.
* References to existing instances will remain valid, but new calls to Create/FindOrCreate will create a new instance
* This function is temporary, to provide functionality needed for Model hot-reloading, but will be removed
* once the Model class does not need it anymore.
*
* @param id The id of the instance to remove
*/
void TEMPOrphan(const InstanceId& id);
private:
InstanceDatabase(const AssetType& assetType);
~InstanceDatabase();
@@ -356,6 +366,20 @@ namespace AZ
return FindOrCreate(Data::InstanceId::CreateRandom(), asset, param);
}
template<typename Type>
void InstanceDatabase<Type>::TEMPOrphan(const InstanceId& id)
{
AZStd::scoped_lock<AZStd::recursive_mutex> lock(m_databaseMutex);
// Check if the instance is still in the database, in case it was orphaned twice
auto instanceItr = m_database.find(id);
if (instanceItr != m_database.end())
{
// Mark the instance as orphaned, and remove it from the database
instanceItr->second->m_isOrphaned = true;
m_database.erase(instanceItr);
}
}
template<typename Type>
void InstanceDatabase<Type>::ReleaseInstance(InstanceData* instance, const InstanceId& instanceId)
{
@@ -374,6 +398,12 @@ namespace AZ
m_database.erase(instance->GetId());
m_instanceHandler.m_deleteFunction(static_cast<Type*>(instance));
}
else if (instance->m_isOrphaned && instance->m_useCount.compare_exchange_strong(expectedRefCount, -1))
{
// If the instance was orphaned, it has already been removed from the database,
// but still needs to be deleted when the refcount drops to 0
m_instanceHandler.m_deleteFunction(static_cast<Type*>(instance));
}
}
template<typename Type>
@@ -181,7 +181,76 @@ namespace UnitTest
EXPECT_EQ(instance, instance3);
}
void ParallelInstanceCreateHelper(size_t threadCountMax, size_t assetIdCount, size_t durationSeconds)
TEST_F(InstanceDatabaseTest, InstanceOrphan)
{
auto& assetManager = AssetManager::Instance();
auto& instanceDatabase = InstanceDatabase<TestInstanceA>::Instance();
Asset<TestAssetType> someAsset = assetManager.CreateAsset<TestAssetType>(s_assetId0, AZ::Data::AssetLoadBehavior::Default);
Instance<TestInstanceA> orphanedInstance = instanceDatabase.FindOrCreate(s_instanceId0, someAsset);
EXPECT_NE(orphanedInstance, nullptr);
instanceDatabase.TEMPOrphan(s_instanceId0);
// After orphan, the instance should not be found in the database, but it should still be valid
EXPECT_EQ(instanceDatabase.Find(s_instanceId0), nullptr);
EXPECT_NE(orphanedInstance, nullptr);
instanceDatabase.TEMPOrphan(s_instanceId0);
// Orphaning twice should be a no-op
EXPECT_EQ(instanceDatabase.Find(s_instanceId0), nullptr);
EXPECT_NE(orphanedInstance, nullptr);
Instance<TestInstanceA> instance2 = instanceDatabase.FindOrCreate(s_instanceId0, someAsset);
// Creating another instance with the same id should return a different instance than the one that was orphaned
EXPECT_NE(orphanedInstance, instance2);
}
enum class ParallelInstanceTestCases
{
Create,
CreateAndDeferRemoval,
CreateAndOrphan,
CreateDeferRemovalAndOrphan
};
enum class ParralleInstanceCurrentAction
{
Create,
DeferredRemoval,
Orphan
};
ParralleInstanceCurrentAction ParallelInstanceGetCurrentAction(ParallelInstanceTestCases testCase)
{
switch (testCase)
{
case ParallelInstanceTestCases::CreateAndDeferRemoval:
switch (rand() % 2)
{
case 0: return ParralleInstanceCurrentAction::Create;
case 1: return ParralleInstanceCurrentAction::DeferredRemoval;
}
case ParallelInstanceTestCases::CreateAndOrphan:
switch (rand() % 2)
{
case 0: return ParralleInstanceCurrentAction::Create;
case 1: return ParralleInstanceCurrentAction::Orphan;
}
case ParallelInstanceTestCases::CreateDeferRemovalAndOrphan:
switch (rand() % 3)
{
case 0: return ParralleInstanceCurrentAction::Create;
case 1: return ParralleInstanceCurrentAction::DeferredRemoval;
case 2: return ParralleInstanceCurrentAction::Orphan;
}
case ParallelInstanceTestCases::Create:
default:
return ParralleInstanceCurrentAction::Create;
}
}
void ParallelInstanceCreateHelper(size_t threadCountMax, size_t assetIdCount, float durationSeconds, ParallelInstanceTestCases testCase)
{
printf("Testing threads=%zu assetIds=%zu ... ", threadCountMax, assetIdCount);
@@ -192,6 +261,7 @@ namespace UnitTest
auto& instanceManager = InstanceDatabase<TestInstanceA>::Instance();
AZStd::vector<Uuid> guids;
AZStd::vector<Data::Instance<Data::InstanceData>> instances;
AZStd::vector<Asset<TestAssetType>> assets;
for (size_t i = 0; i < assetIdCount; ++i)
@@ -199,6 +269,7 @@ namespace UnitTest
Uuid guid = Uuid::CreateRandom();
guids.emplace_back(guid);
instances.emplace_back(nullptr);
// Pre-create asset so we don't attempt to load it from the catalog.
assets.emplace_back(assetManager.CreateAsset<TestAssetType>(guid, AZ::Data::AssetLoadBehavior::Default));
@@ -206,6 +277,7 @@ namespace UnitTest
AZStd::vector<AZStd::thread> threads;
AZStd::mutex mutex;
AZStd::mutex referenceTableMutex;
AZStd::atomic<int> threadCount((int)threadCountMax);
AZStd::condition_variable cv;
AZStd::atomic_bool keepDispatching(true);
@@ -225,11 +297,15 @@ namespace UnitTest
for (size_t i = 0; i < threadCountMax; ++i)
{
threads.emplace_back(
[&instanceManager, &threadCount, &cv, &guids, &assets, &durationSeconds]()
[&instanceManager, &threadCount, &cv, &guids, &instances, &assets, &durationSeconds, &testCase, &referenceTableMutex]()
{
AZ::Debug::Timer timer;
timer.Stamp();
bool deferRemoval = testCase == ParallelInstanceTestCases::CreateAndDeferRemoval ||
testCase == ParallelInstanceTestCases::CreateDeferRemovalAndOrphan
? true : false;
while (timer.GetDeltaTimeInSeconds() < durationSeconds)
{
const size_t index = rand() % guids.size();
@@ -237,11 +313,36 @@ namespace UnitTest
const InstanceId instanceId{ uuid };
const AssetId assetId{ uuid };
Instance<TestInstanceA> instance =
instanceManager.FindOrCreate(instanceId, Asset<TestAssetType>(assetId, azrtti_typeid<TestAssetType>()));
EXPECT_NE(instance, nullptr);
EXPECT_EQ(instance->GetId(), instanceId);
EXPECT_EQ(instance->m_asset, assets[index]);
ParralleInstanceCurrentAction currentAction = ParallelInstanceGetCurrentAction(testCase);
if (currentAction == ParralleInstanceCurrentAction::Orphan)
{
// Orphan the instance, but don't decrease its refcount
instanceManager.TEMPOrphan(instanceId);
}
else if (currentAction == ParralleInstanceCurrentAction::DeferredRemoval)
{
// Drop the refcount to zero so the instance will be released
referenceTableMutex.lock();
instances[index] = nullptr;
referenceTableMutex.unlock();
}
else
{
// Otherwise, add a new instance
Instance<TestInstanceA> instance = instanceManager.FindOrCreate(instanceId, assets[index]);
EXPECT_NE(instance, nullptr);
EXPECT_EQ(instance->GetId(), instanceId);
EXPECT_EQ(instance->m_asset, assets[index]);
if (deferRemoval)
{
// Keep a reference to the instance alive so it can be removed later
referenceTableMutex.lock();
instances[index] = instance;
referenceTableMutex.unlock();
}
}
}
threadCount--;
@@ -254,10 +355,12 @@ namespace UnitTest
// Used to detect a deadlock. If we wait for more than 10 seconds, it's likely a deadlock has occurred
while (threadCount > 0 && !timedOut)
{
size_t durationSecondsRoundedUp = static_cast<size_t>(std::ceil(durationSeconds));
AZStd::unique_lock<AZStd::mutex> lock(mutex);
timedOut =
(AZStd::cv_status::timeout ==
cv.wait_until(lock, AZStd::chrono::system_clock::now() + AZStd::chrono::seconds(durationSeconds * 2)));
cv.wait_until(lock, AZStd::chrono::system_clock::now() + AZStd::chrono::seconds(durationSecondsRoundedUp * 2)));
}
EXPECT_TRUE(threadCount == 0) << "One or more threads appear to be deadlocked at " << timer.GetDeltaTimeInSeconds() << " seconds";
@@ -273,11 +376,11 @@ namespace UnitTest
printf("Took %f seconds\n", timer.GetDeltaTimeInSeconds());
}
TEST_F(InstanceDatabaseTest, ParallelInstanceCreate)
void ParallelCreateTest(ParallelInstanceTestCases testCase)
{
// This is the original test scenario from when InstanceDatabase was first implemented
// threads, AssetIds, seconds
ParallelInstanceCreateHelper(8, 100, 5);
ParallelInstanceCreateHelper(8, 100, 5, testCase);
// This value is checked in as 1 so this test doesn't take too much time, but can be increased locally to soak the test.
const size_t attempts = 1;
@@ -289,11 +392,11 @@ namespace UnitTest
// The idea behind this series of tests is that there are two threads sharing one Instance, and both threads try to
// create or release that instance at the same time.
// At the time, this set of scenarios has something like a 10% failure rate.
const size_t duration = 2;
const float duration = 2.0f;
// threads, AssetIds, seconds
ParallelInstanceCreateHelper(2, 1, duration);
ParallelInstanceCreateHelper(4, 1, duration);
ParallelInstanceCreateHelper(8, 1, duration);
ParallelInstanceCreateHelper(2, 1, duration, testCase);
ParallelInstanceCreateHelper(4, 1, duration, testCase);
ParallelInstanceCreateHelper(8, 1, duration, testCase);
}
for (size_t i = 0; i < attempts; ++i)
@@ -301,19 +404,39 @@ namespace UnitTest
printf("Attempt %zu of %zu... \n", i, attempts);
// Here we try a bunch of different threadCount:assetCount ratios to be thorough
const size_t duration = 2;
const float duration = 2.0f;
// threads, AssetIds, seconds
ParallelInstanceCreateHelper(2, 1, duration);
ParallelInstanceCreateHelper(4, 1, duration);
ParallelInstanceCreateHelper(4, 2, duration);
ParallelInstanceCreateHelper(4, 4, duration);
ParallelInstanceCreateHelper(8, 1, duration);
ParallelInstanceCreateHelper(8, 2, duration);
ParallelInstanceCreateHelper(8, 3, duration);
ParallelInstanceCreateHelper(8, 4, duration);
ParallelInstanceCreateHelper(2, 1, duration, testCase);
ParallelInstanceCreateHelper(4, 1, duration, testCase);
ParallelInstanceCreateHelper(4, 2, duration, testCase);
ParallelInstanceCreateHelper(4, 4, duration, testCase);
ParallelInstanceCreateHelper(8, 1, duration, testCase);
ParallelInstanceCreateHelper(8, 2, duration, testCase);
ParallelInstanceCreateHelper(8, 3, duration, testCase);
ParallelInstanceCreateHelper(8, 4, duration, testCase);
}
}
TEST_F(InstanceDatabaseTest, ParallelInstanceCreate)
{
ParallelCreateTest(ParallelInstanceTestCases::Create);
}
TEST_F(InstanceDatabaseTest, ParallelInstanceCreateAndDeferRemoval)
{
ParallelCreateTest(ParallelInstanceTestCases::CreateAndDeferRemoval);
}
TEST_F(InstanceDatabaseTest, ParallelInstanceCreateAndOrphan)
{
ParallelCreateTest(ParallelInstanceTestCases::CreateAndOrphan);
}
TEST_F(InstanceDatabaseTest, ParallelInstanceCreateDeferRemovalAndOrphan)
{
ParallelCreateTest(ParallelInstanceTestCases::CreateDeferRemovalAndOrphan);
}
TEST_F(InstanceDatabaseTest, InstanceCreateNoDatabase)
{
bool m_deleted = false;
@@ -340,6 +340,14 @@ namespace AZ
// (Load jobs will attempt to reuse blocked threads before spinning off new job threads)
ProcessLoadJob();
}
// Pump the AssetBus function queue once more after the load has completed in case additional
// functions have been queued between the last call to DispatchEvents and the completion
// of the current load job
if (m_shouldDispatchEvents)
{
AssetManager::Instance().DispatchEvents();
}
}
void Finish()
@@ -22,6 +22,7 @@
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/SystemAllocator.h> // Used as the allocator for most components.
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/containers/unordered_set.h>
namespace AZ
{
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/EBus/Policies.h>
+7 -10
View File
@@ -19,14 +19,11 @@
#pragma once
#include <AzCore/EBus/BusImpl.h>
#include <AzCore/EBus/Environment.h>
#include <AzCore/EBus/Results.h>
#include <AzCore/EBus/Internal/Debug.h>
// Included for backwards compatibility purposes
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/typetraits/is_same.h>
// End backwards compat
#include <AzCore/std/utils.h>
#include <AzCore/std/parallel/scoped_lock.h>
@@ -90,14 +87,14 @@ namespace AZ
* For available settings, see AZ::EBusHandlerPolicy.
* By default, an EBus supports any number of handlers.
*/
static const EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Multiple;
static constexpr EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Multiple;
/**
* Defines how many addresses exist on the EBus.
* For available settings, see AZ::EBusAddressPolicy.
* By default, an EBus uses a single address.
*/
static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::Single;
static constexpr EBusAddressPolicy AddressPolicy = EBusAddressPolicy::Single;
/**
* The type of ID that is used to address the EBus.
@@ -152,14 +149,14 @@ namespace AZ
* `<BusName>::ExecuteQueuedEvents()`.
* By default, the event queue is disabled.
*/
static const bool EnableEventQueue = false;
static constexpr bool EnableEventQueue = false;
/**
* Specifies whether the bus should accept queued messages by default or not.
* If set to false, Bus::AllowFunctionQueuing(true) must be called before events are accepted.
* Used only when #EnableEventQueue is true.
*/
static const bool EventQueueingActiveByDefault = true;
static constexpr bool EventQueueingActiveByDefault = true;
/**
* Specifies whether the EBus supports queueing functions which take reference
@@ -168,7 +165,7 @@ namespace AZ
* You should only use this if you know that the data being passed as arguments will
* outlive the dispatch of the queued event.
*/
static const bool EnableQueuedReferences = false;
static constexpr bool EnableQueuedReferences = false;
/**
* Locking primitive that is used when adding and removing
@@ -197,7 +194,7 @@ namespace AZ
* to do.
* By default, the standard policy is used, which locks around all dispatches
*/
static const bool LocklessDispatch = false;
static constexpr bool LocklessDispatch = false;
/**
* Specifies where EBus data is stored.
@@ -13,6 +13,7 @@
#include <AzCore/EBus/EBus.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ
{
+14 -23
View File
@@ -18,9 +18,8 @@
#include <AzCore/std/function/invoke.h>
#include <AzCore/std/containers/queue.h>
#include <AzCore/std/containers/intrusive_set.h>
#include <AzCore/std/parallel/scoped_lock.h>
#include <AzCore/Module/Environment.h>
#include <AzCore/EBus/Environment.h>
namespace AZ
{
@@ -251,29 +250,21 @@ namespace AZ
void Execute()
{
AZ_Warning("System", m_isActive, "You are calling execute queued functions on a bus which has not activated its function queuing! Call YourBus::AllowFunctionQueuing(true)!");
while (true)
MessageQueueType localMessages;
// Swap the current list of queue functions with a local instance
{
BusMessageCall invoke;
AZStd::scoped_lock lock(m_messagesMutex);
AZStd::swap(localMessages, m_messages);
}
//////////////////////////////////////////////////////////////////////////
// Pop element from the queue.
{
AZStd::lock_guard<MutexType> lock(m_messagesMutex);
size_t numMessages = m_messages.size();
if (numMessages == 0)
{
break;
}
AZStd::swap(invoke, m_messages.front());
m_messages.pop();
if (numMessages == 1)
{
m_messages = {};
}
}
//////////////////////////////////////////////////////////////////////////
invoke();
// Execute the queue functions safely now that are owned by the function
while (!localMessages.empty())
{
const BusMessageCall& localMessage = localMessages.front();
localMessage();
localMessages.pop();
}
}
@@ -14,6 +14,7 @@
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/function/invoke.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/string_view.h>
@@ -366,6 +366,42 @@ namespace UnitTest
};
static constexpr AZStd::chrono::seconds MaxDispatchTimeoutSeconds = BaseAssetManagerTest::DefaultTimeoutSeconds * 12;
template <typename Pred>
bool DispatchEventsUntilCondition(AZ::Data::AssetManager& assetManager, Pred&& conditionPredicate,
AZStd::chrono::seconds logIntervalSeconds = BaseAssetManagerTest::DefaultTimeoutSeconds,
AZStd::chrono::seconds maxTimeoutSeconds = MaxDispatchTimeoutSeconds)
{
// If the Max Timeout is hit the test will be marked as a failure
AZStd::chrono::time_point dispatchEventTimeStart = AZStd::chrono::system_clock::now();
AZStd::chrono::seconds dispatchEventNextLogTime = logIntervalSeconds;
while (!conditionPredicate())
{
AZStd::chrono::time_point currentTime = AZStd::chrono::system_clock::now();
if (AZStd::chrono::seconds elapsedTime{ currentTime - dispatchEventTimeStart };
elapsedTime >= dispatchEventNextLogTime)
{
const testing::TestInfo* test_info = ::testing::UnitTest::GetInstance()->current_test_info();
AZ_Printf("AssetManagerLoadingTest", "The DispatchEventsUntiTimeout function has been waiting for %llu seconds"
" in test %s.%s", elapsedTime.count(), test_info->test_case_name(), test_info->name());
// Update the next log time to be the next multiple of DefaultTimeout Seconds
// after current elapsed time
dispatchEventNextLogTime = elapsedTime + logIntervalSeconds - ((elapsedTime + logIntervalSeconds) % logIntervalSeconds);
if (elapsedTime >= maxTimeoutSeconds)
{
return false;
}
}
assetManager.DispatchEvents();
AZStd::this_thread::yield();
}
return true;
}
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_ASSET_MANAGER_FLOOD_TEST
TEST_F(AssetJobsFloodTest, DISABLED_FloodTest)
#else
@@ -1358,42 +1394,74 @@ namespace UnitTest
m_assetHandlerAndCatalog->m_numCreations = 0;
m_assetHandlerAndCatalog->m_numDestructions = 0;
{
ContainerReadyListener containerLoadingCompleteListener(NoLoadAssetId);
OnAssetReadyListener readyListener(NoLoadAssetId, azrtti_typeid<AssetWithAssetReference>());
OnAssetReadyListener depenencyListener(MyAsset2Id, azrtti_typeid<AssetWithAssetReference>());
OnAssetReadyListener dependencyListener(MyAsset2Id, azrtti_typeid<AssetWithAssetReference>());
SCOPED_TRACE("LoadDependencies_BehaviorObeyed");
auto AssetOnlyReady = [&readyListener]() -> bool
{
return readyListener.m_ready;
};
auto AssetAndDependencyReady = [&readyListener, &dependencyListener]() -> bool
{
return readyListener.m_ready && dependencyListener.m_ready;
};
auto AssetContainerReady = [&containerLoadingCompleteListener]() -> bool
{
return containerLoadingCompleteListener.m_ready;
};
auto noLoadRef = m_testAssetManager->GetAsset(NoLoadAssetId, azrtti_typeid<AssetWithAssetReference>(),
AZ::Data::AssetLoadBehavior::Default);
auto maxTimeout = AZStd::chrono::system_clock::now() + DefaultTimeoutSeconds;
// Dispatch AssetBus events until the NoLoadAssetId has signaled an OnAssetReady
// event or the timeout has been reached
EXPECT_TRUE(DispatchEventsUntilCondition(*m_testAssetManager, AssetOnlyReady))
<< "The DispatchEventsUntiTimeout function has not completed in "
<< MaxDispatchTimeoutSeconds.count() << " seconds. The test will be marked as a failure\n";
// Dispatch AssetBus events until the asset container used to load
// NoLoadAssetId has signaled an OnAssetContainerReady event
// or the timeout has been reached
// Wait until the current asset container has finished loading the NoLoadAssetId
// before trigger another load
// If the wait does not occur here, most likely what would occur is
// the AssetManager::m_ownedAssetContainers object is still loading the NoLoadAssetId
// using the default AssetLoadParameters
// If a call to GetAsset occurs at this point while the Asset is still loading
// it will ignore the new loadParams below and instead just re-use the existing
// AssetContainerReader instance, resulting in the dependent MyAsset2Id not
// being loaded
// The function that can return an existing AssetContainer instance is the
// AssetManager::GetAssetContainer. Since it can be in the middle of a load,
// updating the AssetLoadParams would have an effect on the current in progress
// load
EXPECT_TRUE(DispatchEventsUntilCondition(*m_testAssetManager, AssetContainerReady))
<< "The DispatchEventsUntiTimeout function has not completed in "
<< MaxDispatchTimeoutSeconds.count() << " seconds. The test will be marked as a failure\n";
// Reset the ContainerLoadingComplete ready status back to 0
containerLoadingCompleteListener.m_ready = 0;
while (!readyListener.m_ready)
{
m_testAssetManager->DispatchEvents();
if (AZStd::chrono::system_clock::now() > maxTimeout)
{
break;
}
AZStd::this_thread::yield();
}
EXPECT_EQ(readyListener.m_ready, 1);
EXPECT_EQ(depenencyListener.m_ready, 0);
AZ::Data::AssetLoadParameters loadParams(nullptr, AZ::Data::AssetDependencyLoadRules::LoadAll);
loadParams.m_reloadMissingDependencies = true;
auto loadDependencyRef = m_testAssetManager->GetAsset(NoLoadAssetId, azrtti_typeid<AssetWithAssetReference>(),
AZ::Data::AssetLoadBehavior::Default, loadParams);
while (!depenencyListener.m_ready || !readyListener.m_ready)
{
m_testAssetManager->DispatchEvents();
if (AZStd::chrono::system_clock::now() > maxTimeout)
{
break;
}
AZStd::this_thread::yield();
}
// Dispatch AssetBus events until the NoLoadAssetId and the MyAsset2Id has signaled
// an OnAssetReady event or the timeout has been reached
EXPECT_TRUE(DispatchEventsUntilCondition(*m_testAssetManager, AssetAndDependencyReady))
<< "The DispatchEventsUntiTimeout function has not completed in "
<< MaxDispatchTimeoutSeconds.count() << " seconds. The test will be marked as a failure\n";
EXPECT_EQ(readyListener.m_ready, 1);
EXPECT_EQ(depenencyListener.m_ready, 1);
EXPECT_EQ(dependencyListener.m_ready, 1);
EXPECT_TRUE(DispatchEventsUntilCondition(*m_testAssetManager, AssetContainerReady))
<< "The DispatchEventsUntiTimeout function has not completed in "
<< MaxDispatchTimeoutSeconds.count() << " seconds. The test will be marked as a failure\n";
}
CheckFinishedCreationsAndDestructions();
@@ -7,6 +7,7 @@
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/std/containers/unordered_set.h>
using namespace AZ;
@@ -18,6 +18,7 @@
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/smart_ptr/intrusive_base.h>
#include <AzFramework/Archive/Codec.h>
#include <AzFramework/Archive/ZipDirStructures.h>
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Logging/MissingAssetNotificationBus.h>
namespace AzFramework { class LogFile; }
@@ -11,6 +11,7 @@
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/functional.h>
//! Common structures for Render geometry queries
@@ -9,6 +9,7 @@
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Windowing/WindowBus.h>
@@ -12,6 +12,7 @@
#include <AzCore/Interface/Interface.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzNetworking
@@ -11,6 +11,7 @@
#include <AzNetworking/Utilities/IpAddress.h>
#include <AzNetworking/ConnectionLayer/IConnectionSet.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AzNetworking
{
@@ -9,6 +9,7 @@
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntry.h>
@@ -7,6 +7,7 @@
*/
#pragma once
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/IO/Path/Path.h>
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/Component/EntityId.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/SerializeContext.h>
@@ -15,7 +15,7 @@
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/smart_ptr/enable_shared_from_this.h>
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
#include <AzToolsFramework/Manipulators/ManipulatorSpace.h>
@@ -10,6 +10,7 @@
#include <AzCore/base.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/string/string.h>
namespace AzToolsFramework
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzToolsFramework/ViewportUi/ViewportUiRequestBus.h>
namespace AzToolsFramework::ViewportUi::Internal
@@ -11,6 +11,7 @@
#if !defined(Q_MOC_RUN)
#include "UnitTestRunner.h"
#include "native/utilities/IniConfiguration.h"
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <QString>
#endif
@@ -568,17 +568,20 @@ QProgressBar::chunk {
font-size: 12px;
}
#gemRepoNoReposLabel {
font-size: 16px;
}
#gemRepoHeaderRefreshButton {
background-color: transparent;
qproperty-flat: true;
qproperty-iconSize: 14px;
}
#gemRepoHeaderAddButton {
#gemRepoAddButton {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #888888, stop: 1.0 #555555);
qproperty-flat: true;
margin-right:30px;
min-width:120px;
max-width:120px;
min-height:24px;
@@ -588,11 +591,11 @@ QProgressBar::chunk {
font-size:12px;
font-weight:600;
}
#gemRepoHeaderAddButton:hover {
#gemRepoAddButton:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #999999, stop: 1.0 #666666);
}
#gemRepoHeaderAddButton:pressed {
#gemRepoAddButton:pressed {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #555555, stop: 1.0 #777777);
}
@@ -610,6 +613,14 @@ QProgressBar::chunk {
background: #444444;
}
#gemRepoAddDialogInstructionTitleLabel {
font-size:14px;
}
#addGemRepoDialog #formFrame {
margin-left:0px;
}
/************** Gem Repo Inspector **************/
#gemRepoInspectorNameLabel {
@@ -0,0 +1,65 @@
/*
* 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 <GemRepo/GemRepoAddDialog.h>
#include <FormLineEditWidget.h>
#include <QVBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QDialogButtonBox>
#include <QPushButton>
namespace O3DE::ProjectManager
{
GemRepoAddDialog::GemRepoAddDialog(QWidget* parent)
: QDialog(parent)
{
setWindowTitle(tr("Add a User Repository"));
setModal(true);
setObjectName("addGemRepoDialog");
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setContentsMargins(30, 30, 25, 10);
vLayout->setSpacing(0);
setLayout(vLayout);
QLabel* instructionTitleLabel = new QLabel(tr("Enter a valid path to add a new user repository"));
instructionTitleLabel->setObjectName("gemRepoAddDialogInstructionTitleLabel");
instructionTitleLabel->setAlignment(Qt::AlignLeft);
vLayout->addWidget(instructionTitleLabel);
vLayout->addSpacing(10);
QLabel* instructionContextLabel = new QLabel(tr("The path can be a Repository URL or a Local Path in your directory."));
instructionContextLabel->setAlignment(Qt::AlignLeft);
vLayout->addWidget(instructionContextLabel);
m_repoPath = new FormLineEditWidget(tr("Repository Path"), "", this);
m_repoPath->setFixedWidth(600);
vLayout->addWidget(m_repoPath);
vLayout->addSpacing(40);
QDialogButtonBox* dialogButtons = new QDialogButtonBox();
dialogButtons->setObjectName("footer");
vLayout->addWidget(dialogButtons);
QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
cancelButton->setProperty("secondary", true);
QPushButton* applyButton = dialogButtons->addButton(tr("Add"), QDialogButtonBox::ApplyRole);
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
connect(applyButton, &QPushButton::clicked, this, &QDialog::accept);
}
QString GemRepoAddDialog::GetRepoPath()
{
return m_repoPath->lineEdit()->text();
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,31 @@
/*
* 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
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QDialog>
#endif
namespace O3DE::ProjectManager
{
QT_FORWARD_DECLARE_CLASS(FormLineEditWidget)
class GemRepoAddDialog
: public QDialog
{
public:
explicit GemRepoAddDialog(QWidget* parent = nullptr);
~GemRepoAddDialog() = default;
QString GetRepoPath();
private:
FormLineEditWidget* m_repoPath = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -10,6 +10,7 @@
#include <GemRepo/GemRepoItemDelegate.h>
#include <GemRepo/GemRepoListView.h>
#include <GemRepo/GemRepoModel.h>
#include <GemRepo/GemRepoAddDialog.h>
#include <GemRepo/GemRepoInspector.h>
#include <PythonBindingsInterface.h>
@@ -21,6 +22,8 @@
#include <QLabel>
#include <QHeaderView>
#include <QTableWidget>
#include <QFrame>
#include <QStackedWidget>
namespace O3DE::ProjectManager
{
@@ -34,11 +37,135 @@ namespace O3DE::ProjectManager
vLayout->setSpacing(0);
setLayout(vLayout);
m_contentStack = new QStackedWidget(this);
m_noRepoContent = CreateNoReposContent();
m_contentStack->addWidget(m_noRepoContent);
m_repoContent = CreateReposContent();
m_contentStack->addWidget(m_repoContent);
vLayout->addWidget(m_contentStack);
Reinit();
}
void GemRepoScreen::Reinit()
{
m_gemRepoModel->clear();
FillModel();
// If model contains any data show the repos
if (m_gemRepoModel->rowCount())
{
m_contentStack->setCurrentWidget(m_repoContent);
}
else
{
m_contentStack->setCurrentWidget(m_noRepoContent);
}
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemRepoListView->model()->index(0,0);
m_gemRepoListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
});
}
void GemRepoScreen::HandleAddRepoButton()
{
GemRepoAddDialog* repoAddDialog = new GemRepoAddDialog(this);
if (repoAddDialog->exec() == QDialog::DialogCode::Accepted)
{
QString repoUrl = repoAddDialog->GetRepoPath();
if (repoUrl.isEmpty())
{
return;
}
AZ::Outcome<void, AZStd::string> addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUrl);
if (addGemRepoResult.IsSuccess())
{
Reinit();
}
else
{
QMessageBox::critical(this, tr("Operation failed"),
QString("Failed to add gem repo: %1.<br>Error:<br>%2").arg(repoUrl, addGemRepoResult.GetError().c_str()));
}
}
}
void GemRepoScreen::FillModel()
{
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> allGemRepoInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoInfos();
if (allGemRepoInfosResult.IsSuccess())
{
// Add all available repos to the model
const QVector<GemRepoInfo> allGemRepoInfos = allGemRepoInfosResult.GetValue();
for (const GemRepoInfo& gemRepoInfo : allGemRepoInfos)
{
m_gemRepoModel->AddGemRepo(gemRepoInfo);
}
}
else
{
QMessageBox::critical(this, tr("Operation failed"), QString("Cannot retrieve gem repos for engine.<br>Error:<br>%2").arg(allGemRepoInfosResult.GetError().c_str()));
}
}
QFrame* GemRepoScreen::CreateNoReposContent()
{
QFrame* contentFrame = new QFrame(this);
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setAlignment(Qt::AlignHCenter);
vLayout->setMargin(0);
vLayout->setSpacing(0);
contentFrame->setLayout(vLayout);
vLayout->addStretch();
QLabel* noRepoLabel = new QLabel(tr("No repositories have been added yet."), this);
noRepoLabel->setObjectName("gemRepoNoReposLabel");
vLayout->addWidget(noRepoLabel);
vLayout->setAlignment(noRepoLabel, Qt::AlignHCenter);
vLayout->addSpacing(20);
// Size hint for button is wrong so horizontal layout with stretch is used to center it
QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setMargin(0);
hLayout->setSpacing(0);
hLayout->addStretch();
QPushButton* addRepoButton = new QPushButton(tr("Add Repository"), this);
addRepoButton->setObjectName("gemRepoAddButton");
addRepoButton->setMinimumWidth(120);
hLayout->addWidget(addRepoButton);
connect(addRepoButton, &QPushButton::clicked, this, &GemRepoScreen::HandleAddRepoButton);
hLayout->addStretch();
vLayout->addLayout(hLayout);
vLayout->addStretch();
return contentFrame;
}
QFrame* GemRepoScreen::CreateReposContent()
{
QFrame* contentFrame = new QFrame(this);
QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setMargin(0);
hLayout->setSpacing(0);
contentFrame->setLayout(hLayout);
hLayout->addSpacing(60);
QVBoxLayout* middleVLayout = new QVBoxLayout();
@@ -63,9 +190,13 @@ namespace O3DE::ProjectManager
topMiddleHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
m_AddRepoButton = new QPushButton(tr("Add Repository"), this);
m_AddRepoButton->setObjectName("gemRepoHeaderAddButton");
topMiddleHLayout->addWidget(m_AddRepoButton);
QPushButton* addRepoButton = new QPushButton(tr("Add Repository"), this);
addRepoButton->setObjectName("gemRepoAddButton");
topMiddleHLayout->addWidget(addRepoButton);
connect(addRepoButton, &QPushButton::clicked, this, &GemRepoScreen::HandleAddRepoButton);
topMiddleHLayout->addSpacing(30);
middleVLayout->addLayout(topMiddleHLayout);
@@ -105,37 +236,7 @@ namespace O3DE::ProjectManager
m_gemRepoInspector->setFixedWidth(240);
hLayout->addWidget(m_gemRepoInspector);
Reinit();
}
void GemRepoScreen::Reinit()
{
m_gemRepoModel->clear();
FillModel();
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemRepoListView->model()->index(0,0);
m_gemRepoListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
});
}
void GemRepoScreen::FillModel()
{
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> allGemRepoInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoInfos();
if (allGemRepoInfosResult.IsSuccess())
{
// Add all available repos to the model
const QVector<GemRepoInfo> allGemRepoInfos = allGemRepoInfosResult.GetValue();
for (const GemRepoInfo& gemRepoInfo : allGemRepoInfos)
{
m_gemRepoModel->AddGemRepo(gemRepoInfo);
}
}
else
{
QMessageBox::critical(this, tr("Operation failed"), tr("Cannot retrieve gem repos for engine.\n\nError:\n%2").arg(allGemRepoInfosResult.GetError().c_str()));
}
return contentFrame;
}
ProjectManagerScreen GemRepoScreen::GetScreenEnum()
@@ -16,6 +16,8 @@ QT_FORWARD_DECLARE_CLASS(QLabel)
QT_FORWARD_DECLARE_CLASS(QPushButton)
QT_FORWARD_DECLARE_CLASS(QHeaderView)
QT_FORWARD_DECLARE_CLASS(QTableWidget)
QT_FORWARD_DECLARE_CLASS(QFrame)
QT_FORWARD_DECLARE_CLASS(QStackedWidget)
namespace O3DE::ProjectManager
{
@@ -35,8 +37,17 @@ namespace O3DE::ProjectManager
GemRepoModel* GetGemRepoModel() const { return m_gemRepoModel; }
public slots:
void HandleAddRepoButton();
private:
void FillModel();
QFrame* CreateNoReposContent();
QFrame* CreateReposContent();
QStackedWidget* m_contentStack = nullptr;
QFrame* m_noRepoContent;
QFrame* m_repoContent;
QTableWidget* m_gemRepoHeaderTable = nullptr;
QHeaderView* m_gemRepoListHeader = nullptr;
@@ -46,6 +57,5 @@ namespace O3DE::ProjectManager
QLabel* m_lastAllUpdateLabel;
QPushButton* m_AllUpdateButton;
QPushButton* m_AddRepoButton;
};
} // namespace O3DE::ProjectManager
@@ -21,6 +21,7 @@
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/StringFunc/StringFunc.h>
@@ -921,6 +922,13 @@ namespace O3DE::ProjectManager
}
}
AZ::Outcome<void, AZStd::string> PythonBindings::AddGemRepo(const QString& repoUri)
{
// o3de scripts need method added
(void)repoUri;
return AZ::Failure<AZStd::string>("Adding Gem Repo not implemented yet in o3de scripts.");
}
GemRepoInfo PythonBindings::GemRepoInfoFromPath(pybind11::handle path, pybind11::handle pyEnginePath)
{
/* Placeholder Logic */
@@ -57,6 +57,7 @@ namespace O3DE::ProjectManager
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) override;
// Gem Repos
AZ::Outcome<void, AZStd::string> AddGemRepo(const QString& repoUri) override;
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
private:
@@ -160,6 +160,13 @@ namespace O3DE::ProjectManager
// Gem Repos
/**
* A gem repo to engine. Registers this gem repo with the current engine.
* @param repoUri the absolute filesystem path or url to the gem repo manifest file.
* @return An outcome with the success flag as well as an error message in case of a failure.
*/
virtual AZ::Outcome<void, AZStd::string> AddGemRepo(const QString& repoUri) = 0;
/**
* Get all available gem repo infos. Gathers all repos registered with the engine.
* @return A list of gem repo infos.
@@ -104,6 +104,8 @@ set(FILES
Source/GemCatalog/GemSortFilterProxyModel.cpp
Source/GemRepo/GemRepoScreen.h
Source/GemRepo/GemRepoScreen.cpp
Source/GemRepo/GemRepoAddDialog.h
Source/GemRepo/GemRepoAddDialog.cpp
Source/GemRepo/GemRepoInfo.h
Source/GemRepo/GemRepoInfo.cpp
Source/GemRepo/GemRepoInspector.h
@@ -8,6 +8,7 @@
#include <AzCore/base.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/UserSettings/UserSettings.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Memory/SystemAllocator.h>
@@ -9,6 +9,8 @@
#include <AzCore/EBus/EBus.h>
#include <memory>
namespace Aws
{
namespace CognitoIdentityProvider
@@ -9,6 +9,7 @@
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AWSCoreBus.h>
@@ -11,6 +11,7 @@
#include <AzCore/Memory/MemoryDrillerBus.h>
#include <AzCore/Debug/AssetTrackingTypesImpl.h>
#include <AzCore/Debug/AssetTracking.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/smart_ptr/make_shared.h>
///////////////////////////////////////////////////////////////////////////////
@@ -11,6 +11,7 @@
#include <AzCore/std/parallel/threadbus.h>
#include <AzCore/std/parallel/shared_mutex.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ
{
@@ -12,6 +12,7 @@
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/UserSettings/UserSettings.h>
#endif
@@ -7,6 +7,7 @@
*/
#pragma once
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/parallel/shared_mutex.h>
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/containers/unordered_set.h>
// A configurable queue that allows for multiple sources to try to control a single value in a configurable way
// such that each object can control the object independently of the other systems, while still maintaining a reasonable state.
@@ -8,6 +8,7 @@
#include <Multiplayer/Components/NetworkCharacterComponent.h>
#include <Multiplayer/Components/NetworkRigidBodyComponent.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/Visibility/EntityBoundsUnionBus.h>
#include <AzFramework/Physics/CharacterBus.h>
#include <AzFramework/Physics/Character.h>
@@ -19,7 +20,7 @@
namespace Multiplayer
{
bool CollisionLayerBasedControllerFilter(const physx::PxController& controllerA, const physx::PxController& controllerB)
{
PHYSX_SCENE_READ_LOCK(controllerA.getActor()->getScene());
@@ -82,7 +83,7 @@ namespace Multiplayer
return physx::PxQueryHitType::eNONE;
}
void NetworkCharacterComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
@@ -116,7 +117,7 @@ namespace Multiplayer
callbackManager->SetObjectPreFilter(CollisionLayerBasedObjectPreFilter);
}
}
if (!HasController())
{
GetNetworkTransformComponent()->TranslationAddEvent(m_translationEventHandler);
@@ -134,7 +135,7 @@ namespace Multiplayer
}
void NetworkCharacterComponent::OnSyncRewind()
{
{
if (m_physicsCharacter == nullptr)
{
return;
@@ -11,6 +11,7 @@
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/any.h>
#include <AzCore/std/containers/unordered_set.h>
#include <ScriptCanvasDeveloperEditor/EditorAutomation/EditorAutomationAction.h>
#include <ScriptCanvasDeveloperEditor/EditorAutomation/EditorAutomationModelIds.h>
@@ -368,7 +368,6 @@ def main(args):
if not third_party_path.is_dir():
raise common.LmbrCmdError(f"Invalid --third-party-path '{parsed_args.third_party_path}'.",
common.ERROR_CODE_INVALID_PARAMETER)
third_party_path = third_party_path.parent
build_dir = parsed_args.build_dir