merging latest development
Signed-off-by: kberg-amzn <karlberg@amazon.com>
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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/Serialization/Json/JsonImporter.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
JsonSerializationResult::ResultCode JsonImportResolver::ResolveNestedImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
|
||||
JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
for (auto& path : importPathStack)
|
||||
{
|
||||
if (importPath == path)
|
||||
{
|
||||
return settings.m_reporting(
|
||||
AZStd::string::format("'%s' was already imported in this chain. This indicates a cyclic dependency.", importPath.c_str()),
|
||||
ResultCode(Tasks::Import, Outcomes::Catastrophic), element);
|
||||
}
|
||||
}
|
||||
|
||||
importPathStack.push_back(importPath);
|
||||
AZ::StackedString importElement(AZ::StackedString::Format::JsonPointer);
|
||||
JsonImportSettings nestedImportSettings;
|
||||
nestedImportSettings.m_importer = settings.m_importer;
|
||||
nestedImportSettings.m_reporting = settings.m_reporting;
|
||||
nestedImportSettings.m_resolveFlags = ImportTracking::Dependencies;
|
||||
ResultCode result = ResolveImports(jsonDoc, allocator, importPathStack, nestedImportSettings, importElement);
|
||||
importPathStack.pop_back();
|
||||
|
||||
if (result.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode JsonImportResolver::ResolveImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
|
||||
JsonImportSettings& settings, StackedString& element)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
if (jsonDoc.IsObject())
|
||||
{
|
||||
for (auto& field : jsonDoc.GetObject())
|
||||
{
|
||||
if(strncmp(field.name.GetString(), JsonSerialization::ImportDirectiveIdentifier, field.name.GetStringLength()) == 0)
|
||||
{
|
||||
const rapidjson::Value& importDirective = field.value;
|
||||
AZ::IO::FixedMaxPath importAbsPath = importPathStack.back();
|
||||
importAbsPath.RemoveFilename();
|
||||
AZStd::string importName;
|
||||
if (importDirective.IsObject())
|
||||
{
|
||||
auto filenameField = importDirective.FindMember("filename");
|
||||
if (filenameField != importDirective.MemberEnd())
|
||||
{
|
||||
importName = AZStd::string(filenameField->value.GetString(), filenameField->value.GetStringLength());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
importName = AZStd::string(importDirective.GetString(), importDirective.GetStringLength());
|
||||
}
|
||||
importAbsPath.Append(importName);
|
||||
|
||||
rapidjson::Value patch;
|
||||
ResultCode resolveResult = settings.m_importer->ResolveImport(&jsonDoc, patch, importDirective, importAbsPath, allocator);
|
||||
if (resolveResult.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return resolveResult;
|
||||
}
|
||||
|
||||
if ((settings.m_resolveFlags & ImportTracking::Imports) == ImportTracking::Imports)
|
||||
{
|
||||
rapidjson::Pointer path(element.Get().data(), element.Get().size());
|
||||
settings.m_importer->AddImportDirective(path, importName);
|
||||
}
|
||||
if ((settings.m_resolveFlags & ImportTracking::Dependencies) == ImportTracking::Dependencies)
|
||||
{
|
||||
settings.m_importer->AddImportedFile(importAbsPath.String());
|
||||
}
|
||||
|
||||
ResultCode result = ResolveNestedImports(jsonDoc, allocator, importPathStack, settings, importAbsPath, element);
|
||||
if (result.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
settings.m_importer->ApplyPatch(jsonDoc, patch, allocator);
|
||||
}
|
||||
else if (field.value.IsObject() || field.value.IsArray())
|
||||
{
|
||||
ScopedStackedString entryName(element, AZStd::string_view(field.name.GetString(), field.name.GetStringLength()));
|
||||
ResultCode result = ResolveImports(field.value, allocator, importPathStack, settings, element);
|
||||
if (result.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(jsonDoc.IsArray())
|
||||
{
|
||||
int index = 0;
|
||||
for (rapidjson::Value::ValueIterator elem = jsonDoc.Begin(); elem != jsonDoc.End(); ++elem, ++index)
|
||||
{
|
||||
if (!elem->IsObject() && !elem->IsArray())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ScopedStackedString entryName(element, index);
|
||||
ResultCode result = ResolveImports(*elem, allocator, importPathStack, settings, element);
|
||||
if (result.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode JsonImportResolver::RestoreImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
if (jsonDoc.IsObject() || jsonDoc.IsArray())
|
||||
{
|
||||
const BaseJsonImporter::ImportDirectivesList& importDirectives = settings.m_importer->GetImportDirectives();
|
||||
for (auto& import : importDirectives)
|
||||
{
|
||||
rapidjson::Pointer importPtr = import.first;
|
||||
rapidjson::Value* currentValue = importPtr.Get(jsonDoc);
|
||||
|
||||
rapidjson::Value importedValue(rapidjson::kObjectType);
|
||||
importedValue.AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(import.second.c_str()), allocator);
|
||||
ResultCode resolveResult = JsonSerialization::ResolveImports(importedValue, allocator, settings);
|
||||
if (resolveResult.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return resolveResult;
|
||||
}
|
||||
|
||||
rapidjson::Value patch;
|
||||
settings.m_importer->CreatePatch(patch, importedValue, *currentValue, allocator);
|
||||
settings.m_importer->RestoreImport(currentValue, patch, allocator, import.second);
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode BaseJsonImporter::ResolveImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, const rapidjson::Value& importDirective,
|
||||
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
auto importedObject = JsonSerializationUtils::ReadJsonFile(importedFilePath.Native());
|
||||
if (importedObject.IsSuccess())
|
||||
{
|
||||
rapidjson::Value& importedDoc = importedObject.GetValue();
|
||||
|
||||
if (importDirective.IsObject())
|
||||
{
|
||||
auto patchField = importDirective.FindMember("patch");
|
||||
if (patchField != importDirective.MemberEnd())
|
||||
{
|
||||
patch.CopyFrom(patchField->value, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
importPtr->CopyFrom(importedDoc, allocator);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode BaseJsonImporter::RestoreImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator, const AZStd::string& importFilename)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
importPtr->SetObject();
|
||||
if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty()))
|
||||
{
|
||||
rapidjson::Value importDirective(rapidjson::kObjectType);
|
||||
importDirective.AddMember(rapidjson::StringRef("filename"), rapidjson::StringRef(importFilename.c_str()), allocator);
|
||||
importDirective.AddMember(rapidjson::StringRef("patch"), patch, allocator);
|
||||
importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), importDirective, allocator);
|
||||
}
|
||||
else
|
||||
{
|
||||
importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(importFilename.c_str()), allocator);
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode BaseJsonImporter::ApplyPatch(rapidjson::Value& target,
|
||||
const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty()))
|
||||
{
|
||||
return AZ::JsonSerialization::ApplyPatch(target, allocator, patch, JsonMergeApproach::JsonMergePatch);
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode BaseJsonImporter::CreatePatch(rapidjson::Value& patch,
|
||||
const rapidjson::Value& source, const rapidjson::Value& target,
|
||||
rapidjson::Document::AllocatorType& allocator)
|
||||
{
|
||||
return JsonSerialization::CreatePatch(patch, allocator, source, target, JsonMergeApproach::JsonMergePatch);
|
||||
}
|
||||
|
||||
void BaseJsonImporter::AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile)
|
||||
{
|
||||
m_importDirectives.emplace_back(jsonPtr, AZStd::move(importFile));
|
||||
}
|
||||
|
||||
void BaseJsonImporter::AddImportedFile(AZStd::string importedFile)
|
||||
{
|
||||
m_importedFiles.insert(AZStd::move(importedFile));
|
||||
}
|
||||
|
||||
const BaseJsonImporter::ImportDirectivesList& BaseJsonImporter::GetImportDirectives()
|
||||
{
|
||||
return m_importDirectives;
|
||||
}
|
||||
|
||||
const BaseJsonImporter::ImportedFilesList& BaseJsonImporter::GetImportedFiles()
|
||||
{
|
||||
return m_importedFiles;
|
||||
}
|
||||
} // namespace AZ
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
#include<AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/JSON/document.h>
|
||||
#include <AzCore/JSON/pointer.h>
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerializationResult.h>
|
||||
#include <AzCore/Serialization/Json/StackedString.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
struct JsonImportSettings;
|
||||
|
||||
class BaseJsonImporter
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(BaseJsonImporter, "{7B225807-7B43-430F-8B11-C794DCF5ACA5}");
|
||||
|
||||
using ImportDirectivesList = AZStd::vector<AZStd::pair<rapidjson::Pointer, AZStd::string>>;
|
||||
using ImportedFilesList = AZStd::unordered_set<AZStd::string>;
|
||||
|
||||
virtual JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, const rapidjson::Value& importDirective,
|
||||
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator);
|
||||
|
||||
virtual JsonSerializationResult::ResultCode RestoreImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator,
|
||||
const AZStd::string& importFilename);
|
||||
|
||||
virtual JsonSerializationResult::ResultCode ApplyPatch(rapidjson::Value& target,
|
||||
const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator);
|
||||
|
||||
virtual JsonSerializationResult::ResultCode CreatePatch(rapidjson::Value& patch,
|
||||
const rapidjson::Value& source, const rapidjson::Value& target,
|
||||
rapidjson::Document::AllocatorType& allocator);
|
||||
|
||||
void AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile);
|
||||
const ImportDirectivesList& GetImportDirectives();
|
||||
|
||||
void AddImportedFile(AZStd::string importedFile);
|
||||
const ImportedFilesList& GetImportedFiles();
|
||||
|
||||
virtual ~BaseJsonImporter() = default;
|
||||
|
||||
protected:
|
||||
|
||||
ImportDirectivesList m_importDirectives;
|
||||
ImportedFilesList m_importedFiles;
|
||||
};
|
||||
|
||||
enum class ImportTracking : AZ::u8
|
||||
{
|
||||
None = 0,
|
||||
Dependencies = (1<<0),
|
||||
Imports = (1<<1),
|
||||
All = (Dependencies | Imports)
|
||||
};
|
||||
AZ_DEFINE_ENUM_BITWISE_OPERATORS(ImportTracking);
|
||||
|
||||
class JsonImportResolver final
|
||||
{
|
||||
public:
|
||||
|
||||
using ImportPathStack = AZStd::vector<AZ::IO::FixedMaxPath>;
|
||||
|
||||
JsonImportResolver() = delete;
|
||||
JsonImportResolver& operator=(const JsonImportResolver& rhs) = delete;
|
||||
JsonImportResolver& operator=(JsonImportResolver&& rhs) = delete;
|
||||
JsonImportResolver(const JsonImportResolver& rhs) = delete;
|
||||
JsonImportResolver(JsonImportResolver&& rhs) = delete;
|
||||
~JsonImportResolver() = delete;
|
||||
|
||||
static JsonSerializationResult::ResultCode ResolveImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
|
||||
JsonImportSettings& settings, StackedString& element);
|
||||
|
||||
static JsonSerializationResult::ResultCode RestoreImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
|
||||
|
||||
private:
|
||||
|
||||
static JsonSerializationResult::ResultCode ResolveNestedImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
|
||||
JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element);
|
||||
};
|
||||
|
||||
|
||||
struct JsonImportSettings final
|
||||
{
|
||||
JsonSerializationResult::JsonIssueCallback m_reporting;
|
||||
|
||||
BaseJsonImporter* m_importer = nullptr;
|
||||
|
||||
ImportTracking m_resolveFlags = ImportTracking::All;
|
||||
|
||||
AZ::IO::FixedMaxPath m_loadedJsonPath;
|
||||
};
|
||||
} // namespace AZ
|
||||
@@ -706,7 +706,7 @@ namespace AZ
|
||||
rapidjson::Value(rapidjson::kNullType), field.value, element, settings);
|
||||
}
|
||||
|
||||
if (result.GetOutcome() == Outcomes::Success)
|
||||
if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults)
|
||||
{
|
||||
rapidjson::Value name;
|
||||
name.CopyFrom(field.name, allocator, true);
|
||||
@@ -717,6 +717,10 @@ namespace AZ
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultCode.Combine(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Do an extra pass to find all the fields that are removed.
|
||||
@@ -751,7 +755,7 @@ namespace AZ
|
||||
rapidjson::Value value;
|
||||
ResultCode result = CreateMergePatchInternal(value, allocator,
|
||||
rapidjson::Value(rapidjson::kNullType), field.value, element, settings);
|
||||
if (result.GetOutcome() == Outcomes::Success)
|
||||
if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults)
|
||||
{
|
||||
rapidjson::Value name;
|
||||
name.CopyFrom(field.name, allocator, true);
|
||||
@@ -762,11 +766,20 @@ namespace AZ
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultCode.Combine(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (target.MemberCount() == 0)
|
||||
{
|
||||
resultCode.Combine(settings.m_reporting("Added empty object to JSON Merge Patch.",
|
||||
ResultCode(Tasks::CreatePatch, Outcomes::Success), element));
|
||||
}
|
||||
}
|
||||
|
||||
patch = AZStd::move(resultValue);
|
||||
resultCode.Combine(ResultCode(Tasks::CreatePatch, Outcomes::Success));
|
||||
return resultCode;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <AzCore/Component/ComponentApplicationBus.h>
|
||||
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
|
||||
#include <AzCore/Serialization/Json/JsonDeserializer.h>
|
||||
#include <AzCore/Serialization/Json/JsonImporter.h>
|
||||
#include <AzCore/Serialization/Json/JsonMerger.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerializer.h>
|
||||
@@ -19,11 +20,6 @@
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
const char* JsonSerialization::TypeIdFieldIdentifier = "$type";
|
||||
const char* JsonSerialization::DefaultStringIdentifier = "{}";
|
||||
const char* JsonSerialization::KeyFieldIdentifier = "Key";
|
||||
const char* JsonSerialization::ValueFieldIdentifier = "Value";
|
||||
|
||||
namespace JsonSerializationInternal
|
||||
{
|
||||
template<typename T>
|
||||
@@ -394,6 +390,60 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode JsonSerialization::ResolveImports(
|
||||
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
if (settings.m_importer == nullptr)
|
||||
{
|
||||
AZ_Assert(false, "Importer object needs to be provided");
|
||||
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
|
||||
}
|
||||
|
||||
AZStd::string scratchBuffer;
|
||||
auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode
|
||||
{
|
||||
return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target);
|
||||
};
|
||||
if (!settings.m_reporting)
|
||||
{
|
||||
settings.m_reporting = issueReportingCallback;
|
||||
}
|
||||
|
||||
JsonImportResolver::ImportPathStack importPathStack;
|
||||
importPathStack.push_back(settings.m_loadedJsonPath);
|
||||
StackedString element(StackedString::Format::JsonPointer);
|
||||
|
||||
return JsonImportResolver::ResolveImports(jsonDoc, allocator, importPathStack, settings, element);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode JsonSerialization::RestoreImports(
|
||||
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
if (settings.m_importer == nullptr)
|
||||
{
|
||||
AZ_Assert(false, "Importer object needs to be provided");
|
||||
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
|
||||
}
|
||||
|
||||
AZStd::string scratchBuffer;
|
||||
auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode
|
||||
{
|
||||
return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target);
|
||||
};
|
||||
if (!settings.m_reporting)
|
||||
{
|
||||
settings.m_reporting = issueReportingCallback;
|
||||
}
|
||||
|
||||
settings.m_resolveFlags = ImportTracking::None;
|
||||
|
||||
return JsonImportResolver::RestoreImports(jsonDoc, allocator, settings);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode JsonSerialization::DefaultIssueReporter(AZStd::string& scratchBuffer,
|
||||
AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view path)
|
||||
{
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
namespace AZ
|
||||
{
|
||||
class BaseJsonSerializer;
|
||||
|
||||
struct JsonImportSettings;
|
||||
|
||||
enum class JsonMergeApproach
|
||||
{
|
||||
@@ -51,10 +53,11 @@ namespace AZ
|
||||
class JsonSerialization final
|
||||
{
|
||||
public:
|
||||
static const char* TypeIdFieldIdentifier;
|
||||
static const char* DefaultStringIdentifier;
|
||||
static const char* KeyFieldIdentifier;
|
||||
static const char* ValueFieldIdentifier;
|
||||
static constexpr const char* TypeIdFieldIdentifier = "$type";
|
||||
static constexpr const char* DefaultStringIdentifier = "{}";
|
||||
static constexpr const char* KeyFieldIdentifier = "Key";
|
||||
static constexpr const char* ValueFieldIdentifier = "Value";
|
||||
static constexpr const char* ImportDirectiveIdentifier = "$import";
|
||||
|
||||
//! Merges two json values together by applying "patch" to "target" using the selected merge algorithm.
|
||||
//! This version of ApplyPatch is destructive to "target". If the patch can't be correctly applied it will
|
||||
@@ -284,6 +287,22 @@ namespace AZ
|
||||
//! @return An enum containing less, equal or greater. In case of an error, the value for the enum will "error".
|
||||
static JsonSerializerCompareResult Compare(const rapidjson::Value& lhs, const rapidjson::Value& rhs);
|
||||
|
||||
//! Resolves all import directives, including nested imports, in the given document. An importer object needs to be passed
|
||||
//! in through the settings.
|
||||
//! @param jsonDoc The json document in which to resolve imports.
|
||||
//! @param allocator The allocator associated with the json document.
|
||||
//! @param settings Additional settings that control the way the imports are resolved.
|
||||
static JsonSerializationResult::ResultCode ResolveImports(
|
||||
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
|
||||
|
||||
//! Restores all import directives that were present in the json document. The same importer object that was
|
||||
//! passed into ResolveImports through the settings needs to be passed here through settings as well.
|
||||
//! @param jsonDoc The json document in which to restore imports.
|
||||
//! @param allocator The allocator associated with the json document.
|
||||
//! @param settings Additional settings that control the way the imports are restored.
|
||||
static JsonSerializationResult::ResultCode RestoreImports(
|
||||
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
|
||||
|
||||
private:
|
||||
JsonSerialization() = delete;
|
||||
~JsonSerialization() = delete;
|
||||
|
||||
@@ -69,6 +69,9 @@ namespace AZ
|
||||
case Tasks::CreatePatch:
|
||||
target.append("a create patch operation ");
|
||||
break;
|
||||
case Tasks::Import:
|
||||
target.append("an import operation");
|
||||
break;
|
||||
default:
|
||||
target.append("an unknown operation ");
|
||||
break;
|
||||
|
||||
@@ -32,7 +32,8 @@ namespace AZ
|
||||
ReadField, //!< Task to read a field from JSON to a value.
|
||||
WriteValue, //!< Task to write a value to a JSON field.
|
||||
Merge, //!< Task to merge two JSON values/documents together.
|
||||
CreatePatch //!< Task to create a patch to transform one value/document to another.
|
||||
CreatePatch, //!< Task to create a patch to transform one value/document to another.
|
||||
Import //!< Task to import a JSON document.
|
||||
};
|
||||
|
||||
//! Describes how the task was processed.
|
||||
|
||||
@@ -522,6 +522,8 @@ set(FILES
|
||||
Serialization/Json/IntSerializer.cpp
|
||||
Serialization/Json/JsonDeserializer.h
|
||||
Serialization/Json/JsonDeserializer.cpp
|
||||
Serialization/Json/JsonImporter.cpp
|
||||
Serialization/Json/JsonImporter.h
|
||||
Serialization/Json/JsonMerger.h
|
||||
Serialization/Json/JsonMerger.cpp
|
||||
Serialization/Json/JsonSerialization.h
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* 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/Serialization/Json/JsonImporter.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
#include <Tests/Serialization/Json/JsonSerializationTests.h>
|
||||
|
||||
namespace JsonSerializationTests
|
||||
{
|
||||
class JsonImportingTests;
|
||||
|
||||
class JsonImporterCustom
|
||||
: public AZ::BaseJsonImporter
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(JsonImporterCustom, "{003F5896-71E0-4A50-A14F-08C319B06AD0}");
|
||||
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, const rapidjson::Value& importDirective,
|
||||
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator) override;
|
||||
|
||||
JsonImporterCustom(JsonImportingTests* tests)
|
||||
{
|
||||
testClass = tests;
|
||||
}
|
||||
|
||||
private:
|
||||
JsonImportingTests* testClass;
|
||||
};
|
||||
|
||||
class JsonImportingTests
|
||||
: public BaseJsonSerializerFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
BaseJsonSerializerFixture::SetUp();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
BaseJsonSerializerFixture::TearDown();
|
||||
}
|
||||
|
||||
void GetTestDocument(const AZStd::string& docName, rapidjson::Document& out)
|
||||
{
|
||||
const char *objectJson = R"({
|
||||
"field_1" : "value_1",
|
||||
"field_2" : "value_2",
|
||||
"field_3" : "value_3"
|
||||
})";
|
||||
|
||||
const char *arrayJson = R"([
|
||||
{ "element_1" : "value_1" },
|
||||
{ "element_2" : "value_2" },
|
||||
{ "element_3" : "value_3" }
|
||||
])";
|
||||
|
||||
const char *nestedImportJson = R"({
|
||||
"desc" : "Nested Import",
|
||||
"obj" : {"$import" : "object.json"}
|
||||
})";
|
||||
|
||||
const char *nestedImportCycle1Json = R"({
|
||||
"desc" : "Nested Import Cycle 1",
|
||||
"obj" : {"$import" : "nested_import_c2.json"}
|
||||
})";
|
||||
|
||||
const char *nestedImportCycle2Json = R"({
|
||||
"desc" : "Nested Import Cycle 2",
|
||||
"obj" : {"$import" : "nested_import_c1.json"}
|
||||
})";
|
||||
|
||||
if (docName.compare("object.json") == 0)
|
||||
{
|
||||
out.Parse(objectJson);
|
||||
ASSERT_FALSE(out.HasParseError());
|
||||
}
|
||||
else if (docName.compare("array.json") == 0)
|
||||
{
|
||||
out.Parse(arrayJson);
|
||||
ASSERT_FALSE(out.HasParseError());
|
||||
}
|
||||
else if (docName.compare("nested_import.json") == 0)
|
||||
{
|
||||
out.Parse(nestedImportJson);
|
||||
ASSERT_FALSE(out.HasParseError());
|
||||
}
|
||||
else if (docName.compare("nested_import_c1.json") == 0)
|
||||
{
|
||||
out.Parse(nestedImportCycle1Json);
|
||||
ASSERT_FALSE(out.HasParseError());
|
||||
}
|
||||
else if (docName.compare("nested_import_c2.json") == 0)
|
||||
{
|
||||
out.Parse(nestedImportCycle2Json);
|
||||
ASSERT_FALSE(out.HasParseError());
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void TestImportLoadStore(const char* input, const char* expectedImportedValue)
|
||||
{
|
||||
m_jsonDocument->Parse(input);
|
||||
ASSERT_FALSE(m_jsonDocument->HasParseError());
|
||||
|
||||
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
|
||||
|
||||
rapidjson::Document expectedOutcome;
|
||||
expectedOutcome.Parse(expectedImportedValue);
|
||||
ASSERT_FALSE(expectedOutcome.HasParseError());
|
||||
|
||||
TestResolveImports(importerObj);
|
||||
|
||||
Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutcome.GetObject());
|
||||
|
||||
rapidjson::Document originalInput;
|
||||
originalInput.Parse(input);
|
||||
ASSERT_FALSE(originalInput.HasParseError());
|
||||
|
||||
TestRestoreImports(importerObj);
|
||||
|
||||
Expect_DocStrEq(m_jsonDocument->GetObject(), originalInput.GetObject());
|
||||
|
||||
m_jsonDocument->SetObject();
|
||||
delete importerObj;
|
||||
}
|
||||
|
||||
void TestImportCycle(const char* input)
|
||||
{
|
||||
m_jsonDocument->Parse(input);
|
||||
ASSERT_FALSE(m_jsonDocument->HasParseError());
|
||||
|
||||
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode result = TestResolveImports(importerObj);
|
||||
|
||||
EXPECT_EQ(result.GetOutcome(), AZ::JsonSerializationResult::Outcomes::Catastrophic);
|
||||
|
||||
m_jsonDocument->SetObject();
|
||||
delete importerObj;
|
||||
}
|
||||
|
||||
void TestInsertNewImport(const char* input, const char* expectedRestoredValue)
|
||||
{
|
||||
m_jsonDocument->Parse(input);
|
||||
ASSERT_FALSE(m_jsonDocument->HasParseError());
|
||||
|
||||
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
|
||||
|
||||
TestResolveImports(importerObj);
|
||||
|
||||
importerObj->AddImportDirective(rapidjson::Pointer("/object_2"), "object.json");
|
||||
|
||||
rapidjson::Document expectedOutput;
|
||||
expectedOutput.Parse(expectedRestoredValue);
|
||||
ASSERT_FALSE(expectedOutput.HasParseError());
|
||||
|
||||
TestRestoreImports(importerObj);
|
||||
|
||||
Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutput.GetObject());
|
||||
|
||||
m_jsonDocument->SetObject();
|
||||
delete importerObj;
|
||||
}
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode TestResolveImports(JsonImporterCustom* importerObj)
|
||||
{
|
||||
AZ::JsonImportSettings settings;
|
||||
settings.m_importer = importerObj;
|
||||
|
||||
return AZ::JsonSerialization::ResolveImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings);
|
||||
}
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode TestRestoreImports(JsonImporterCustom* importerObj)
|
||||
{
|
||||
AZ::JsonImportSettings settings;
|
||||
settings.m_importer = importerObj;
|
||||
|
||||
return AZ::JsonSerialization::RestoreImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings);
|
||||
}
|
||||
};
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode JsonImporterCustom::ResolveImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, const rapidjson::Value& importDirective, const AZ::IO::FixedMaxPath& importedFilePath,
|
||||
rapidjson::Document::AllocatorType& allocator)
|
||||
{
|
||||
AZ::JsonSerializationResult::ResultCode resultCode(AZ::JsonSerializationResult::Tasks::Import);
|
||||
|
||||
rapidjson::Document importedDoc;
|
||||
testClass->GetTestDocument(importedFilePath.String(), importedDoc);
|
||||
|
||||
if (importDirective.IsObject())
|
||||
{
|
||||
auto patchField = importDirective.FindMember("patch");
|
||||
if (patchField != importDirective.MemberEnd())
|
||||
{
|
||||
patch.CopyFrom(patchField->value, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
importPtr->CopyFrom(importedDoc, allocator);
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
// Test Cases
|
||||
|
||||
TEST_F(JsonImportingTests, ImportSimpleObjectTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object": {"$import" : "object.json"}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object": {
|
||||
"field_1" : "value_1",
|
||||
"field_2" : "value_2",
|
||||
"field_3" : "value_3"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, ImportSimpleObjectPatchTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object": {
|
||||
"$import" : {
|
||||
"filename" : "object.json",
|
||||
"patch" : { "field_2" : "patched_value" }
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object": {
|
||||
"field_1" : "value_1",
|
||||
"field_2" : "patched_value",
|
||||
"field_3" : "value_3"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, ImportSimpleArrayTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "simple_array_import",
|
||||
"object": {"$import" : "array.json"}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "simple_array_import",
|
||||
"object": [
|
||||
{ "element_1" : "value_1" },
|
||||
{ "element_2" : "value_2" },
|
||||
{ "element_3" : "value_3" }
|
||||
]
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, ImportSimpleArrayPatchTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "simple_array_import",
|
||||
"object": {
|
||||
"$import" : {
|
||||
"filename" : "array.json",
|
||||
"patch" : [ { "element_1" : "patched_value" } ]
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "simple_array_import",
|
||||
"object": [
|
||||
{ "element_1" : "patched_value" }
|
||||
]
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, NestedImportTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "nested_import",
|
||||
"object": {"$import" : "nested_import.json"}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "nested_import",
|
||||
"object": {
|
||||
"desc" : "Nested Import",
|
||||
"obj" : {
|
||||
"field_1" : "value_1",
|
||||
"field_2" : "value_2",
|
||||
"field_3" : "value_3"
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, NestedImportPatchTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "nested_import",
|
||||
"object": {
|
||||
"$import" : {
|
||||
"filename" : "nested_import.json",
|
||||
"patch" : { "obj" : { "field_3" : "patched_value" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "nested_import",
|
||||
"object": {
|
||||
"desc" : "Nested Import",
|
||||
"obj" : {
|
||||
"field_1" : "value_1",
|
||||
"field_2" : "value_2",
|
||||
"field_3" : "patched_value"
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, NestedImportCycleTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "nested_import_cycle",
|
||||
"object": {"$import" : "nested_import_c1.json"}
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportCycle(inputFile);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, InsertNewImportTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object_1": {"$import" : "object.json"},
|
||||
"object_2": {
|
||||
"field_1" : "other_value",
|
||||
"field_2" : "value_2",
|
||||
"field_3" : "value_3"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object_1": {"$import" : "object.json"},
|
||||
"object_2": {
|
||||
"$import" : {
|
||||
"filename" : "object.json",
|
||||
"patch" : { "field_1" : "other_value" }
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
TestInsertNewImport(inputFile, expectedOutput);
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,7 @@ set(FILES
|
||||
Serialization/Json/TestCases_Classes.cpp
|
||||
Serialization/Json/TestCases_Compare.cpp
|
||||
Serialization/Json/TestCases_Enum.cpp
|
||||
Serialization/Json/TestCases_Importing.cpp
|
||||
Serialization/Json/TestCases_Patching.cpp
|
||||
Serialization/Json/TestCases_Pointers.h
|
||||
Serialization/Json/TestCases_Pointers.cpp
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace AzFramework
|
||||
class IMatchmakingAsyncRequests
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(ISessionAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}");
|
||||
AZ_RTTI(IMatchmakingAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}");
|
||||
|
||||
IMatchmakingAsyncRequests() = default;
|
||||
virtual ~IMatchmakingAsyncRequests() = default;
|
||||
@@ -60,4 +60,31 @@ namespace AzFramework
|
||||
// @param stopMatchmakingRequest The request of StopMatchmaking operation
|
||||
virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0;
|
||||
};
|
||||
|
||||
//! MatchmakingAsyncRequestNotifications
|
||||
//! The notifications correspond to matchmaking async requests
|
||||
class MatchmakingAsyncRequestNotifications
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
// Safeguard handler for multi-threaded use case
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
|
||||
virtual void OnAcceptMatchAsyncComplete() = 0;
|
||||
|
||||
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
|
||||
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
|
||||
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
|
||||
|
||||
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
|
||||
virtual void OnStopMatchmakingAsyncComplete() = 0;
|
||||
};
|
||||
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -13,36 +13,10 @@
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
//! MatchmakingAsyncRequestNotifications
|
||||
//! The notifications correspond to matchmaking async requests
|
||||
class MatchmakingAsyncRequestNotifications
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
// Safeguard handler for multi-threaded use case
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
|
||||
virtual void OnAcceptMatchAsyncComplete() = 0;
|
||||
|
||||
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
|
||||
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
|
||||
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
|
||||
|
||||
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
|
||||
virtual void OnStopMatchmakingAsyncComplete() = 0;
|
||||
};
|
||||
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
|
||||
|
||||
//! MatchmakingNotifications
|
||||
//! The matchmaking notifications to listen for performing required operations
|
||||
class MatchAcceptanceNotifications
|
||||
//! based on matchmaking ticket event
|
||||
class MatchmakingNotifications
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
@@ -55,8 +29,18 @@ namespace AzFramework
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// OnMatchAcceptance is fired when DescribeMatchmaking ticket status is REQUIRES_ACCEPTANCE
|
||||
// OnMatchAcceptance is fired when match is found and pending on acceptance
|
||||
// Use this notification to accept found match
|
||||
virtual void OnMatchAcceptance() = 0;
|
||||
|
||||
// OnMatchComplete is fired when match is complete
|
||||
virtual void OnMatchComplete() = 0;
|
||||
|
||||
// OnMatchError is fired when match is processed with error
|
||||
virtual void OnMatchError() = 0;
|
||||
|
||||
// OnMatchFailure is fired when match is failed to complete
|
||||
virtual void OnMatchFailure() = 0;
|
||||
};
|
||||
using MatchAcceptanceNotificationBus = AZ::EBus<MatchAcceptanceNotifications>;
|
||||
using MatchmakingNotificationBus = AZ::EBus<MatchmakingNotifications>;
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace AzFramework
|
||||
AZStd::scoped_ptr<ProcessWatcher> pWatcher(LaunchProcess(processLaunchInfo, communicationType));
|
||||
if (!pWatcher)
|
||||
{
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -31,7 +31,7 @@ namespace AzFramework
|
||||
ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator();
|
||||
if (!pCommunicator || !pCommunicator->IsValid())
|
||||
{
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
||||
return false;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -308,7 +308,7 @@ namespace AzFramework
|
||||
event.data.data32[2] = 0;
|
||||
event.data.data32[3] = 1;
|
||||
event.data.data32[4] = 0;
|
||||
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
|
||||
[[maybe_unused]] xcb_void_cookie_t xcbCheckResult = xcb_send_event(
|
||||
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
|
||||
(const char*)&event);
|
||||
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_WM_STATE_FULLSCREEN");
|
||||
@@ -333,7 +333,7 @@ namespace AzFramework
|
||||
event.data.data32[2] = 0;
|
||||
event.data.data32[3] = 0;
|
||||
event.data.data32[4] = 0;
|
||||
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
|
||||
[[maybe_unused]] xcb_void_cookie_t xcbCheckResult = xcb_send_event(
|
||||
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
|
||||
(const char*)&event);
|
||||
AZ_Assert(
|
||||
|
||||
+18
@@ -7,17 +7,35 @@
|
||||
*/
|
||||
|
||||
#include <AzFramework/Application/Application.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
#include <AzFramework/XcbApplication.h>
|
||||
#endif
|
||||
|
||||
constexpr rlim_t g_minimumOpenFileHandles = 65536L;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Application::Implementation* Application::Implementation::Create()
|
||||
{
|
||||
// The default open file limit for processes may not be enough for O3DE applications.
|
||||
// We will need to increase to the recommended value if the current open file limit
|
||||
// is not sufficient.
|
||||
rlimit currentLimit;
|
||||
int get_limit_result = getrlimit(RLIMIT_NOFILE, ¤tLimit);
|
||||
AZ_Warning("Application", get_limit_result == 0, "Unable to read current ulimit open file limits");
|
||||
if ((get_limit_result == 0) && (currentLimit.rlim_cur < g_minimumOpenFileHandles || currentLimit.rlim_max < g_minimumOpenFileHandles))
|
||||
{
|
||||
rlimit newLimit;
|
||||
newLimit.rlim_cur = g_minimumOpenFileHandles; // Soft Limit
|
||||
newLimit.rlim_max = g_minimumOpenFileHandles; // Hard Limit
|
||||
[[maybe_unused]] int set_limit_result = setrlimit(RLIMIT_NOFILE, &newLimit);
|
||||
AZ_Assert(set_limit_result == 0, "Unable to update open file limits");
|
||||
}
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
return aznew XcbApplication();
|
||||
#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND
|
||||
|
||||
+71
-24
@@ -91,31 +91,42 @@ namespace AzFramework
|
||||
return processId == 0;
|
||||
}
|
||||
|
||||
/*! Executes a command in the child process after the fork operation has been executed.
|
||||
* This function will never return. If the execvp command fails this will call _exit with
|
||||
* the errno value as the return value since continuing execution after a execvp command
|
||||
* is invalid (it will be running the parent's code and in its address space and will
|
||||
* cause many issues).
|
||||
/*! Executes a command in the child process after the fork operation
|
||||
* has been executed. This function will never return. If the execvpe
|
||||
* command fails this will call _exit since continuing execution after
|
||||
* a execvpe command is invalid (it will be running the parent's code
|
||||
* and in its address space and will cause many issues).
|
||||
*
|
||||
* This function runs after a `fork()` call. `fork()` creates a copy of
|
||||
* the current process, including the current state of the process's
|
||||
* memory, at the time the call is made. However, it only creates a
|
||||
* copy of the one thread that called `fork()`. This means that if any
|
||||
* mutexes are locked by other threads at the time of `fork()`, those
|
||||
* mutexes will remain locked in the child process, with no way to
|
||||
* unlock them. So this function needs to ensure that it does as little
|
||||
* work as possible.
|
||||
*
|
||||
* \param commandAndArgs - Array of strings that has the command to execute in index 0 with any args for the command following. Last element must be a null pointer.
|
||||
* \param envionrmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer.
|
||||
* \param environmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer.
|
||||
* \param processLaunchInfo - struct containing information about luanching the command
|
||||
* \param startupInfo - struct containing information needed to startup the command
|
||||
* \param errorPipe - a pipe file descriptor used to communicate a failed execvpe call's error code to the parent process
|
||||
*/
|
||||
void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo)
|
||||
[[noreturn]] static void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo, const AZStd::array<int, 2>& errorPipe)
|
||||
{
|
||||
close(errorPipe[0]);
|
||||
|
||||
if (!processLaunchInfo.m_workingDirectory.empty())
|
||||
{
|
||||
int res = chdir(processLaunchInfo.m_workingDirectory.c_str());
|
||||
if (res != 0)
|
||||
{
|
||||
std::cerr << strerror(errno) << std::endl;
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to change the launched process' directory to '%s'.", processLaunchInfo.m_workingDirectory.c_str());
|
||||
write(errorPipe[1], &errno, sizeof(int));
|
||||
// We *have* to _exit as we are the child process and simply
|
||||
// returning at this point would mean we would start running
|
||||
// the code from our parent process and that will just wreck
|
||||
// havoc.
|
||||
_exit(errno);
|
||||
_exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,15 +146,17 @@ namespace AzFramework
|
||||
|
||||
startupInfo.SetupHandlesForChildProcess();
|
||||
|
||||
execve(commandAndArgs[0], commandAndArgs, environmentVariables);
|
||||
execvpe(commandAndArgs[0], commandAndArgs, environmentVariables);
|
||||
const int errval = errno;
|
||||
|
||||
// If we get here then execve failed to run the requested program and
|
||||
// If we get here then execvpe failed to run the requested program and
|
||||
// we have an error. In this case we need to exit the child process
|
||||
// to stop it from continuing to run as a clone of the parent
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process %s : errno = %s ", commandAndArgs[0], strerror(errno));
|
||||
std::cerr << strerror(errno) << std::endl;
|
||||
// to stop it from continuing to run as a clone of the parent.
|
||||
// Communicate the error code back to the parent via a pipe for the
|
||||
// parent to read.
|
||||
write(errorPipe[1], &errval, sizeof(errval));
|
||||
|
||||
_exit(errno);
|
||||
_exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,9 +225,8 @@ namespace AzFramework
|
||||
|
||||
AZStd::string outputString;
|
||||
bool inQuotes = false;
|
||||
for (size_t pos = 0; pos < processLaunchInfo.m_commandlineParameters.size(); ++pos)
|
||||
for (const char currentChar : processLaunchInfo.m_commandlineParameters)
|
||||
{
|
||||
char currentChar = processLaunchInfo.m_commandlineParameters[pos];
|
||||
if (currentChar == '"')
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
@@ -231,7 +243,7 @@ namespace AzFramework
|
||||
outputString.push_back(currentChar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!outputString.empty())
|
||||
{
|
||||
commandTokens.push_back(outputString);
|
||||
@@ -249,10 +261,10 @@ namespace AzFramework
|
||||
return false;
|
||||
}
|
||||
|
||||
// Because of the way execve is defined we need to copy the strings from
|
||||
// Because of the way execvpe is defined we need to copy the strings from
|
||||
// AZ::string (using c_str() returns a const char*) into a non-const char*
|
||||
|
||||
// Need to add one more as exec requires the array's last element to be a null pointer
|
||||
// Need to add one more as execvpe requires the array's last element to be a null pointer
|
||||
char** commandAndArgs = new char*[commandTokens.size() + 1];
|
||||
for (int i = 0; i < commandTokens.size(); ++i)
|
||||
{
|
||||
@@ -275,7 +287,7 @@ namespace AzFramework
|
||||
azstrcat(environmentVariable.get(), envVarString.size() + 1, envVarString.c_str());
|
||||
environmentVariablesVector.emplace_back(environmentVariable.get());
|
||||
}
|
||||
// Adding one more as exec expects the array to have a nullptr as the last element
|
||||
// Adding one more as execvpe expects the array to have a nullptr as the last element
|
||||
environmentVariablesVector.emplace_back(nullptr);
|
||||
environmentVariables = environmentVariablesVector.data();
|
||||
}
|
||||
@@ -288,15 +300,50 @@ namespace AzFramework
|
||||
AZ_Assert(environmentVariables, "Environment variables for current process not available\n");
|
||||
}
|
||||
|
||||
// Set up a pipe to communicate the error code from the subprocess's execvpe call
|
||||
AZStd::array<int, 2> childErrorPipeFds{};
|
||||
pipe(childErrorPipeFds.data());
|
||||
|
||||
// This configures the write end of the pipe to close on calls to `exec`
|
||||
fcntl(childErrorPipeFds[1], F_SETFD, fcntl(childErrorPipeFds[1], F_GETFD) | FD_CLOEXEC);
|
||||
|
||||
pid_t child_pid = fork();
|
||||
if (IsIdChildProcess(child_pid))
|
||||
{
|
||||
ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo);
|
||||
ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo, childErrorPipeFds);
|
||||
}
|
||||
processData.m_childProcessId = child_pid;
|
||||
|
||||
// Close these handles as they are only to be used by the child process
|
||||
processData.m_startupInfo.CloseAllHandles();
|
||||
close(childErrorPipeFds[1]);
|
||||
|
||||
{
|
||||
int errorCodeFromChild = 0;
|
||||
int count = 0;
|
||||
// Read from the error pipe.
|
||||
// * If the child's call to execvpe succeeded, then the pipe will
|
||||
// be closed due to setting FD_CLOEXEC on the write end of the
|
||||
// pipe. `read()` will return 0.
|
||||
// * If the child's call to execvpe failed, the child will have
|
||||
// written the error code to the pipe. `read()` will return >0, and
|
||||
// the data to be read is the error code from execvpe.
|
||||
while ((count = read(childErrorPipeFds[0], &errorCodeFromChild, sizeof(errorCodeFromChild))) == -1)
|
||||
{
|
||||
if (errno != EAGAIN && errno != EINTR)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (count)
|
||||
{
|
||||
AZ_TracePrintf("Process Watcher", "ProcessLauncher::LaunchProcess: Unable to launch process %s : errno = %s\n", commandAndArgs[0], strerror(errorCodeFromChild));
|
||||
processData.m_childProcessIsDone = true;
|
||||
child_pid = -1;
|
||||
}
|
||||
}
|
||||
close(childErrorPipeFds[0]);
|
||||
|
||||
processData.m_childProcessId = child_pid;
|
||||
|
||||
for (int i = 0; i < commandTokens.size(); i++)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
#include <dlfcn.h>
|
||||
#include <iostream>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzTest/Platform.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
@@ -20,11 +21,16 @@ public:
|
||||
explicit ModuleHandle(const std::string& lib)
|
||||
: m_libHandle(nullptr)
|
||||
{
|
||||
std::string libext = lib;
|
||||
if (!AZ::Test::EndsWith(libext, ".dylib"))
|
||||
AZ::IO::FixedMaxPath libext = AZStd::string_view{ lib.c_str(), lib.size() };
|
||||
if (!libext.Stem().Native().starts_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX))
|
||||
{
|
||||
libext += ".dylib";
|
||||
libext = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX + libext.Native();
|
||||
}
|
||||
if (libext.Extension() != AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION)
|
||||
{
|
||||
libext.Native() += AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
|
||||
}
|
||||
|
||||
m_libHandle = dlopen(libext.c_str(), RTLD_NOW);
|
||||
const char* error = dlerror();
|
||||
if (error)
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ namespace AzToolsFramework
|
||||
AZ_Assert(m_loaderInterface != nullptr,
|
||||
"Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work");
|
||||
|
||||
m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "NewLevel.prefab"));
|
||||
m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "newLevel.prefab"));
|
||||
m_sliceOwnershipService.BusConnect(m_entityContextId);
|
||||
m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage;
|
||||
m_editorSliceOwnershipService.BusConnect();
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
||||
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Math/Uuid.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
||||
@@ -38,6 +40,13 @@ namespace AzToolsFramework
|
||||
AZ::Edit::SliceFlags::DontGatherReference);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->ConstantProperty(
|
||||
"EditorPrefabComponentTypeId", BehaviorConstant(AZ::Uuid(EditorPrefabComponent::EditorPrefabComponentTypeId)))
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPrefabComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
|
||||
|
||||
@@ -16,7 +16,9 @@ namespace AzToolsFramework
|
||||
class EditorPrefabComponent : public AzToolsFramework::Components::EditorComponentBase
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(EditorPrefabComponent, "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}", EditorComponentBase);
|
||||
static constexpr const char* const EditorPrefabComponentTypeId = "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}";
|
||||
|
||||
AZ_COMPONENT(EditorPrefabComponent, EditorPrefabComponentTypeId, EditorComponentBase);
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
|
||||
|
||||
@@ -28,7 +28,9 @@ namespace AzToolsFramework::Prefab
|
||||
"Instance Entity Mapper Interface could not be found. "
|
||||
"Check that it is being correctly initialized.");
|
||||
|
||||
EditorEntityInfoNotificationBus::Handler::BusConnect();
|
||||
EditorEntityContextNotificationBus::Handler::BusConnect();
|
||||
PrefabPublicNotificationBus::Handler::BusConnect();
|
||||
AZ::Interface<PrefabFocusInterface>::Register(this);
|
||||
AZ::Interface<PrefabFocusPublicInterface>::Register(this);
|
||||
}
|
||||
@@ -37,10 +39,12 @@ namespace AzToolsFramework::Prefab
|
||||
{
|
||||
AZ::Interface<PrefabFocusPublicInterface>::Unregister(this);
|
||||
AZ::Interface<PrefabFocusInterface>::Unregister(this);
|
||||
PrefabPublicNotificationBus::Handler::BusDisconnect();
|
||||
EditorEntityContextNotificationBus::Handler::BusDisconnect();
|
||||
EditorEntityInfoNotificationBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::Initialize()
|
||||
void PrefabFocusHandler::InitializeEditorInterfaces()
|
||||
{
|
||||
m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
|
||||
AZ_Assert(
|
||||
@@ -55,13 +59,6 @@ namespace AzToolsFramework::Prefab
|
||||
"Prefab - PrefabFocusHandler - "
|
||||
"Focus Mode Interface could not be found. "
|
||||
"Check that it is being correctly initialized.");
|
||||
|
||||
m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
|
||||
AZ_Assert(
|
||||
m_instanceEntityMapperInterface,
|
||||
"Prefab - PrefabFocusHandler - "
|
||||
"Instance Entity Mapper Interface could not be found. "
|
||||
"Check that it is being correctly initialized.");
|
||||
}
|
||||
|
||||
PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId)
|
||||
@@ -90,12 +87,12 @@ namespace AzToolsFramework::Prefab
|
||||
|
||||
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
|
||||
{
|
||||
if (index < 0 || index >= m_instanceFocusVector.size())
|
||||
if (index < 0 || index >= m_instanceFocusHierarchy.size())
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex."));
|
||||
}
|
||||
|
||||
InstanceOptionalReference focusedInstance = m_instanceFocusVector[index];
|
||||
InstanceOptionalReference focusedInstance = m_instanceFocusHierarchy[index];
|
||||
|
||||
FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId());
|
||||
|
||||
@@ -134,42 +131,38 @@ namespace AzToolsFramework::Prefab
|
||||
return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on."));
|
||||
}
|
||||
|
||||
if (!m_isInitialized)
|
||||
// Close all container entities in the old path.
|
||||
CloseInstanceContainers(m_instanceFocusHierarchy);
|
||||
|
||||
m_focusedInstance = focusedInstance;
|
||||
m_focusedTemplateId = focusedInstance->get().GetTemplateId();
|
||||
|
||||
AZ::EntityId containerEntityId;
|
||||
|
||||
if (focusedInstance->get().GetParentInstance() != AZStd::nullopt)
|
||||
{
|
||||
Initialize();
|
||||
containerEntityId = focusedInstance->get().GetContainerEntityId();
|
||||
}
|
||||
|
||||
if (!m_focusedInstance.has_value() || &m_focusedInstance->get() != &focusedInstance->get())
|
||||
else
|
||||
{
|
||||
// Close all container entities in the old path
|
||||
CloseInstanceContainers(m_instanceFocusVector);
|
||||
|
||||
m_focusedInstance = focusedInstance;
|
||||
m_focusedTemplateId = focusedInstance->get().GetTemplateId();
|
||||
|
||||
AZ::EntityId containerEntityId;
|
||||
|
||||
if (focusedInstance->get().GetParentInstance() != AZStd::nullopt)
|
||||
{
|
||||
containerEntityId = focusedInstance->get().GetContainerEntityId();
|
||||
}
|
||||
else
|
||||
{
|
||||
containerEntityId = AZ::EntityId();
|
||||
}
|
||||
containerEntityId = AZ::EntityId();
|
||||
}
|
||||
|
||||
// Focus on the descendants of the container entity
|
||||
// Focus on the descendants of the container entity in the Editor, if the interface is initialized.
|
||||
if (m_focusModeInterface)
|
||||
{
|
||||
m_focusModeInterface->SetFocusRoot(containerEntityId);
|
||||
|
||||
// Refresh path variables
|
||||
RefreshInstanceFocusList();
|
||||
|
||||
// Open all container entities in the new path
|
||||
OpenInstanceContainers(m_instanceFocusVector);
|
||||
|
||||
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
|
||||
}
|
||||
|
||||
// Refresh path variables.
|
||||
RefreshInstanceFocusList();
|
||||
RefreshInstanceFocusPath();
|
||||
|
||||
// Open all container entities in the new path.
|
||||
OpenInstanceContainers(m_instanceFocusHierarchy);
|
||||
|
||||
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
@@ -220,49 +213,80 @@ namespace AzToolsFramework::Prefab
|
||||
|
||||
const int PrefabFocusHandler::GetPrefabFocusPathLength([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
|
||||
{
|
||||
return aznumeric_cast<int>(m_instanceFocusVector.size());
|
||||
return aznumeric_cast<int>(m_instanceFocusHierarchy.size());
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::OnEntityStreamLoadSuccess()
|
||||
void PrefabFocusHandler::OnContextReset()
|
||||
{
|
||||
if (!m_isInitialized)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
// Clear the old focus vector
|
||||
m_instanceFocusVector.clear();
|
||||
m_instanceFocusHierarchy.clear();
|
||||
|
||||
// Focus on the root prefab (AZ::EntityId() will default to it)
|
||||
FocusOnPrefabInstanceOwningEntityId(AZ::EntityId());
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::OnEntityInfoUpdatedName(AZ::EntityId entityId, [[maybe_unused]]const AZStd::string& name)
|
||||
{
|
||||
// Determine if the entityId is the container for any of the instances in the vector
|
||||
auto result = AZStd::find_if(
|
||||
m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end(),
|
||||
[entityId](const InstanceOptionalReference& instance)
|
||||
{
|
||||
return (instance->get().GetContainerEntityId() == entityId);
|
||||
}
|
||||
);
|
||||
|
||||
if (result != m_instanceFocusHierarchy.end())
|
||||
{
|
||||
// Refresh the path and notify changes.
|
||||
RefreshInstanceFocusPath();
|
||||
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::OnPrefabInstancePropagationEnd()
|
||||
{
|
||||
// Refresh the path and notify changes in case propagation updated any container names.
|
||||
RefreshInstanceFocusPath();
|
||||
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::RefreshInstanceFocusList()
|
||||
{
|
||||
m_instanceFocusVector.clear();
|
||||
m_instanceFocusPath.clear();
|
||||
m_instanceFocusHierarchy.clear();
|
||||
|
||||
AZStd::list<InstanceOptionalReference> instanceFocusList;
|
||||
|
||||
// Use a support list to easily push front while traversing the prefab hierarchy
|
||||
InstanceOptionalReference currentInstance = m_focusedInstance;
|
||||
while (currentInstance.has_value())
|
||||
{
|
||||
instanceFocusList.push_front(currentInstance);
|
||||
m_instanceFocusHierarchy.emplace_back(currentInstance);
|
||||
|
||||
currentInstance = currentInstance->get().GetParentInstance();
|
||||
}
|
||||
|
||||
// Populate internals using the support list
|
||||
for (auto& instance : instanceFocusList)
|
||||
// Invert the vector, since we need the top instance to be at index 0
|
||||
AZStd::reverse(m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end());
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::RefreshInstanceFocusPath()
|
||||
{
|
||||
m_instanceFocusPath.clear();
|
||||
|
||||
for (const InstanceOptionalReference& instance : m_instanceFocusHierarchy)
|
||||
{
|
||||
m_instanceFocusPath.Append(instance->get().GetContainerEntity()->get().GetName());
|
||||
m_instanceFocusVector.emplace_back(instance);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
|
||||
{
|
||||
// If this is called outside the Editor, this interface won't be initialized.
|
||||
if (!m_containerEntityInterface)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const InstanceOptionalReference& instance : instances)
|
||||
{
|
||||
if (instance.has_value())
|
||||
@@ -274,6 +298,12 @@ namespace AzToolsFramework::Prefab
|
||||
|
||||
void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
|
||||
{
|
||||
// If this is called outside the Editor, this interface won't be initialized.
|
||||
if (!m_containerEntityInterface)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const InstanceOptionalReference& instance : instances)
|
||||
{
|
||||
if (instance.has_value())
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
|
||||
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
|
||||
#include <AzToolsFramework/Prefab/Template/Template.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
@@ -30,7 +32,9 @@ namespace AzToolsFramework::Prefab
|
||||
class PrefabFocusHandler final
|
||||
: private PrefabFocusInterface
|
||||
, private PrefabFocusPublicInterface
|
||||
, private PrefabPublicNotificationBus::Handler
|
||||
, private EditorEntityContextNotificationBus::Handler
|
||||
, private EditorEntityInfoNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0);
|
||||
@@ -38,9 +42,8 @@ namespace AzToolsFramework::Prefab
|
||||
PrefabFocusHandler();
|
||||
~PrefabFocusHandler();
|
||||
|
||||
void Initialize();
|
||||
|
||||
// PrefabFocusInterface overrides ...
|
||||
void InitializeEditorInterfaces() override;
|
||||
PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override;
|
||||
TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override;
|
||||
InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override;
|
||||
@@ -54,25 +57,34 @@ namespace AzToolsFramework::Prefab
|
||||
const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override;
|
||||
|
||||
// EditorEntityContextNotificationBus overrides ...
|
||||
void OnEntityStreamLoadSuccess() override;
|
||||
void OnContextReset() override;
|
||||
|
||||
// EditorEntityInfoNotificationBus overrides ...
|
||||
void OnEntityInfoUpdatedName(AZ::EntityId entityId, const AZStd::string& name) override;
|
||||
|
||||
// PrefabPublicNotifications overrides ...
|
||||
void OnPrefabInstancePropagationEnd();
|
||||
|
||||
private:
|
||||
PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance);
|
||||
void RefreshInstanceFocusList();
|
||||
void RefreshInstanceFocusPath();
|
||||
|
||||
void OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
|
||||
void CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
|
||||
|
||||
//! The instance the editor is currently focusing on.
|
||||
InstanceOptionalReference m_focusedInstance;
|
||||
//! The templateId of the focused instance.
|
||||
TemplateId m_focusedTemplateId;
|
||||
AZStd::vector<InstanceOptionalReference> m_instanceFocusVector;
|
||||
//! The list of instances going from the root (index 0) to the focused instance.
|
||||
AZStd::vector<InstanceOptionalReference> m_instanceFocusHierarchy;
|
||||
//! A path containing the names of the containers in the instance focus hierarchy, separated with a /.
|
||||
AZ::IO::Path m_instanceFocusPath;
|
||||
|
||||
ContainerEntityInterface* m_containerEntityInterface = nullptr;
|
||||
FocusModeInterface* m_focusModeInterface = nullptr;
|
||||
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
|
||||
|
||||
bool m_isInitialized = false;
|
||||
};
|
||||
|
||||
} // namespace AzToolsFramework::Prefab
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace AzToolsFramework::Prefab
|
||||
public:
|
||||
AZ_RTTI(PrefabFocusInterface, "{F3CFA37B-5FD8-436A-9C30-60EB54E350E1}");
|
||||
|
||||
//! Initializes the editor interfaces for Prefab Focus mode.
|
||||
//! If this is not called on initialization, the Prefab Focus Mode functions will still work
|
||||
//! but won't trigger the Editor APIs to visualize focus mode on the UI.
|
||||
virtual void InitializeEditorInterfaces() = 0;
|
||||
|
||||
//! Set the focused prefab instance to the owning instance of the entityId provided.
|
||||
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
|
||||
virtual PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) = 0;
|
||||
|
||||
@@ -974,7 +974,7 @@ namespace AzToolsFramework
|
||||
return DeleteFromInstance(entityIds, true);
|
||||
}
|
||||
|
||||
PrefabOperationResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
|
||||
DuplicatePrefabResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
|
||||
{
|
||||
if (entityIds.empty())
|
||||
{
|
||||
@@ -1021,6 +1021,7 @@ namespace AzToolsFramework
|
||||
|
||||
ScopedUndoBatch undoBatch("Duplicate Entities");
|
||||
|
||||
EntityIdList duplicatedEntityAndInstanceIds;
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities");
|
||||
|
||||
@@ -1033,7 +1034,7 @@ namespace AzToolsFramework
|
||||
|
||||
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
||||
{
|
||||
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
|
||||
return AZ::Failure(retrieveEntitiesAndInstancesOutcome.TakeError());
|
||||
}
|
||||
|
||||
// Take a snapshot of the instance DOM before we manipulate it
|
||||
@@ -1044,8 +1045,6 @@ namespace AzToolsFramework
|
||||
PrefabDom instanceDomAfter;
|
||||
instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator());
|
||||
|
||||
EntityIdList duplicatedEntityAndInstanceIds;
|
||||
|
||||
// Duplicate any nested entities and instances as requested
|
||||
AZStd::unordered_map<InstanceAlias, Instance*> newInstanceAliasToOldInstanceMap;
|
||||
AZStd::unordered_map<EntityAlias, EntityAlias> duplicateEntityAliasMap;
|
||||
@@ -1114,7 +1113,7 @@ namespace AzToolsFramework
|
||||
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds);
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
return AZ::Success(AZStd::move(duplicatedEntityAndInstanceIds));
|
||||
}
|
||||
|
||||
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace AzToolsFramework
|
||||
|
||||
PrefabOperationResult DeleteEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
|
||||
PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
|
||||
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
typedef AZ::Outcome<AZ::EntityId, AZStd::string> CreatePrefabResult;
|
||||
typedef AZ::Outcome<AZ::EntityId, AZStd::string> InstantiatePrefabResult;
|
||||
typedef AZ::Outcome<EntityIdList, AZStd::string> DuplicatePrefabResult;
|
||||
typedef AZ::Outcome<void, AZStd::string> PrefabOperationResult;
|
||||
typedef AZ::Outcome<bool, AZStd::string> PrefabRequestResult;
|
||||
typedef AZ::Outcome<AZ::EntityId, AZStd::string> PrefabEntityResult;
|
||||
@@ -160,14 +161,15 @@ namespace AzToolsFramework
|
||||
/**
|
||||
* Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance.
|
||||
* @param entities The entities to duplicate.
|
||||
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
* @return An outcome object with a list of ids of target entities' duplicates if duplication succeeded;
|
||||
* on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
|
||||
virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
|
||||
|
||||
/**
|
||||
* If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting
|
||||
* the container entity into a regular entity and putting it under the parent prefab, removing the link between this
|
||||
* instance and the parent, removing links between this instance and it's nested instances, adding entities directly
|
||||
* instance and the parent, removing links between this instance and its nested instances, and adding entities directly
|
||||
* owned by this instance under the parent instance.
|
||||
* Bails if the entity is not a container entity or belongs to the level prefab instance.
|
||||
* @param containerEntityId The container entity id of the instance to detach.
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
using CreatePrefabResult = AZ::Outcome<AZ::EntityId, AZStd::string>;
|
||||
using InstantiatePrefabResult = AZ::Outcome<AZ::EntityId, AZStd::string>;
|
||||
using DuplicatePrefabResult = AZ::Outcome<EntityIdList, AZStd::string>;
|
||||
using PrefabOperationResult = AZ::Outcome<void, AZStd::string>;
|
||||
|
||||
/**
|
||||
@@ -69,6 +70,29 @@ namespace AzToolsFramework
|
||||
* Return an outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) = 0;
|
||||
|
||||
/**
|
||||
* If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting
|
||||
* the container entity into a regular entity and putting it under the parent prefab, removing the link between this
|
||||
* instance and the parent, removing links between this instance and its nested instances, and adding entities directly
|
||||
* owned by this instance under the parent instance.
|
||||
* Bails if the entity is not a container entity or belongs to the level prefab instance.
|
||||
* Return an outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) = 0;
|
||||
|
||||
/**
|
||||
* Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance.
|
||||
* Return an outcome object with a list of ids of given entities' duplicates if duplication succeeded;
|
||||
* on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
|
||||
|
||||
/**
|
||||
* Get the file path to the prefab file for the prefab instance owning the entity provided.
|
||||
* Returns the path to the prefab, or an empty path if the entity is owned by the level.
|
||||
*/
|
||||
virtual AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const = 0;
|
||||
};
|
||||
|
||||
using PrefabPublicRequestBus = AZ::EBus<PrefabPublicRequests>;
|
||||
|
||||
+17
@@ -28,6 +28,9 @@ namespace AzToolsFramework
|
||||
->Event("CreatePrefabInMemory", &PrefabPublicRequests::CreatePrefabInMemory)
|
||||
->Event("InstantiatePrefab", &PrefabPublicRequests::InstantiatePrefab)
|
||||
->Event("DeleteEntitiesAndAllDescendantsInInstance", &PrefabPublicRequests::DeleteEntitiesAndAllDescendantsInInstance)
|
||||
->Event("DetachPrefab", &PrefabPublicRequests::DetachPrefab)
|
||||
->Event("DuplicateEntitiesInInstance", &PrefabPublicRequests::DuplicateEntitiesInInstance)
|
||||
->Event("GetOwningInstancePrefabPath", &PrefabPublicRequests::GetOwningInstancePrefabPath)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -62,5 +65,19 @@ namespace AzToolsFramework
|
||||
return m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(entityIds);
|
||||
}
|
||||
|
||||
PrefabOperationResult PrefabPublicRequestHandler::DetachPrefab(const AZ::EntityId& containerEntityId)
|
||||
{
|
||||
return m_prefabPublicInterface->DetachPrefab(containerEntityId);
|
||||
}
|
||||
|
||||
DuplicatePrefabResult PrefabPublicRequestHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
|
||||
{
|
||||
return m_prefabPublicInterface->DuplicateEntitiesInInstance(entityIds);
|
||||
}
|
||||
|
||||
AZStd::string PrefabPublicRequestHandler::GetOwningInstancePrefabPath(AZ::EntityId entityId) const
|
||||
{
|
||||
return m_prefabPublicInterface->GetOwningInstancePrefabPath(entityId).Native();
|
||||
}
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -34,6 +34,9 @@ namespace AzToolsFramework
|
||||
CreatePrefabResult CreatePrefabInMemory(const EntityIdList& entityIds, AZStd::string_view filePath) override;
|
||||
InstantiatePrefabResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override;
|
||||
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
|
||||
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
|
||||
DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const override;
|
||||
|
||||
private:
|
||||
PrefabPublicInterface* m_prefabPublicInterface = nullptr;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
//PrefabInstanceUndo
|
||||
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation)
|
||||
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation)
|
||||
: PrefabUndoBase(undoOperationName)
|
||||
{
|
||||
m_useImmediatePropagation = useImmediatePropagation;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace AzToolsFramework
|
||||
: public PrefabUndoBase
|
||||
{
|
||||
public:
|
||||
explicit PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation = true);
|
||||
explicit PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation = true);
|
||||
|
||||
void Capture(
|
||||
const PrefabDom& initialState,
|
||||
|
||||
+18
-5
@@ -20,6 +20,8 @@
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzToolsFramework/SourceControl/PerforceConnection.h>
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace
|
||||
@@ -75,9 +77,17 @@ namespace AzToolsFramework
|
||||
m_resolveKey = true;
|
||||
m_testTrust = false;
|
||||
|
||||
|
||||
// set up signals before we start thread.
|
||||
m_shutdownThreadSignal = false;
|
||||
m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this));
|
||||
|
||||
// Check to see if the 'p4' command is available at the command line
|
||||
int p4VersionExitCode = QProcess::execute("p4", QStringList{ "-V" });
|
||||
m_p4ApplicationDetected = (p4VersionExitCode == 0);
|
||||
if (m_p4ApplicationDetected)
|
||||
{
|
||||
m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this));
|
||||
}
|
||||
|
||||
SourceControlConnectionRequestBus::Handler::BusConnect();
|
||||
SourceControlCommandBus::Handler::BusConnect();
|
||||
@@ -88,10 +98,13 @@ namespace AzToolsFramework
|
||||
SourceControlCommandBus::Handler::BusDisconnect();
|
||||
SourceControlConnectionRequestBus::Handler::BusDisconnect();
|
||||
|
||||
m_shutdownThreadSignal = true; // tell the thread to die.
|
||||
m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal
|
||||
m_WorkerThread.join(); // wait for the thread to finish.
|
||||
m_WorkerThread = AZStd::thread();
|
||||
if (m_p4ApplicationDetected)
|
||||
{
|
||||
m_shutdownThreadSignal = true; // tell the thread to die.
|
||||
m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal
|
||||
m_WorkerThread.join(); // wait for the thread to finish.
|
||||
m_WorkerThread = AZStd::thread();
|
||||
}
|
||||
|
||||
SetConnection(nullptr);
|
||||
}
|
||||
|
||||
@@ -260,5 +260,7 @@ namespace AzToolsFramework
|
||||
AZStd::atomic_bool m_validConnection;
|
||||
|
||||
SourceControlState m_connectionState;
|
||||
|
||||
bool m_p4ApplicationDetected { false };
|
||||
};
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+9
-15
@@ -27,6 +27,7 @@
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
@@ -135,6 +136,10 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Editor functionality for the Prefab Focus Handler
|
||||
auto prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
|
||||
prefabFocusInterface->InitializeEditorInterfaces();
|
||||
|
||||
EditorContextMenuBus::Handler::BusConnect();
|
||||
EditorEventsBus::Handler::BusConnect();
|
||||
PrefabInstanceContainerNotificationBus::Handler::BusConnect();
|
||||
@@ -250,7 +255,7 @@ namespace AzToolsFramework
|
||||
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity))
|
||||
{
|
||||
// Edit Prefab
|
||||
if (prefabWipFeaturesEnabled && !s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
|
||||
if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
|
||||
{
|
||||
QAction* editAction = menu->addAction(QObject::tr("Edit Prefab"));
|
||||
editAction->setToolTip(QObject::tr("Edit the prefab in focus mode."));
|
||||
@@ -1154,25 +1159,14 @@ namespace AzToolsFramework
|
||||
{
|
||||
s_editorEntityUiInterface->RegisterEntity(entityId, m_prefabUiHandler.GetHandlerId());
|
||||
|
||||
bool prefabWipFeaturesEnabled = false;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
||||
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
|
||||
|
||||
if (prefabWipFeaturesEnabled)
|
||||
{
|
||||
// Register entity as a container
|
||||
s_containerEntityInterface->RegisterEntityAsContainer(entityId);
|
||||
}
|
||||
// Register entity as a container
|
||||
s_containerEntityInterface->RegisterEntityAsContainer(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::OnPrefabComponentDeactivate(AZ::EntityId entityId)
|
||||
{
|
||||
bool prefabWipFeaturesEnabled = false;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
||||
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
|
||||
|
||||
if (prefabWipFeaturesEnabled && !s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId))
|
||||
if (!s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId))
|
||||
{
|
||||
// Unregister entity as a container
|
||||
s_containerEntityInterface->UnregisterEntityAsContainer(entityId);
|
||||
|
||||
@@ -316,14 +316,7 @@ namespace AzToolsFramework
|
||||
|
||||
void PrefabUiHandler::OnDoubleClick(AZ::EntityId entityId) const
|
||||
{
|
||||
bool prefabWipFeaturesEnabled = false;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
||||
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
|
||||
|
||||
if (prefabWipFeaturesEnabled)
|
||||
{
|
||||
// Focus on this prefab
|
||||
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
|
||||
}
|
||||
// Focus on this prefab
|
||||
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user