Merge branch 'stabilization/2106' into Helios_TransformImporterFixV2_stabilization_2106

This commit is contained in:
amzn-mike
2021-06-21 08:16:21 -05:00
390 changed files with 32081 additions and 18885 deletions
@@ -36,6 +36,12 @@ namespace AZ
Color* color = reinterpret_cast<Color*>(outputValue);
AZ_Assert(color, "Output value for JsonColorSerializer can't be null.");
if (IsExplicitDefault(inputValue))
{
*color = Color::CreateZero();
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, "Color value set to default of zero.");
}
switch (inputValue.GetType())
{
case rapidjson::kArrayType:
@@ -43,10 +49,14 @@ namespace AZ
case rapidjson::kObjectType:
return LoadObject(*color, inputValue, context);
case rapidjson::kStringType: // fall through
case rapidjson::kNumberType: // fall through
case rapidjson::kNullType: // fall through
case rapidjson::kFalseType: // fall through
case rapidjson::kStringType:
[[fallthrough]];
case rapidjson::kNumberType:
[[fallthrough]];
case rapidjson::kNullType:
[[fallthrough]];
case rapidjson::kFalseType:
[[fallthrough]];
case rapidjson::kTrueType:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. Colors can only be read from arrays or objects.");
@@ -91,6 +101,11 @@ namespace AZ
}
}
auto JsonColorSerializer::GetOperationsFlags() const -> OperationFlags
{
return OperationFlags::InitializeNewInstance;
}
JsonSerializationResult::Result JsonColorSerializer::LoadObject(Color& output, const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
{
@@ -28,6 +28,8 @@ namespace AZ
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) override;
OperationFlags GetOperationsFlags() const override;
private:
enum class LoadAlpha
{
@@ -263,7 +263,7 @@ namespace AZ::JsonMathMatrixSerializerInternal
template<typename MatrixType, size_t RowCount, size_t ColumnCount>
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
const rapidjson::Value& inputValue, JsonDeserializerContext& context, bool isExplicitDefault)
{
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
@@ -279,6 +279,12 @@ namespace AZ::JsonMathMatrixSerializerInternal
MatrixType* matrix = reinterpret_cast<MatrixType*>(outputValue);
AZ_Assert(matrix, "Output value for JsonMatrix%zux%zuSerializer can't be null.", RowCount, ColumnCount);
if (isExplicitDefault)
{
*matrix = MatrixType::CreateIdentity();
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, "Matrix value set to identity matrix.");
}
switch (inputValue.GetType())
{
case rapidjson::kArrayType:
@@ -381,6 +387,16 @@ namespace AZ::JsonMathMatrixSerializerInternal
namespace AZ
{
// BaseJsonMatrixSerializer
AZ_CLASS_ALLOCATOR_IMPL(BaseJsonMatrixSerializer, SystemAllocator, 0);
auto BaseJsonMatrixSerializer::GetOperationsFlags() const -> OperationFlags
{
return OperationFlags::InitializeNewInstance;
}
// Matrix3x3
AZ_CLASS_ALLOCATOR_IMPL(JsonMatrix3x3Serializer, SystemAllocator, 0);
@@ -389,10 +405,7 @@ namespace AZ
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
return JsonMathMatrixSerializerInternal::Load<Matrix3x3, 3, 3>(
outputValue,
outputValueTypeId,
inputValue,
context);
outputValue, outputValueTypeId, inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonMatrix3x3Serializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -401,11 +414,7 @@ namespace AZ
outputValue.SetObject();
return JsonMathMatrixSerializerInternal::StoreRotationAndScale<Matrix3x3>(
outputValue,
inputValue,
defaultValue,
valueTypeId,
context);
outputValue, inputValue, defaultValue, valueTypeId, context);
}
@@ -417,10 +426,7 @@ namespace AZ
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
return JsonMathMatrixSerializerInternal::Load<Matrix3x4, 3, 4>(
outputValue,
outputValueTypeId,
inputValue,
context);
outputValue, outputValueTypeId, inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonMatrix3x4Serializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -428,19 +434,11 @@ namespace AZ
{
outputValue.SetObject();
auto result = JsonMathMatrixSerializerInternal::StoreRotationAndScale<Matrix3x4>(
outputValue,
inputValue,
defaultValue,
valueTypeId,
context);
auto result =
JsonMathMatrixSerializerInternal::StoreRotationAndScale<Matrix3x4>(outputValue, inputValue, defaultValue, valueTypeId, context);
auto resultTranslation = JsonMathMatrixSerializerInternal::StoreTranslation<Matrix3x4>(
outputValue,
inputValue,
defaultValue,
valueTypeId,
context);
auto resultTranslation =
JsonMathMatrixSerializerInternal::StoreTranslation<Matrix3x4>(outputValue, inputValue, defaultValue, valueTypeId, context);
result.GetResultCode().Combine(resultTranslation);
return result;
@@ -454,10 +452,7 @@ namespace AZ
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
return JsonMathMatrixSerializerInternal::Load<Matrix4x4, 4, 4>(
outputValue,
outputValueTypeId,
inputValue,
context);
outputValue, outputValueTypeId, inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonMatrix4x4Serializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -465,19 +460,11 @@ namespace AZ
{
outputValue.SetObject();
auto result = JsonMathMatrixSerializerInternal::StoreRotationAndScale<Matrix4x4>(
outputValue,
inputValue,
defaultValue,
valueTypeId,
context);
auto result =
JsonMathMatrixSerializerInternal::StoreRotationAndScale<Matrix4x4>(outputValue, inputValue, defaultValue, valueTypeId, context);
auto resultTranslation = JsonMathMatrixSerializerInternal::StoreTranslation<Matrix4x4>(
outputValue,
inputValue,
defaultValue,
valueTypeId,
context);
auto resultTranslation =
JsonMathMatrixSerializerInternal::StoreTranslation<Matrix4x4>(outputValue, inputValue, defaultValue, valueTypeId, context);
result.GetResultCode().Combine(resultTranslation);
return result;
@@ -16,11 +16,18 @@
namespace AZ
{
class JsonMatrix3x3Serializer
: public BaseJsonSerializer
class BaseJsonMatrixSerializer : public BaseJsonSerializer
{
public:
AZ_RTTI(JsonMatrix3x3Serializer, "{8C76CD6A-8576-4604-A746-CF7A7F20F366}", BaseJsonSerializer);
AZ_RTTI(BaseJsonMatrixSerializer, "{18CA4637-C9B7-454B-9126-107E18A8C096}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
OperationFlags GetOperationsFlags() const override;
};
class JsonMatrix3x3Serializer : public BaseJsonMatrixSerializer
{
public:
AZ_RTTI(JsonMatrix3x3Serializer, "{8C76CD6A-8576-4604-A746-CF7A7F20F366}", BaseJsonMatrixSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -28,11 +35,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonMatrix3x4Serializer
: public BaseJsonSerializer
class JsonMatrix3x4Serializer : public BaseJsonMatrixSerializer
{
public:
AZ_RTTI(JsonMatrix3x4Serializer, "{E801333B-4AF1-4F43-976C-579670B02DC5}", BaseJsonSerializer);
AZ_RTTI(JsonMatrix3x4Serializer, "{E801333B-4AF1-4F43-976C-579670B02DC5}", BaseJsonMatrixSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -40,11 +46,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonMatrix4x4Serializer
: public BaseJsonSerializer
class JsonMatrix4x4Serializer : public BaseJsonMatrixSerializer
{
public:
AZ_RTTI(JsonMatrix4x4Serializer, "{46E888FC-248A-4910-9221-4E101A10AEA1}", BaseJsonSerializer);
AZ_RTTI(JsonMatrix4x4Serializer, "{46E888FC-248A-4910-9221-4E101A10AEA1}", BaseJsonMatrixSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -124,7 +124,7 @@ namespace AZ
template<typename VectorType, size_t ElementCount>
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
JsonDeserializerContext& context, bool isExplicitDefault)
{
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
@@ -138,6 +138,12 @@ namespace AZ
VectorType* vector = reinterpret_cast<VectorType*>(outputValue);
AZ_Assert(vector, "Output value for JsonVector%iSerializer can't be null.", ElementCount);
if (isExplicitDefault)
{
*vector = VectorType::CreateZero();
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, "Math vector value set to default of zero.");
}
switch (inputValue.GetType())
{
case rapidjson::kArrayType:
@@ -145,10 +151,14 @@ namespace AZ
case rapidjson::kObjectType:
return LoadObject<VectorType, ElementCount>(*vector, inputValue, context);
case rapidjson::kStringType: // fall through
case rapidjson::kNumberType: // fall through
case rapidjson::kNullType: // fall through
case rapidjson::kFalseType: // fall through
case rapidjson::kStringType:
[[fallthrough]];
case rapidjson::kNumberType:
[[fallthrough]];
case rapidjson::kNullType:
[[fallthrough]];
case rapidjson::kFalseType:
[[fallthrough]];
case rapidjson::kTrueType:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. Math vectors can only be read from arrays or objects.");
@@ -189,6 +199,16 @@ namespace AZ
}
}
// BaseJsonVectorSerializer
AZ_CLASS_ALLOCATOR_IMPL(BaseJsonVectorSerializer, SystemAllocator, 0);
auto BaseJsonVectorSerializer::GetOperationsFlags() const -> OperationFlags
{
return OperationFlags::InitializeNewInstance;
}
// Vector2
@@ -197,7 +217,8 @@ namespace AZ
JsonSerializationResult::Result JsonVector2Serializer::Load(void* outputValue, const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
return JsonMathVectorSerializerInternal::Load<Vector2, 2>(outputValue, outputValueTypeId, inputValue, context);
return JsonMathVectorSerializerInternal::Load<Vector2, 2>(
outputValue, outputValueTypeId, inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonVector2Serializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -214,7 +235,8 @@ namespace AZ
JsonSerializationResult::Result JsonVector3Serializer::Load(void* outputValue, const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
return JsonMathVectorSerializerInternal::Load<Vector3, 3>(outputValue, outputValueTypeId, inputValue, context);
return JsonMathVectorSerializerInternal::Load<Vector3, 3>(
outputValue, outputValueTypeId, inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonVector3Serializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -231,7 +253,8 @@ namespace AZ
JsonSerializationResult::Result JsonVector4Serializer::Load(void* outputValue, const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
return JsonMathVectorSerializerInternal::Load<Vector4, 4>(outputValue, outputValueTypeId, inputValue, context);
return JsonMathVectorSerializerInternal::Load<Vector4, 4>(
outputValue, outputValueTypeId, inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonVector4Serializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -252,7 +275,7 @@ namespace AZ
// check for "yaw, pitch, roll" object
if (inputValue.IsObject())
{
if (inputValue.GetObject().ObjectEmpty())
if (IsExplicitDefault(inputValue))
{
Quaternion* outQuaternion = reinterpret_cast<Quaternion*>(outputValue);
*outQuaternion = Quaternion::CreateIdentity();
@@ -283,7 +306,7 @@ namespace AZ
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Success, "Successfully read quaternion.");
}
return JsonMathVectorSerializerInternal::Load<Quaternion, 4>(outputValue, outputValueTypeId, inputValue, context);
return JsonMathVectorSerializerInternal::Load<Quaternion, 4>(outputValue, outputValueTypeId, inputValue, context, false);
}
JsonSerializationResult::Result JsonQuaternionSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -16,11 +16,18 @@
namespace AZ
{
class JsonVector2Serializer
: public BaseJsonSerializer
class BaseJsonVectorSerializer : public BaseJsonSerializer
{
public:
AZ_RTTI(JsonVector2Serializer, "{E1EAA209-9682-4120-B26B-3EDD9AD56D6F}", BaseJsonSerializer);
AZ_RTTI(BaseJsonVectorSerializer, "{C188D355-E6DF-4590-8B31-F40591F48A8E}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
OperationFlags GetOperationsFlags() const override;
};
class JsonVector2Serializer : public BaseJsonVectorSerializer
{
public:
AZ_RTTI(JsonVector2Serializer, "{E1EAA209-9682-4120-B26B-3EDD9AD56D6F}", BaseJsonVectorSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -28,11 +35,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonVector3Serializer
: public BaseJsonSerializer
class JsonVector3Serializer : public BaseJsonVectorSerializer
{
public:
AZ_RTTI(JsonVector3Serializer, "{BF82BBF3-3CD9-48DA-97CC-E4DF2EF01552}", BaseJsonSerializer);
AZ_RTTI(JsonVector3Serializer, "{BF82BBF3-3CD9-48DA-97CC-E4DF2EF01552}", BaseJsonVectorSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -40,11 +46,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonVector4Serializer
: public BaseJsonSerializer
class JsonVector4Serializer : public BaseJsonVectorSerializer
{
public:
AZ_RTTI(JsonVector4Serializer, "{05B45EA7-7102-4281-8AA0-2AC72D74AAFD}", BaseJsonSerializer);
AZ_RTTI(JsonVector4Serializer, "{05B45EA7-7102-4281-8AA0-2AC72D74AAFD}", BaseJsonVectorSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -52,11 +57,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonQuaternionSerializer
: public BaseJsonSerializer
class JsonQuaternionSerializer : public BaseJsonVectorSerializer
{
public:
AZ_RTTI(JsonQuaternionSerializer, "{18604375-3606-49AC-B366-0F6DF9149FF3}", BaseJsonSerializer);
AZ_RTTI(JsonQuaternionSerializer, "{18604375-3606-49AC-B366-0F6DF9149FF3}", BaseJsonVectorSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -33,6 +33,12 @@ namespace AZ
AZ::Transform* transformInstance = reinterpret_cast<AZ::Transform*>(outputValue);
AZ_Assert(transformInstance, "Output value for JsonTransformSerializer can't be null.");
if (IsExplicitDefault(inputValue))
{
*transformInstance = AZ::Transform::CreateIdentity();
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, "Transform value set to identity.");
}
JSR::ResultCode result(JSR::Tasks::ReadField);
{
@@ -72,7 +78,7 @@ namespace AZ
return context.Report(
result,
result.GetProcessing() != JSR::Processing::Halted ? "Succesfully loaded Transform information."
result.GetProcessing() != JSR::Processing::Halted ? "Successfully loaded Transform information."
: "Failed to load Transform information.");
}
@@ -140,4 +146,9 @@ namespace AZ
: "Failed to store Transform information.");
}
auto JsonTransformSerializer::GetOperationsFlags() const -> OperationFlags
{
return OperationFlags::InitializeNewInstance;
}
} // namespace AZ
@@ -30,6 +30,8 @@ namespace AZ
rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const Uuid& valueTypeId,
JsonSerializerContext& context) override;
OperationFlags GetOperationsFlags() const override;
private:
// Note: These need to be defined as "const char[]" instead of "const char*" so that they can be implicitly converted
// to a rapidjson::GenericStringRef<>. (This also lets rapidjson get the string length at compile time)
@@ -33,6 +33,11 @@ namespace AZ
AZStd::regex_constants::icase | AZStd::regex_constants::optimize);
}
auto JsonUuidSerializer::GetOperationsFlags() const -> OperationFlags
{
return OperationFlags::InitializeNewInstance;
}
JsonSerializationResult::Result JsonUuidSerializer::Load(void* outputValue, const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
@@ -53,13 +58,24 @@ namespace AZ
Uuid* valAsUuid = reinterpret_cast<Uuid*>(outputValue);
if (IsExplicitDefault(inputValue))
{
*valAsUuid = AZ::Uuid::CreateNull();
return MessageResult("Uuid value set to default of null.", JSR::ResultCode(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed));
}
switch (inputValue.GetType())
{
case rapidjson::kArrayType: // fallthrough
case rapidjson::kObjectType:// fallthrough
case rapidjson::kFalseType: // fallthrough
case rapidjson::kTrueType: // fallthrough
case rapidjson::kNumberType:// fallthrough
case rapidjson::kArrayType:
[[fallthrough]];
case rapidjson::kObjectType:
[[fallthrough]];
case rapidjson::kFalseType:
[[fallthrough]];
case rapidjson::kTrueType:
[[fallthrough]];
case rapidjson::kNumberType:
[[fallthrough]];
case rapidjson::kNullType:
return MessageResult("Unsupported type. Uuids can only be read from strings.",
JSR::ResultCode(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported));
@@ -41,6 +41,8 @@ namespace AZ
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) override;
OperationFlags GetOperationsFlags() const override;
//! Does the same as load, but doesn't report through the provided callback in the settings. Instead the final
//! ResultCode and message are returned and it's up to the caller to report if need needed.
MessageResult UnreportedLoad(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue);
@@ -32,13 +32,24 @@ namespace AZ
switch (inputValue.GetType())
{
case rapidjson::kArrayType:
return LoadContainer(outputValue, outputValueTypeId, inputValue, context);
return LoadContainer(outputValue, outputValueTypeId, inputValue, false, context);
case rapidjson::kObjectType: // fall through
case rapidjson::kNullType: // fall through
case rapidjson::kStringType: // fall through
case rapidjson::kFalseType: // fall through
case rapidjson::kTrueType: // fall through
case rapidjson::kObjectType:
if (IsExplicitDefault(inputValue))
{
// Because this serializer has only the operation flag "InitializeNewInstance" set, the only time this will be called with
// an explicit default is when a new instance has been created.
return LoadContainer(outputValue, outputValueTypeId, inputValue, true, context);
}
[[fallthrough]];
case rapidjson::kNullType:
[[fallthrough]];
case rapidjson::kStringType:
[[fallthrough]];
case rapidjson::kFalseType:
[[fallthrough]];
case rapidjson::kTrueType:
[[fallthrough]];
case rapidjson::kNumberType:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. AZStd::array entries can only be read from an array.");
@@ -129,7 +140,16 @@ namespace AZ
}
}
JsonSerializationResult::Result JsonArraySerializer::LoadContainer(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
auto JsonArraySerializer::GetOperationsFlags() const -> OperationFlags
{
return OperationFlags::InitializeNewInstance;
}
JsonSerializationResult::Result JsonArraySerializer::LoadContainer(
void* outputValue,
const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
bool isNewInstance,
JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult; // Used to remove name conflicts in AzCore in uber builds.
@@ -154,14 +174,7 @@ namespace AZ
"Unable to retrieve the correct container information for AZStd::array instance.");
}
const size_t size = container->Size(outputValue);
if (inputValue.Size() < size)
{
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Not enough entries in JSON array to load an AZStd::array from.");
}
ContinuationFlags flags = ContinuationFlags::None;
ContinuationFlags flags = isNewInstance ? ContinuationFlags::LoadAsNewInstance : ContinuationFlags::None;
Uuid elementTypeId = Uuid::CreateNull();
auto typeEnumCallback = [&elementTypeId, &flags](const Uuid&, const SerializeContext::ClassElement* genericClassElement)
{
@@ -175,13 +188,23 @@ namespace AZ
};
container->EnumTypes(typeEnumCallback);
const size_t size = container->Size(outputValue);
if (!isNewInstance && inputValue.Size() < size)
{
return context.Report(
JSR::Tasks::ReadField, JSR::Outcomes::Unsupported, "Not enough entries in JSON array to load an AZStd::array from.");
}
rapidjson::Value explicitDefaultValue = GetExplicitDefault();
JSR::ResultCode retVal(JSR::Tasks::ReadField);
for (size_t i = 0; i < size; ++i)
{
ScopedContextPath subPath(context, i);
void* element = container->GetElementByIndex(outputValue, nullptr, i);
JSR::ResultCode result = ContinueLoading(element, elementTypeId, inputValue[aznumeric_caster(i)], context, flags);
JSR::ResultCode result = ContinueLoading(
element, elementTypeId, isNewInstance ? explicitDefaultValue : inputValue[aznumeric_caster(i)], context, flags);
if (result.GetProcessing() == JSR::Processing::Halted)
{
return context.Report(result, "Failed to load data to element in AZStd::array.");
@@ -189,15 +212,19 @@ namespace AZ
retVal.Combine(result);
}
if (container->Size(outputValue) == inputValue.Size())
if (isNewInstance)
{
return context.Report(retVal, "Filled new instance of AZStd::array with defaults.");
}
else if (container->Size(outputValue) == inputValue.Size())
{
return context.Report(retVal, "Successfully read entries into AZStd::array.");
}
else
{
retVal.Combine(JSR::ResultCode(JSR::Tasks::ReadField, JSR::Outcomes::Skipped));
return context.Report(retVal,
"Successfully read available entries into AZStd::array, but there were still values left in the JSON array.");
return context.Report(
retVal, "Successfully read available entries into AZStd::array, but there were still values left in the JSON array.");
}
}
} // namespace AZ
@@ -30,8 +30,14 @@ namespace AZ
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) override;
OperationFlags GetOperationsFlags() const override;
protected:
JsonSerializationResult::Result LoadContainer(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonSerializationResult::Result LoadContainer(
void* outputValue,
const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
bool isNewInstance,
JsonDeserializerContext& context);
};
} // namespace AZ
@@ -216,9 +216,10 @@ namespace AZ
JsonSerializationResult::ResultCode BaseJsonSerializer::ContinueLoading(
void* object, const Uuid& typeId, const rapidjson::Value& value, JsonDeserializerContext& context, ContinuationFlags flags)
{
bool loadAsNewInstance = (flags & ContinuationFlags::LoadAsNewInstance) == ContinuationFlags::LoadAsNewInstance;
return (flags & ContinuationFlags::ResolvePointer) == ContinuationFlags::ResolvePointer
? JsonDeserializer::LoadToPointer(object, typeId, value, context)
: JsonDeserializer::Load(object, typeId, value, context);
: JsonDeserializer::Load(object, typeId, value, loadAsNewInstance, context);
}
JsonSerializationResult::ResultCode BaseJsonSerializer::ContinueStoring(
@@ -163,15 +163,20 @@ namespace AZ
enum class ContinuationFlags
{
None = 0, //! No extra flags.
ResolvePointer = 1 << 0, //! The pointer passed in contains a pointer. The (de)serializer will attempt to resolve to an instance.
ReplaceDefault = 1 << 1 //! The default value provided for storing will be replaced with a newly created one.
None = 0, //! No extra flags.
ResolvePointer = 1 << 0, //! The pointer passed in contains a pointer. The (de)serializer will attempt to resolve to an instance.
ReplaceDefault = 1 << 1, //! The default value provided for storing will be replaced with a newly created one.
LoadAsNewInstance = 1 << 2 //! Treats the value as if it's a newly created instance. This may trigger serializers marked with
//! OperationFlags::InitializeNewInstance. Used for instance by pointers or new instances added to
//! an array.
};
enum class OperationFlags
{
None = 0, //! No flags that control how the custom json serializer is used.
ManualDefault = 1 << 0 //! Even if an (explicit) default is found the custom json serializer will still be called.
None = 0, //! No flags that control how the custom json serializer is used.
ManualDefault = 1 << 0, //! Even if an (explicit) default is found the custom json serializer will still be called.
InitializeNewInstance = 1 << 1 //! If set, the custom json serializer will be called with an explicit default if a new
//! instance of its target type is created.
};
virtual ~BaseJsonSerializer() = default;
@@ -34,11 +34,16 @@ namespace AZ
case rapidjson::kArrayType:
return LoadContainer(outputValue, outputValueTypeId, inputValue, context);
case rapidjson::kObjectType: // fall through
case rapidjson::kNullType: // fall through
case rapidjson::kStringType: // fall through
case rapidjson::kFalseType: // fall through
case rapidjson::kTrueType: // fall through
case rapidjson::kObjectType:
[[fallthrough]];
case rapidjson::kNullType:
[[fallthrough]];
case rapidjson::kStringType:
[[fallthrough]];
case rapidjson::kFalseType:
[[fallthrough]];
case rapidjson::kTrueType:
[[fallthrough]];
case rapidjson::kNumberType:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. Basic containers can only be read from an array.");
@@ -123,6 +128,10 @@ namespace AZ
{
if (retVal.HasDoneWork())
{
// If at least one value was written, even if it has all defaults, then the array has
// a value written to it and is therefore not in a default state anymore.
retVal.Combine(JSR::ResultCode(JSR::Tasks::WriteValue, JSR::Outcomes::Success));
outputValue = AZStd::move(array);
return context.Report(retVal, "Content written to basic container.");
}
@@ -165,6 +174,7 @@ namespace AZ
ContinuationFlags flags = classElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER
? ContinuationFlags::ResolvePointer
: ContinuationFlags::None;
flags |= ContinuationFlags::LoadAsNewInstance;
const size_t capacity = container->IsFixedCapacity() ? container->Capacity(outputValue) : std::numeric_limits<size_t>::max();
@@ -243,6 +253,11 @@ namespace AZ
}
size_t addedCount = container->Size(outputValue) - containerSize;
if (addedCount > 0)
{
// Values were added which means the container is no longer in its default state of being empty.
retVal.Combine(JSR::ResultCode(JSR::Tasks::ReadField, JSR::Outcomes::Success));
}
AZStd::string_view message =
addedCount >= arraySize ? "Successfully read basic container.":
addedCount == 0 ? "Unable to read data for basic container." :
@@ -82,12 +82,18 @@ namespace AZ
bool* valAsBool = reinterpret_cast<bool*>(outputValue);
if (IsExplicitDefault(inputValue))
{
*valAsBool = {};
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, "Boolean value set to default of 'false'.");
}
switch (inputValue.GetType())
{
case rapidjson::kArrayType:
// fallthrough
[[fallthrough]];
case rapidjson::kObjectType:
// fallthrough
[[fallthrough]];
case rapidjson::kNullType:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. Booleans can't be read from arrays, objects or null.");
@@ -96,7 +102,7 @@ namespace AZ
return SerializerInternal::TextToValue(valAsBool, inputValue.GetString(), inputValue.GetStringLength(), context);
case rapidjson::kFalseType:
// fallthrough
[[fallthrough]];
case rapidjson::kTrueType:
*valAsBool = inputValue.GetBool();
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Success, "Successfully read boolean.");
@@ -145,4 +151,9 @@ namespace AZ
return context.Report(JSR::Tasks::WriteValue, JSR::Outcomes::DefaultsUsed, "Default boolean used.");
}
auto JsonBoolSerializer::GetOperationsFlags() const -> OperationFlags
{
return OperationFlags::InitializeNewInstance;
}
} // namespace AZ
@@ -27,5 +27,6 @@ namespace AZ
JsonDeserializerContext& context) override;
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) override;
OperationFlags GetOperationsFlags() const override;
};
} // namespace AZ
@@ -62,19 +62,26 @@ namespace AZ
}
template <typename T>
static JsonSerializationResult::Result Load(T* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context)
static JsonSerializationResult::Result Load(
T* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context, bool isExplicitDefault)
{
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
static_assert(AZStd::is_floating_point<T>::value, "Expected T to be a floating point type");
AZ_Assert(outputValue, "Expected a valid pointer to load from json value.");
if (isExplicitDefault)
{
*outputValue = {};
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, "Floating point value set to default of 0.0.");
}
switch (inputValue.GetType())
{
case rapidjson::kArrayType:
// fallthrough
[[fallthrough]];
case rapidjson::kObjectType:
// fallthrough
[[fallthrough]];
case rapidjson::kNullType:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. Floating point values can't be read from arrays, objects or null.");
@@ -83,7 +90,7 @@ namespace AZ
return TextToValue(outputValue, inputValue.GetString(), context);
case rapidjson::kFalseType:
// fallthrough
[[fallthrough]];
case rapidjson::kTrueType:
*outputValue = inputValue.GetBool() ? 1.0f : 0.0f;
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Success,
@@ -144,7 +151,8 @@ namespace AZ
"Unable to deserialize double to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerFloatingPointInternal::Load(reinterpret_cast<double*>(outputValue), inputValue, context);
return SerializerFloatingPointInternal::Load(
reinterpret_cast<double*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonDoubleSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -156,6 +164,11 @@ namespace AZ
return SerializerFloatingPointInternal::Store<double>(outputValue, inputValue, defaultValue, context);
}
auto JsonDoubleSerializer::GetOperationsFlags() const -> OperationFlags
{
return OperationFlags::InitializeNewInstance;
}
JsonSerializationResult::Result JsonFloatSerializer::Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
{
@@ -163,7 +176,8 @@ namespace AZ
"Unable to deserialize float to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerFloatingPointInternal::Load(reinterpret_cast<float*>(outputValue), inputValue, context);
return SerializerFloatingPointInternal::Load(
reinterpret_cast<float*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonFloatSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -174,4 +188,9 @@ namespace AZ
AZ_UNUSED(valueTypeId);
return SerializerFloatingPointInternal::Store<float>(outputValue, inputValue, defaultValue, context);
}
auto JsonFloatSerializer::GetOperationsFlags() const -> OperationFlags
{
return OperationFlags::InitializeNewInstance;
}
} // namespace AZ
@@ -28,6 +28,7 @@ namespace AZ
JsonDeserializerContext& context) override;
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) override;
OperationFlags GetOperationsFlags() const override;
};
class JsonFloatSerializer
@@ -40,5 +41,6 @@ namespace AZ
JsonDeserializerContext& context) override;
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) override;
OperationFlags GetOperationsFlags() const override;
};
} // namespace AZ
@@ -25,6 +25,8 @@
namespace AZ
{
AZ_CLASS_ALLOCATOR_IMPL(BaseJsonIntegerSerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonCharSerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonShortSerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonIntSerializer, SystemAllocator, 0);
@@ -56,19 +58,25 @@ namespace AZ
template <typename T>
static JsonSerializationResult::Result LoadInt(T* outputValue, const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
JsonDeserializerContext& context, bool isDefaultValue)
{
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
static_assert(AZStd::is_integral<T>(), "Expected T to be a signed or unsigned type");
AZ_Assert(outputValue, "Expected a valid pointer to load from json value.");
if (isDefaultValue)
{
*outputValue = {};
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, "Integer value set to default of zero.");
}
switch (inputValue.GetType())
{
case rapidjson::kArrayType:
// fallthrough
[[fallthrough]];
case rapidjson::kObjectType:
// fallthrough
[[fallthrough]];
case rapidjson::kNullType:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. Integers can't be read from arrays, objects or null.");
@@ -77,7 +85,7 @@ namespace AZ
return TextToValue(outputValue, inputValue.GetString(), context);
case rapidjson::kFalseType:
// fallthrough
[[fallthrough]];
case rapidjson::kTrueType:
*outputValue = inputValue.GetBool() ? 1 : 0;
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Success,
@@ -125,6 +133,11 @@ namespace AZ
}
} // namespace SerializerInternal
auto BaseJsonIntegerSerializer::GetOperationsFlags() const -> OperationFlags
{
return OperationFlags::InitializeNewInstance;
}
JsonSerializationResult::Result JsonCharSerializer::Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
{
@@ -132,7 +145,7 @@ namespace AZ
"Unable to deserialize char to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerInternal::LoadInt(reinterpret_cast<char*>(outputValue), inputValue, context);
return SerializerInternal::LoadInt(reinterpret_cast<char*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonCharSerializer::Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
@@ -151,7 +164,7 @@ namespace AZ
"Unable to deserialize short to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerInternal::LoadInt(reinterpret_cast<short*>(outputValue), inputValue, context);
return SerializerInternal::LoadInt(reinterpret_cast<short*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonShortSerializer::Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
@@ -170,7 +183,7 @@ namespace AZ
"Unable to deserialize int to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerInternal::LoadInt(reinterpret_cast<int*>(outputValue), inputValue, context);
return SerializerInternal::LoadInt(reinterpret_cast<int*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonIntSerializer::Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
@@ -189,7 +202,7 @@ namespace AZ
"Unable to deserialize long to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerInternal::LoadInt(reinterpret_cast<long*>(outputValue), inputValue, context);
return SerializerInternal::LoadInt(reinterpret_cast<long*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonLongSerializer::Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
@@ -208,7 +221,7 @@ namespace AZ
"Unable to deserialize long long to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerInternal::LoadInt(reinterpret_cast<long long*>(outputValue), inputValue, context);
return SerializerInternal::LoadInt(reinterpret_cast<long long*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonLongLongSerializer::Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
@@ -227,7 +240,8 @@ namespace AZ
"Unable to deserialize unsigned char to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerInternal::LoadInt(reinterpret_cast<unsigned char*>(outputValue), inputValue, context);
return SerializerInternal::LoadInt(
reinterpret_cast<unsigned char*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonUnsignedCharSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -246,7 +260,8 @@ namespace AZ
"Unable to deserialize unsigned short to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerInternal::LoadInt(reinterpret_cast<unsigned short*>(outputValue), inputValue, context);
return SerializerInternal::LoadInt(
reinterpret_cast<unsigned short*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonUnsignedShortSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -265,7 +280,8 @@ namespace AZ
"Unable to deserialize unsigned int to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerInternal::LoadInt(reinterpret_cast<unsigned int*>(outputValue), inputValue, context);
return SerializerInternal::LoadInt(
reinterpret_cast<unsigned int*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonUnsignedIntSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -284,7 +300,8 @@ namespace AZ
"Unable to deserialize unsigned long to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerInternal::LoadInt(reinterpret_cast<unsigned long*>(outputValue), inputValue, context);
return SerializerInternal::LoadInt(
reinterpret_cast<unsigned long*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonUnsignedLongSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -303,7 +320,8 @@ namespace AZ
"Unable to deserialize unsigned long long to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
return SerializerInternal::LoadInt(reinterpret_cast<unsigned long long*>(outputValue), inputValue, context);
return SerializerInternal::LoadInt(
reinterpret_cast<unsigned long long*>(outputValue), inputValue, context, IsExplicitDefault(inputValue));
}
JsonSerializationResult::Result JsonUnsignedLongLongSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
@@ -18,11 +18,18 @@
namespace AZ
{
class JsonCharSerializer
: public BaseJsonSerializer
class BaseJsonIntegerSerializer : public BaseJsonSerializer
{
public:
AZ_RTTI(JsonCharSerializer, "{CA2A4AAC-3068-40B2-94F8-A537FBA8236E}", BaseJsonSerializer);
AZ_RTTI(BaseJsonIntegerSerializer, "{FD060F54-D3B5-4D5B-B64A-AFE371CD6F20}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
OperationFlags GetOperationsFlags() const override;
};
class JsonCharSerializer : public BaseJsonIntegerSerializer
{
public:
AZ_RTTI(JsonCharSerializer, "{CA2A4AAC-3068-40B2-94F8-A537FBA8236E}", BaseJsonIntegerSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -30,11 +37,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonShortSerializer
: public BaseJsonSerializer
class JsonShortSerializer : public BaseJsonIntegerSerializer
{
public:
AZ_RTTI(JsonShortSerializer, "{3D6789BD-231B-4E5D-B81D-609E71A2BCB5}", BaseJsonSerializer);
AZ_RTTI(JsonShortSerializer, "{3D6789BD-231B-4E5D-B81D-609E71A2BCB5}", BaseJsonIntegerSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -42,11 +48,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonIntSerializer
: public BaseJsonSerializer
class JsonIntSerializer : public BaseJsonIntegerSerializer
{
public:
AZ_RTTI(JsonIntSerializer, "{29E26946-0F1F-44B0-A098-1171B7B0C8FA}", BaseJsonSerializer);
AZ_RTTI(JsonIntSerializer, "{29E26946-0F1F-44B0-A098-1171B7B0C8FA}", BaseJsonIntegerSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -54,11 +59,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonLongSerializer
: public BaseJsonSerializer
class JsonLongSerializer : public BaseJsonIntegerSerializer
{
public:
AZ_RTTI(JsonLongSerializer, "{0EB432D0-A0C8-43B2-9D65-A73A4D6DFE3E}", BaseJsonSerializer);
AZ_RTTI(JsonLongSerializer, "{0EB432D0-A0C8-43B2-9D65-A73A4D6DFE3E}", BaseJsonIntegerSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -66,11 +70,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonLongLongSerializer
: public BaseJsonSerializer
class JsonLongLongSerializer : public BaseJsonIntegerSerializer
{
public:
AZ_RTTI(JsonLongLongSerializer, "{5E7967DE-A4DC-40E1-81A1-2896A054BB8A}", BaseJsonSerializer);
AZ_RTTI(JsonLongLongSerializer, "{5E7967DE-A4DC-40E1-81A1-2896A054BB8A}", BaseJsonIntegerSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -78,11 +81,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonUnsignedCharSerializer
: public BaseJsonSerializer
class JsonUnsignedCharSerializer : public BaseJsonIntegerSerializer
{
public:
AZ_RTTI(JsonUnsignedCharSerializer, "{1E6D606F-8490-4736-AAFF-91046FDEA2BB}", BaseJsonSerializer);
AZ_RTTI(JsonUnsignedCharSerializer, "{1E6D606F-8490-4736-AAFF-91046FDEA2BB}", BaseJsonIntegerSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -90,11 +92,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonUnsignedShortSerializer
: public BaseJsonSerializer
class JsonUnsignedShortSerializer : public BaseJsonIntegerSerializer
{
public:
AZ_RTTI(JsonUnsignedShortSerializer, "{3C92D2CC-CB13-4A40-B779-47562EE36451}", BaseJsonSerializer);
AZ_RTTI(JsonUnsignedShortSerializer, "{3C92D2CC-CB13-4A40-B779-47562EE36451}", BaseJsonIntegerSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -102,11 +103,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonUnsignedIntSerializer
: public BaseJsonSerializer
class JsonUnsignedIntSerializer : public BaseJsonIntegerSerializer
{
public:
AZ_RTTI(JsonUnsignedIntSerializer, "{70C0714A-690D-4F30-8986-ABC9DEFE9D62}", BaseJsonSerializer);
AZ_RTTI(JsonUnsignedIntSerializer, "{70C0714A-690D-4F30-8986-ABC9DEFE9D62}", BaseJsonIntegerSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -114,11 +114,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonUnsignedLongSerializer
: public BaseJsonSerializer
class JsonUnsignedLongSerializer : public BaseJsonIntegerSerializer
{
public:
AZ_RTTI(JsonUnsignedLongSerializer, "{28E5499F-6AF4-4778-AE14-66BA40B56247}", BaseJsonSerializer);
AZ_RTTI(JsonUnsignedLongSerializer, "{28E5499F-6AF4-4778-AE14-66BA40B56247}", BaseJsonIntegerSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -126,11 +125,10 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonUnsignedLongLongSerializer
: public BaseJsonSerializer
class JsonUnsignedLongLongSerializer : public BaseJsonIntegerSerializer
{
public:
AZ_RTTI(JsonUnsignedLongLongSerializer, "{AB048BB3-C280-4166-9E2E-54CE2C3413CA}", BaseJsonSerializer);
AZ_RTTI(JsonUnsignedLongLongSerializer, "{AB048BB3-C280-4166-9E2E-54CE2C3413CA}", BaseJsonIntegerSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -19,25 +19,30 @@
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Serialization/Json/StackedString.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
JsonSerializationResult::ResultCode JsonDeserializer::DeserializerDefaultCheck(BaseJsonSerializer* serializer, void* object,
const Uuid& typeId, const rapidjson::Value& value, JsonDeserializerContext& context)
const Uuid& typeId,const rapidjson::Value& value, bool isNewInstance, JsonDeserializerContext& context)
{
using namespace AZ::JsonSerializationResult;
bool isExplicitDefault = IsExplicitDefault(value);
bool manuallyDefaults = (serializer->GetOperationsFlags() & BaseJsonSerializer::OperationFlags::ManualDefault) ==
BaseJsonSerializer::OperationFlags::ManualDefault;
return !isExplicitDefault || (isExplicitDefault && manuallyDefaults)
bool initializeNewInstance = (serializer->GetOperationsFlags() & BaseJsonSerializer::OperationFlags::InitializeNewInstance) ==
BaseJsonSerializer::OperationFlags::InitializeNewInstance;
return
!isExplicitDefault || (isExplicitDefault && manuallyDefaults) || (isExplicitDefault && isNewInstance && initializeNewInstance)
? serializer->Load(object, typeId, value, context)
: context.Report(Tasks::ReadField, Outcomes::DefaultsUsed, "Value has an explicit default.");
}
JsonSerializationResult::ResultCode JsonDeserializer::Load(void* object, const Uuid& typeId, const rapidjson::Value& value,
JsonDeserializerContext& context)
JsonSerializationResult::ResultCode JsonDeserializer::Load(
void* object, const Uuid& typeId, const rapidjson::Value& value, bool isNewInstance, JsonDeserializerContext& context)
{
using namespace AZ::JsonSerializationResult;
@@ -50,7 +55,7 @@ namespace AZ
BaseJsonSerializer* serializer = context.GetRegistrationContext()->GetSerializerForType(typeId);
if (serializer)
{
return DeserializerDefaultCheck(serializer, object, typeId, value, context);
return DeserializerDefaultCheck(serializer, object, typeId, value, isNewInstance, context);
}
const SerializeContext::ClassData* classData = context.GetSerializeContext()->FindClassData(typeId);
@@ -72,7 +77,7 @@ namespace AZ
serializer = context.GetRegistrationContext()->GetSerializerForType(classData->m_azRtti->GetGenericTypeId());
if (serializer)
{
return DeserializerDefaultCheck(serializer, object, typeId, value, context);
return DeserializerDefaultCheck(serializer, object, typeId, value, isNewInstance, context);
}
}
@@ -133,7 +138,7 @@ namespace AZ
const SerializeContext::ClassData* resolvedClassData = context.GetSerializeContext()->FindClassData(resolvedTypeId);
if (resolvedClassData)
{
status = JsonDeserializer::Load(*objectPtr, resolvedTypeId, value, context);
status = JsonDeserializer::Load(*objectPtr, resolvedTypeId, value, true, context);
*objectPtr = resolvedClassData->m_azRtti->Cast(*objectPtr, typeId);
@@ -174,7 +179,7 @@ namespace AZ
}
else
{
return Load(object, classElement.m_typeId, value, context);
return Load(object, classElement.m_typeId, value, false, context);
}
}
@@ -591,7 +596,9 @@ namespace AZ
}
else
{
status = context.Report(Tasks::RetrieveInfo, Outcomes::Unknown, "Serialization information for target type not found.");
using ReporterString = AZStd::fixed_string<1024>;
status = context.Report(Tasks::RetrieveInfo, Outcomes::Unknown,
ReporterString::format("Serialization information for target type %s not found.", loadedTypeId.m_typeId.ToString<ReporterString>().c_str()));
return ResolvePointerResult::FullyProcessed;
}
objectType = loadedTypeId.m_typeId;
@@ -58,8 +58,8 @@ namespace AZ
JsonDeserializer(const JsonDeserializer& rhs) = delete;
JsonDeserializer(JsonDeserializer&& rhs) = delete;
static JsonSerializationResult::ResultCode Load(void* object, const Uuid& typeId, const rapidjson::Value& value,
JsonDeserializerContext& context);
static JsonSerializationResult::ResultCode Load(
void* object, const Uuid& typeId, const rapidjson::Value& value, bool isNewInstance, JsonDeserializerContext& context);
static JsonSerializationResult::ResultCode LoadToPointer(void* object, const Uuid& typeId, const rapidjson::Value& value,
JsonDeserializerContext& context);
@@ -120,6 +120,7 @@ namespace AZ
void* object,
const Uuid& typeId,
const rapidjson::Value& value,
bool isNewInstance,
JsonDeserializerContext& context);
};
} // namespace AZ
@@ -249,7 +249,7 @@ namespace AZ
{
StackedString path(StackedString::Format::JsonPointer);
JsonDeserializerContext context(settings);
result = JsonDeserializer::Load(object, objectType, root, context);
result = JsonDeserializer::Load(object, objectType, root, false, context);
}
return result;
}
@@ -190,6 +190,12 @@ namespace AZ
}
size_t addedCount = container->Size(outputValue) - containerSize;
if (addedCount > 0)
{
// If at least one entry was added then the map is no longer in it's default state so
// mark is with success so the result can at best be partial defaults.
retVal.Combine(JSR::ResultCode(JSR::Tasks::ReadField, JSR::Outcomes::Success));
}
AZStd::string_view message =
addedCount >= maximumSize ? "Successfully read associative container." :
addedCount == 0 ? "Unable to read data for the associative container." :
@@ -215,10 +221,10 @@ namespace AZ
// Load key
void* keyAddress = pairContainer->GetElementByIndex(address, pairElement, 0);
AZ_Assert(keyAddress, "Element reserved for associative container, but unable to retrieve address of the key.");
ContinuationFlags keyLoadFlags = ContinuationFlags::None;
ContinuationFlags keyLoadFlags = ContinuationFlags::LoadAsNewInstance;
if (keyElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER)
{
keyLoadFlags = ContinuationFlags::ResolvePointer;
keyLoadFlags |= ContinuationFlags::ResolvePointer;
*reinterpret_cast<void**>(keyAddress) = nullptr;
}
JSR::ResultCode keyResult = ContinueLoading(keyAddress, keyElement->m_typeId, key, context, keyLoadFlags);
@@ -231,10 +237,10 @@ namespace AZ
// Load value
void* valueAddress = pairContainer->GetElementByIndex(address, pairElement, 1);
AZ_Assert(valueAddress, "Element reserved for associative container, but unable to retrieve address of the value.");
ContinuationFlags valueLoadFlags = ContinuationFlags::None;
ContinuationFlags valueLoadFlags = ContinuationFlags::LoadAsNewInstance;
if (valueElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER)
{
valueLoadFlags = ContinuationFlags::ResolvePointer;
valueLoadFlags |= ContinuationFlags::ResolvePointer;
*reinterpret_cast<void**>(valueAddress) = nullptr;
}
JSR::ResultCode valueResult = ContinueLoading(valueAddress, valueElement->m_typeId, value, context, valueLoadFlags);
@@ -34,13 +34,24 @@ namespace AZ
switch (inputValue.GetType())
{
case rapidjson::kArrayType:
return LoadContainer(outputValue, outputValueTypeId, inputValue, context);
return LoadContainer(outputValue, outputValueTypeId, inputValue, false, context);
case rapidjson::kObjectType: // fall through
case rapidjson::kNullType: // fall through
case rapidjson::kStringType: // fall through
case rapidjson::kFalseType: // fall through
case rapidjson::kTrueType: // fall through
case rapidjson::kObjectType:
if (IsExplicitDefault(inputValue))
{
// Because this serializer has only the operation flag "InitializeNewInstance" set, the only time this will be called with
// an explicit default is when a new instance has been created.
return LoadContainer(outputValue, outputValueTypeId, inputValue, true, context);
}
[[fallthrough]];
case rapidjson::kNullType:
[[fallthrough]];
case rapidjson::kStringType:
[[fallthrough]];
case rapidjson::kFalseType:
[[fallthrough]];
case rapidjson::kTrueType:
[[fallthrough]];
case rapidjson::kNumberType:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. AZStd::pair or AZStd::tuple can only be read from an array.");
@@ -127,8 +138,13 @@ namespace AZ
}
}
auto JsonTupleSerializer::GetOperationsFlags() const -> OperationFlags
{
return OperationFlags::InitializeNewInstance;
}
JsonSerializationResult::Result JsonTupleSerializer::LoadContainer(void* outputValue, const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
const rapidjson::Value& inputValue, bool isNewInstance, JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult; // Used to remove name conflicts in AzCore in uber builds.
@@ -154,13 +170,6 @@ namespace AZ
};
container->EnumTypes(typeCountCallback);
rapidjson::SizeType arraySize = inputValue.Size();
if (arraySize < typeCount)
{
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Not enough entries in array to load an AZStd::pair or AZStd::tuple from.");
}
AZStd::vector<const SerializeContext::ClassElement*> classElements;
classElements.reserve(typeCount);
auto typeEnumCallback = [&classElements](const Uuid&, const SerializeContext::ClassElement* genericClassElement)
@@ -171,46 +180,80 @@ namespace AZ
container->EnumTypes(typeEnumCallback);
JSR::ResultCode retVal(JSR::Tasks::ReadField);
rapidjson::SizeType arrayIndex = 0;
size_t numElementsWritten = 0;
for (size_t i = 0; i < typeCount; ++i)
if (isNewInstance)
{
ScopedContextPath subPath(context, i);
void* elementAddress = container->GetElementByIndex(outputValue, nullptr, i);
AZ_Assert(elementAddress, "Address of AZStd::pair or AZStd::tuple element %zu could not be retrieved.", i);
rapidjson::Value explicitDefaultValue = GetExplicitDefault();
ContinuationFlags flags = classElements[i]->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER
? ContinuationFlags::ResolvePointer
: ContinuationFlags::None;
while (arrayIndex < inputValue.Size())
for (size_t i = 0; i < typeCount; ++i)
{
JSR::ResultCode result = ContinueLoading(elementAddress, classElements[i]->m_typeId, inputValue[arrayIndex], context, flags);
ScopedContextPath subPath(context, i);
void* elementAddress = container->GetElementByIndex(outputValue, nullptr, i);
AZ_Assert(elementAddress, "Address of AZStd::pair or AZStd::tuple element %zu could not be retrieved.", i);
ContinuationFlags flags = ContinuationFlags::LoadAsNewInstance;
flags |=
(classElements[i]->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER ? ContinuationFlags::ResolvePointer
: ContinuationFlags::None);
JSR::ResultCode result = ContinueLoading(elementAddress, classElements[i]->m_typeId, explicitDefaultValue, context, flags);
retVal.Combine(result);
arrayIndex++;
if (result.GetProcessing() == JSR::Processing::Halted)
{
return context.Report(retVal, "Failed to read element for AZStd::pair or AZStd::tuple.");
}
else if (result.GetProcessing() != JSR::Processing::Altered)
{
numElementsWritten++;
break;
}
}
}
if (numElementsWritten < typeCount)
{
AZStd::string_view message = numElementsWritten == 0 ?
"Unable to read data for AZStd::pair or AZStd::tuple." :
"Partially read data for AZStd::pair or AZStd::tuple.";
return context.Report(retVal, message);
return context.Report(retVal, "Initialized AZStd::pair or AZStd::tuple to defaults.");
}
else
{
return context.Report(retVal, "Successfully read AZStd::pair or AZStd::tuple.");
if (inputValue.Size() < typeCount)
{
return context.Report(
JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Not enough entries in array to load an AZStd::pair or AZStd::tuple from.");
}
rapidjson::SizeType arrayIndex = 0;
size_t numElementsWritten = 0;
for (size_t i = 0; i < typeCount; ++i)
{
ScopedContextPath subPath(context, i);
void* elementAddress = container->GetElementByIndex(outputValue, nullptr, i);
AZ_Assert(elementAddress, "Address of AZStd::pair or AZStd::tuple element %zu could not be retrieved.", i);
ContinuationFlags flags =
(classElements[i]->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER ? ContinuationFlags::ResolvePointer
: ContinuationFlags::None);
while (arrayIndex < inputValue.Size())
{
JSR::ResultCode result =
ContinueLoading(elementAddress, classElements[i]->m_typeId, inputValue[arrayIndex], context, flags);
retVal.Combine(result);
arrayIndex++;
if (result.GetProcessing() == JSR::Processing::Halted)
{
return context.Report(retVal, "Failed to read element for AZStd::pair or AZStd::tuple.");
}
else if (result.GetProcessing() != JSR::Processing::Altered)
{
numElementsWritten++;
break;
}
}
}
if (numElementsWritten < typeCount)
{
AZStd::string_view message = numElementsWritten == 0 ? "Unable to read data for AZStd::pair or AZStd::tuple."
: "Partially read data for AZStd::pair or AZStd::tuple.";
return context.Report(retVal, message);
}
else
{
return context.Report(retVal, "Successfully read AZStd::pair or AZStd::tuple.");
}
}
}
} // namespace AZ
@@ -23,13 +23,20 @@ namespace AZ
public:
AZ_RTTI(JsonTupleSerializer, "{1AA0ADC1-395A-4223-8A73-304ACDEE7793}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) override;
OperationFlags GetOperationsFlags() const override;
private:
JsonSerializationResult::Result LoadContainer(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonSerializationResult::Result LoadContainer(
void* outputValue,
const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
bool isNewInstance,
JsonDeserializerContext& context);
};
} // namespace AZ
@@ -816,7 +816,11 @@ namespace AZ::SettingsRegistryMergeUtils
}
}
void MergeSettingsToRegistry_CommandLine(SettingsRegistryInterface& registry, const AZ::CommandLine& commandLine, bool executeCommands)
// This function intentionally copies `commandLine`. It looks like it only uses it as a const reference, but the
// code in the loop makes calls that mutates the `commandLine` instance, invalidating the iterators. Making a copy
// ensures that the iterators remain valid.
// NOLINTNEXTLINE(performance-unnecessary-value-param)
void MergeSettingsToRegistry_CommandLine(SettingsRegistryInterface& registry, AZ::CommandLine commandLine, bool executeCommands)
{
// Iterate over all the command line options in order to parse the --regset and --regremove
// arguments in the order they were supplied
@@ -831,7 +835,7 @@ namespace AZ::SettingsRegistryMergeUtils
continue;
}
}
if (commandArgument.m_option == "regremove")
else if (commandArgument.m_option == "regremove")
{
if (!registry.Remove(commandArgument.m_value))
{
@@ -15,11 +15,7 @@
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Settings/SettingsRegistry.h>
namespace AZ
{
class CommandLine;
}
#include <AzCore/Settings/CommandLine.h>
namespace AZ::IO
{
@@ -217,7 +213,7 @@ namespace AZ::SettingsRegistryMergeUtils
//! example: --regdump /My/Array/With/Objects
//! --regdumpall Dumps the entire settings registry to output.
//! Note that this function is only called in development builds and is compiled out in release builds.
void MergeSettingsToRegistry_CommandLine(SettingsRegistryInterface& registry, const AZ::CommandLine& commandLine, bool executeCommands);
void MergeSettingsToRegistry_CommandLine(SettingsRegistryInterface& registry, AZ::CommandLine commandLine, bool executeCommands);
//! Stores the command line settings into the Setting Registry
//! The arguments can be used later anywhere the command line is needed
@@ -168,6 +168,10 @@ namespace JsonSerializationTests
{
features.EnableJsonType(rapidjson::kObjectType);
features.m_typeToInject = rapidjson::kNullType;
// Assets are not fully registered with the Serialize Context for historical reasons. Due to the missing
// information the Json Serializer Conformity Tests can't run the subsection of tests that explicitly
// require the missing information.
features.m_enableNewInstanceTests = false;
}
bool AreEqual(const Asset& lhs, const Asset& rhs) override
@@ -32,6 +32,11 @@ namespace JsonSerializationTests
AZ::NameDictionary::Destroy();
}
void Reflect(AZStd::unique_ptr<AZ::SerializeContext>& context)
{
AZ::Name::Reflect(context.get());
}
void Reflect(AZStd::unique_ptr<AZ::JsonRegistrationContext>& context)
{
AZ::Name::Reflect(context.get());
@@ -126,9 +126,9 @@ namespace JsonSerializationTests
{
auto array = AZStd::shared_ptr<Array>(new Array(), Deleter);
(*array)[0] = nullptr;
(*array)[1] = aznew MultipleInheritence();
(*array)[1] = nullptr;
(*array)[2] = nullptr;
(*array)[3] = aznew MultipleInheritence();
(*array)[3] = nullptr;
return array;
}
@@ -154,6 +154,7 @@ namespace JsonSerializationTests
null,
null,
{
"$type": "MultipleInheritence",
"base_var": 242.0,
"var1" : 142
}
@@ -246,56 +247,21 @@ namespace JsonSerializationTests
])";
}
AZStd::string_view GetJsonFor_Store_SerializeFullySetInstance() override
{
// This is a unique situation because the $type is determined separate from other values, so all
// member values can be changed, but since the default type matches the stored type the $type
// will only be written if default values are explicitly kept.
return R"(
[
{
"$type": "MultipleInheritence",
"base_var": 1142.0,
"base2_var1": 1242.0,
"base2_var2": 1342.0,
"base2_var3": 1442.0,
"var1" : 1542,
"var2" : 1642.0
},
{
"base_var": 2142.0,
"base2_var1": 2242.0,
"base2_var2": 2342.0,
"base2_var3": 2442.0,
"var1" : 2542,
"var2" : 2642.0
},
{
"$type": "MultipleInheritence",
"base_var": 3142.0,
"base2_var1": 3242.0,
"base2_var2": 3342.0,
"base2_var3": 3442.0,
"var1" : 3542,
"var2" : 3642.0
},
{
"base_var": 4142.0,
"base2_var1": 4242.0,
"base2_var2": 4342.0,
"base2_var3": 4442.0,
"var1" : 4542,
"var2" : 4642.0
}
])";
}
void Reflect(AZStd::unique_ptr<AZ::SerializeContext>& context) override
{
Base::Reflect(context);
MultipleInheritence::Reflect(context, true);
}
void ConfigureFeatures(JsonSerializerConformityTestDescriptorFeatures& features) override
{
Base::ConfigureFeatures(features);
// These tests don't work with pointers because there'll be a random value in the pointer
// which the Json Serialization will try to delete. The POD version of these tests already cover
// these cases.
features.m_enableNewInstanceTests = false;
}
bool AreEqual(const Array& lhs, const Array& rhs) override
{
size_t size = lhs.size();
@@ -311,6 +277,11 @@ namespace JsonSerializationTests
return rhs[i] == nullptr;
}
if (rhs[i] == nullptr)
{
return false;
}
if (!static_cast<const MultipleInheritence*>(lhs[i])->Equals(*static_cast<const MultipleInheritence*>(rhs[i]), true))
{
return false;
@@ -54,6 +54,11 @@ namespace JsonSerializationTests
return AZStd::make_shared<Container>(Container{ 188, 288, 388 });
}
AZStd::shared_ptr<Container> CreateSingleArrayDefaultInstance() override
{
return AZStd::make_shared<Container>(Container{ 0 });
}
AZStd::string_view GetJsonForFullySetInstance() override
{
return "[188, 288, 388]";
@@ -120,6 +125,13 @@ namespace JsonSerializationTests
&SimplePointerTestDescription::Delete);
}
AZStd::shared_ptr<Container> CreateSingleArrayDefaultInstance() override
{
int* value = reinterpret_cast<int*>(azmalloc(sizeof(int), alignof(int)));
*value = 0;
return AZStd::shared_ptr<Container>(new Container{ value }, &SimplePointerTestDescription::Delete);
}
AZStd::string_view GetJsonForFullySetInstance() override
{
return "[188, 288, 388]";
@@ -180,6 +192,13 @@ namespace JsonSerializationTests
return instance;
}
AZStd::shared_ptr<Container> CreateSingleArrayDefaultInstance() override
{
auto instance = AZStd::make_shared<Container>();
*instance = { SimpleClass{} };
return instance;
}
AZStd::string_view GetJsonForFullySetInstance() override
{
return R"([
@@ -301,7 +320,7 @@ namespace JsonSerializationTests
ResultCode result = m_serializer->Store(*m_jsonDocument, &instance, &instance, azrtti_typeid(&instance), *m_jsonSerializationContext);
EXPECT_EQ(Processing::Completed, result.GetProcessing());
EXPECT_EQ(Outcomes::DefaultsUsed, result.GetOutcome());
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
Expect_DocStrEq("[{}]");
}
@@ -315,7 +334,7 @@ namespace JsonSerializationTests
ResultCode result = m_serializer->Store(*m_jsonDocument, &instance, nullptr, azrtti_typeid(&instance), *m_jsonSerializationContext);
EXPECT_EQ(Processing::Completed, result.GetProcessing());
EXPECT_EQ(Outcomes::DefaultsUsed, result.GetOutcome());
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
Expect_DocStrEq("[{},{}]");
}
@@ -330,7 +349,6 @@ namespace JsonSerializationTests
ResultCode result = m_serializer->Store(*m_jsonDocument, &instance, nullptr, azrtti_typeid(&instance), *m_jsonSerializationContext);
EXPECT_EQ(Processing::Completed, result.GetProcessing());
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
EXPECT_NE(Outcomes::DefaultsUsed, result.GetOutcome());
Expect_DocStrEq(R"([{"$type": "SimpleInheritence"},{"$type": "SimpleInheritence"}])");
}
@@ -63,6 +63,18 @@ namespace JsonSerializationTests
: public BaseJsonSerializerFixture
{
public:
struct BoolPointerWrapper
{
AZ_TYPE_INFO(BoolPointerWrapper, "{2E67C069-BB0F-4F00-A704-E964F5FE5ED2}");
bool* m_value{ nullptr };
~BoolPointerWrapper()
{
azfree(m_value);
}
};
void SetUp() override
{
BaseJsonSerializerFixture::SetUp();
@@ -75,6 +87,12 @@ namespace JsonSerializationTests
BaseJsonSerializerFixture::TearDown();
}
void RegisterAdditional(AZStd::unique_ptr<AZ::SerializeContext>& serializeContext) override
{
serializeContext->Class<BoolPointerWrapper>()
->Field("Value", &BoolPointerWrapper::m_value);
}
void Load(rapidjson::Value& testVal, bool expectedBool, AZ::JsonSerializationResult::Outcomes expectedOutcome)
{
using namespace AZ::JsonSerializationResult;
@@ -32,7 +32,7 @@ namespace JsonSerializationTests
AZStd::shared_ptr<FloatingPointType> CreateDefaultInstance() override
{
return AZStd::make_shared<FloatingPointType>(-2.0f);
return AZStd::make_shared<FloatingPointType>(0.0f);
}
AZStd::shared_ptr<FloatingPointType> CreateFullySetInstance() override
@@ -71,6 +71,20 @@ namespace JsonSerializationTests
: public BaseJsonSerializerFixture
{
public:
struct DoublePointerWrapper
{
AZ_TYPE_INFO(DoublePointerWrapper, "{C2FD9E0B-2641-4D24-A3D9-A29FD1A21A81}");
double* m_double{ nullptr };
float* m_float{ nullptr };
~DoublePointerWrapper()
{
azfree(m_float);
azfree(m_double);
}
};
void SetUp() override
{
BaseJsonSerializerFixture::SetUp();
@@ -85,6 +99,13 @@ namespace JsonSerializationTests
BaseJsonSerializerFixture::TearDown();
}
void RegisterAdditional(AZStd::unique_ptr<AZ::SerializeContext>& serializeContext) override
{
serializeContext->Class<DoublePointerWrapper>()
->Field("Double", &DoublePointerWrapper::m_double)
->Field("Float", &DoublePointerWrapper::m_float);
}
void TestSerializers(rapidjson::Value& testVal, double expectedValue, AZ::JsonSerializationResult::Outcomes expectedOutcome)
{
using namespace AZ::JsonSerializationResult;
@@ -62,6 +62,14 @@ namespace JsonSerializationTests
//! can be used to manually create these documents. If that is also not an option the tests can be
//! disabled by setting this flag to false.
bool m_supportsInjection{ true };
//! Enables the check that tries to determine if variables are initialized and if not whether they have the
//! OperationFlags::ManualDefault set. This applies for instance to integers, which won't be initialized if
//! constructed a new instance is created for pointers.
bool m_enableInitializationTest{ true };
//! Enable the test that creates a new instance of the provided test type through the factory that's found in
//! the Serialize Context. This test is automatically disabled for classes that don't have a factory or
//! have a null factory as well as for classes that have mandatory fields.
bool m_enableNewInstanceTests{ true };
private:
// There's no way to retrieve the number of types from RapidJSON so they're hard-coded here.
@@ -87,6 +95,7 @@ namespace JsonSerializationTests
{
public:
using Type = T;
virtual ~JsonSerializerConformityTestDescriptor() = default;
virtual AZStd::shared_ptr<AZ::BaseJsonSerializer> CreateSerializer() = 0;
@@ -104,13 +113,21 @@ namespace JsonSerializationTests
virtual AZStd::shared_ptr<T> CreatePartialDefaultInstance() { return nullptr; }
//! Create an instance where all values are set to non-default values.
virtual AZStd::shared_ptr<T> CreateFullySetInstance() = 0;
//! Create an instance of the target array type with a single value that has all defaults.
//! If the target type doesn't support arrays or requires more than one entry this can be ignored and
//! tests using this value will be skipped.
virtual AZStd::shared_ptr<T> CreateSingleArrayDefaultInstance() { return nullptr; }
//! Get the json that represents the default instance.
//! If the target type doesn't support partial specialization this can be ignored and
//! tests for partial support will be skipped.
virtual AZStd::string_view GetJsonForPartialDefaultInstance() { return ""; }
virtual AZStd::string_view GetJsonForPartialDefaultInstance() { return ""; }
//! Get the json that represents the instance with all values set.
virtual AZStd::string_view GetJsonForFullySetInstance() = 0;
//! Get the json that represents an array with a single value that has only defaults.
//! If the target type doesn't support arrays or requires more than one entry this can be ignored and
//! tests using this value will be skipped.
virtual AZStd::string_view GetJsonForSingleArrayDefaultInstance() { return "[{}]"; }
//! Get the json where additional values are added to the json file.
//! If this function is not overloaded, but features.m_supportsInjection is enabled then
//! the Json Serializer Conformity Tests will inject extra values in the json for a fully.
@@ -138,12 +155,15 @@ namespace JsonSerializationTests
virtual AZStd::string_view GetJsonFor_Load_DeserializeUnreflectedType() { return this->GetJsonForFullySetInstance(); }
virtual AZStd::string_view GetJsonFor_Load_DeserializeFullySetInstance() { return this->GetJsonForFullySetInstance(); }
virtual AZStd::string_view GetJsonFor_Load_DeserializePartialInstance() { return this->GetJsonForPartialDefaultInstance(); }
virtual AZStd::string_view GetJsonFor_Load_DeserializeArrayWithDefaultValue() { return this->GetJsonForSingleArrayDefaultInstance(); }
virtual AZStd::string_view GetJsonFor_Load_DeserializeFullInstanceOnTopOfPartialDefaulted() { return this->GetJsonForFullySetInstance(); }
virtual AZStd::string_view GetJsonFor_Load_HaltedThroughCallback() { return this->GetJsonForFullySetInstance(); }
virtual AZStd::string_view GetJsonFor_Store_SerializeWithDefaultsKept() { return this->GetJsonForFullySetInstance(); }
virtual AZStd::string_view GetJsonFor_Store_SerializeFullySetInstance() { return this->GetJsonForFullySetInstance(); }
virtual AZStd::string_view GetJsonFor_Store_SerializeWithoutDefault() { return this->GetJsonForFullySetInstance(); }
virtual AZStd::string_view GetJsonFor_Store_SerializeWithoutDefaultAndDefaultsKept() { return this->GetJsonForFullySetInstance(); }
virtual AZStd::string_view GetJsonFor_Store_SerializePartialInstance() { return this->GetJsonForPartialDefaultInstance(); }
virtual AZStd::string_view GetJsonFor_Store_SerializeArrayWithSingleDefaultValue() { return this->GetJsonForSingleArrayDefaultInstance(); }
};
template<typename T>
@@ -154,6 +174,21 @@ namespace JsonSerializationTests
using Description = T;
using Type = typename T::Type;
struct PointerWrapper
{
AZ_TYPE_INFO(PointerWrapper, "{32FA6645-074A-458A-B79C-B173D0BD4B42}");
AZ_CLASS_ALLOCATOR(PointerWrapper, AZ::SystemAllocator, 0);
Type* m_value{ nullptr };
~PointerWrapper()
{
// Using free because not all types can safely use delete. Since this just to clear the memory to satisfy the memory
// leak test, this is fine.
azfree(m_value);
}
};
void SetUp() override
{
using namespace AZ::JsonSerializationResult;
@@ -165,6 +200,7 @@ namespace JsonSerializationTests
descriptor->ConfigureFeatures(this->m_features);
descriptor->Reflect(this->m_serializeContext);
descriptor->Reflect(this->m_jsonRegistrationContext);
this->m_serializeContext->template Class<PointerWrapper>()->Field("Value", &PointerWrapper::m_value);
this->m_deserializationSettings->m_reporting = &Internal::VerifyCallback;
this->m_serializationSettings->m_reporting = &Internal::VerifyCallback;
@@ -185,6 +221,7 @@ namespace JsonSerializationTests
this->m_jsonRegistrationContext->DisableRemoveReflection();
this->m_serializeContext->EnableRemoveReflection();
this->m_serializeContext->template Class<PointerWrapper>()->Field("Value", &PointerWrapper::m_value);
descriptor->Reflect(this->m_serializeContext);
this->m_serializeContext->DisableRemoveReflection();
@@ -487,6 +524,41 @@ namespace JsonSerializationTests
}
}
TYPED_TEST_P(JsonSerializerConformityTests, Load_DeserializeArrayWithDefaultValue_SucceedsAndReportPartialDefaults)
{
using namespace AZ::JsonSerializationResult;
if (this->m_features.SupportsJsonType(rapidjson::kArrayType))
{
this->m_jsonDocument->Parse(this->m_description.GetJsonFor_Load_DeserializeArrayWithDefaultValue().data());
ASSERT_FALSE(this->m_jsonDocument->HasParseError());
auto serializer = this->m_description.CreateSerializer();
auto instance = this->m_description.CreateDefaultInstance();
this->m_jsonDeserializationContext->PushPath(DefaultPath);
ResultCode result =
serializer->Load(instance.get(), azrtti_typeid(*instance), *this->m_jsonDocument, *this->m_jsonDeserializationContext);
if (this->m_features.m_fixedSizeArray)
{
EXPECT_EQ(Outcomes::Unsupported, result.GetOutcome());
EXPECT_EQ(Processing::Altered, result.GetProcessing());
}
else
{
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
EXPECT_EQ(Processing::Completed, result.GetProcessing());
auto compare = this->m_description.CreateSingleArrayDefaultInstance();
ASSERT_NE(nullptr, compare)
<< "Conformity tests for variably sized arrays require an implementation of CreateSingleArrayDefaultInstance";
EXPECT_TRUE(this->m_description.AreEqual(*compare, *instance));
}
}
}
TYPED_TEST_P(JsonSerializerConformityTests, Load_InterruptClearingTarget_ContainerIsNotCleared)
{
using namespace AZ::JsonSerializationResult;
@@ -548,7 +620,6 @@ namespace JsonSerializationTests
this->m_jsonDocument->Parse(json.data());
ASSERT_FALSE(this->m_jsonDocument->HasParseError());
auto serializer = this->m_description.CreateSerializer();
auto instance = this->m_description.CreateDefaultConstructedInstance();
auto compare = this->m_description.CreateFullySetInstance();
@@ -622,6 +693,94 @@ namespace JsonSerializationTests
}
}
TYPED_TEST_P(JsonSerializerConformityTests, Load_DeserializeFullInstanceOnTopOfPartialDefaulted_SucceedsAndObjectMatchesParialInstance)
{
using namespace AZ::JsonSerializationResult;
if (this->m_features.m_supportsPartialInitialization)
{
AZStd::string_view json = this->m_description.GetJsonFor_Load_DeserializeFullInstanceOnTopOfPartialDefaulted();
// If tests for partial initialization are enabled than json for the partial initialization is needed.
ASSERT_FALSE(json.empty());
this->m_jsonDocument->Parse(json.data());
ASSERT_FALSE(this->m_jsonDocument->HasParseError());
auto serializer = this->m_description.CreateSerializer();
auto instance = this->m_description.CreatePartialDefaultInstance();
auto compare = this->m_description.CreateFullySetInstance();
ASSERT_NE(nullptr, compare);
// Clear containers which should effectively turn them into default containers.
this->m_deserializationSettings->m_clearContainers = true;
this->ResetJsonContexts();
this->m_jsonDeserializationContext->PushPath(DefaultPath);
ResultCode result =
serializer->Load(instance.get(), azrtti_typeid(*instance), *this->m_jsonDocument, *this->m_jsonDeserializationContext);
EXPECT_EQ(Outcomes::Success, result.GetOutcome());
EXPECT_EQ(Processing::Completed, result.GetProcessing());
EXPECT_TRUE(this->m_description.AreEqual(*instance, *compare));
}
}
TYPED_TEST_P(JsonSerializerConformityTests, Load_DefaultToPointer_SucceedsAndValueIsInitialized)
{
using namespace AZ::JsonSerializationResult;
if (this->m_features.m_enableNewInstanceTests && this->m_features.m_mandatoryFields.empty())
{
AZ::SerializeContext* serializeContext = this->m_jsonDeserializationContext->GetSerializeContext();
const AZ::SerializeContext::ClassData* classData = serializeContext->FindClassData(azrtti_typeid<typename TypeParam::Type>());
ASSERT_NE(nullptr, classData);
// Skip this test if the target type doesn't have a factor to create a new instance with or if the factor explicit
// prohibits construction.
if (classData->m_factory && classData->m_factory != AZ::Internal::NullFactory::GetInstance())
{
typename JsonSerializerConformityTests<TypeParam>::PointerWrapper instance;
auto compare = this->m_description.CreateDefaultInstance();
this->m_jsonDocument->Parse(R"({ "Value": {}})");
ASSERT_FALSE(this->m_jsonDocument->HasParseError());
AZ::JsonDeserializerSettings settings;
settings.m_serializeContext = serializeContext;
settings.m_registrationContext = this->m_jsonDeserializationContext->GetRegistrationContext();
ResultCode result = AZ::JsonSerialization::Load(instance, *this->m_jsonDocument, settings);
EXPECT_EQ(Outcomes::DefaultsUsed, result.GetOutcome());
EXPECT_EQ(Processing::Completed, result.GetProcessing());
ASSERT_NE(nullptr, instance.m_value);
EXPECT_TRUE(this->m_description.AreEqual(*instance.m_value, *compare));
}
}
}
TYPED_TEST_P(JsonSerializerConformityTests, Load_InitializeNewInstance_SucceedsAndValueIsInitialized)
{
using namespace AZ;
using namespace AZ::JsonSerializationResult;
if (this->m_features.m_enableNewInstanceTests && this->m_features.m_mandatoryFields.empty())
{
auto serializer = this->m_description.CreateSerializer();
if ((serializer->GetOperationsFlags() & BaseJsonSerializer::OperationFlags::InitializeNewInstance) ==
BaseJsonSerializer::OperationFlags::InitializeNewInstance)
{
typename TypeParam::Type instance;
auto compare = this->m_description.CreateDefaultInstance();
this->m_jsonDocument->SetObject();
ResultCode result =
serializer->Load(&instance, azrtti_typeid(instance), *this->m_jsonDocument, *this->m_jsonDeserializationContext);
EXPECT_EQ(Outcomes::DefaultsUsed, result.GetOutcome());
EXPECT_EQ(Processing::Completed, result.GetProcessing());
EXPECT_TRUE(this->m_description.AreEqual(instance, *compare));
}
}
}
TYPED_TEST_P(JsonSerializerConformityTests, Load_HaltedThroughCallback_LoadFailsAndHaltReported)
{
using namespace AZ::JsonSerializationResult;
@@ -909,6 +1068,28 @@ namespace JsonSerializationTests
}
}
TYPED_TEST_P(JsonSerializerConformityTests, Store_SerializeArrayWithSingleDefaultValue_StoredSuccessfullyAndJsonMatches)
{
using namespace AZ::JsonSerializationResult;
if (this->m_features.SupportsJsonType(rapidjson::kArrayType) && !this->m_features.m_fixedSizeArray)
{
this->m_jsonSerializationContext->PushPath(DefaultPath);
auto serializer = this->m_description.CreateSerializer();
auto instance = this->m_description.CreateSingleArrayDefaultInstance();
ASSERT_NE(nullptr, instance)
<< "Conformity tests for variably sized arrays require an implementation of CreateSingleArrayDefaultInstance";
ResultCode result = serializer->Store(
*this->m_jsonDocument, instance.get(), instance.get(), azrtti_typeid(*instance), *this->m_jsonSerializationContext);
EXPECT_EQ(Processing::Completed, result.GetProcessing());
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
this->Expect_DocStrEq(this->m_description.GetJsonFor_Store_SerializeArrayWithSingleDefaultValue());
}
}
TYPED_TEST_P(JsonSerializerConformityTests, Store_HaltedThroughCallback_StoreFailsAndHaltReported)
{
using namespace AZ::JsonSerializationResult;
@@ -1017,7 +1198,29 @@ namespace JsonSerializationTests
}
}
TYPED_TEST_P(JsonSerializerConformityTests, GetOperationsFlags_ManualDefaultSetIfNeeded_ManualDefaultOperationSetIfMandatoryFieldsAreDeclared)
TYPED_TEST_P(JsonSerializerConformityTests, GetOperationFlags_RequiresExplicitInit_ObjectsThatDoNotConstructHaveExplicitInitOption)
{
using namespace AZ;
using namespace AZ::JsonSerializationResult;
if (this->m_features.m_enableInitializationTest)
{
auto instance = this->m_description.CreateDefaultInstance();
typename TypeParam::Type compare = typename TypeParam::Type{};
if (!this->m_description.AreEqual(*instance, compare))
{
auto serializer = this->m_description.CreateSerializer();
BaseJsonSerializer::OperationFlags flags = serializer->GetOperationsFlags();
bool hasManualDefaultSet =
(flags & BaseJsonSerializer::OperationFlags::ManualDefault) == BaseJsonSerializer::OperationFlags::ManualDefault ||
(flags & BaseJsonSerializer::OperationFlags::InitializeNewInstance) ==
BaseJsonSerializer::OperationFlags::InitializeNewInstance;
EXPECT_TRUE(hasManualDefaultSet);
}
}
}
TYPED_TEST_P(JsonSerializerConformityTests, GetOperationFlags_ManualDefaultSetIfNeeded_ManualDefaultOperationSetIfMandatoryFieldsAreDeclared)
{
if (this->m_features.SupportsJsonType(rapidjson::kObjectType))
{
@@ -1048,10 +1251,14 @@ namespace JsonSerializationTests
Load_DeserializeEmptyArray_SucceedsAndObjectMatchesDefaults,
Load_DeserializeEmptyArrayWithClearEnabled_SucceedsAndObjectMatchesDefaults,
Load_DeserializeEmptyArrayWithClearedTarget_SucceedsAndObjectMatchesDefaults,
Load_DeserializeArrayWithDefaultValue_SucceedsAndReportPartialDefaults,
Load_InterruptClearingTarget_ContainerIsNotCleared,
Load_DeserializeFullySetInstance_SucceedsAndObjectMatchesFullySetInstance,
Load_DeserializeFullySetInstanceThroughMainLoad_SucceedsAndObjectMatchesFullySetInstance,
Load_DeserializePartialInstance_SucceedsAndObjectMatchesParialInstance,
Load_DeserializeFullInstanceOnTopOfPartialDefaulted_SucceedsAndObjectMatchesParialInstance,
Load_DefaultToPointer_SucceedsAndValueIsInitialized,
Load_InitializeNewInstance_SucceedsAndValueIsInitialized,
Load_DeserializeWithMissingMandatoryField_LoadFailedAndUnsupportedReported,
Load_InsertAdditionalData_SucceedsAndObjectMatchesFullySetInstance,
Load_HaltedThroughCallback_LoadFailsAndHaltReported,
@@ -1066,13 +1273,15 @@ namespace JsonSerializationTests
Store_SerializeWithoutDefaultAndDefaultsKept_StoredSuccessfullyAndJsonMatches,
Store_SerializePartialInstance_StoredSuccessfullyAndJsonMatches,
Store_SerializeEmptyArray_StoredSuccessfullyAndJsonMatches,
Store_SerializeArrayWithSingleDefaultValue_StoredSuccessfullyAndJsonMatches,
Store_HaltedThroughCallback_StoreFailsAndHaltReported,
StoreLoad_RoundTripWithPartialDefault_IdenticalInstances,
StoreLoad_RoundTripWithFullSet_IdenticalInstances,
StoreLoad_RoundTripWithDefaultsKept_IdenticalInstances,
GetOperationsFlags_ManualDefaultSetIfNeeded_ManualDefaultOperationSetIfMandatoryFieldsAreDeclared);
GetOperationFlags_RequiresExplicitInit_ObjectsThatDoNotConstructHaveExplicitInitOption,
GetOperationFlags_ManualDefaultSetIfNeeded_ManualDefaultOperationSetIfMandatoryFieldsAreDeclared);
} // namespace JsonSerializationTests
namespace AZ
@@ -35,6 +35,11 @@ namespace JsonSerializationTests
return AZStd::make_shared<Map>();
}
AZStd::string_view GetJsonForSingleArrayDefaultInstance() override
{
return R"({ "{}": {} })";
}
void ConfigureFeatures(JsonSerializerConformityTestDescriptorFeatures& features) override
{
features.EnableJsonType(rapidjson::kArrayType);
@@ -61,6 +66,13 @@ namespace JsonSerializationTests
public:
using Map = T<int, double>;
AZStd::shared_ptr<Map> CreateSingleArrayDefaultInstance() override
{
auto instance = AZStd::make_shared<Map>();
instance->emplace(AZStd::make_pair(0, 0.0));
return instance;
}
AZStd::shared_ptr<Map> CreateFullySetInstance() override
{
auto instance = AZStd::make_shared<Map>();
@@ -100,6 +112,13 @@ namespace JsonSerializationTests
public:
using Map = T<AZStd::string, double>;
AZStd::shared_ptr<Map> CreateSingleArrayDefaultInstance() override
{
auto instance = AZStd::make_shared<Map>();
instance->emplace(AZStd::make_pair(AZStd::string(), 0.0));
return instance;
}
AZStd::shared_ptr<Map> CreateFullySetInstance() override
{
auto instance = AZStd::make_shared<Map>();
@@ -163,6 +182,14 @@ namespace JsonSerializationTests
return instance;
}
AZStd::shared_ptr<Map> CreateSingleArrayDefaultInstance() override
{
auto instance = AZStd::shared_ptr<Map>(new Map{}, &Delete);
instance->emplace(AZStd::make_pair(aznew SimpleClass(), aznew SimpleClass()));
return instance;
}
AZStd::string_view GetJsonForPartialDefaultInstance() override
{
if constexpr (IsMultiMap)
@@ -237,13 +264,23 @@ namespace JsonSerializationTests
return false;
}
auto compare = [](typename Map::const_reference lhs, typename Map::const_reference rhs) -> bool
// Naive compare to avoid having to split up the test because comparing for ordered and unordered maps would need to be
// different.
for (auto&& [key, value] : lhs)
{
return
lhs.first->Equals(*rhs.first, true) &&
lhs.second->Equals(*rhs.second, true);
};
return AZStd::equal(lhs.begin(), lhs.end(), rhs.begin(), compare);
for (auto&& [keyCompare, valueCompare] : rhs)
{
if (key->Equals(*keyCompare, true))
{
if (!value->Equals(*valueCompare, true))
{
return false;
}
break;
}
}
}
return true;
}
};
@@ -393,6 +430,29 @@ namespace JsonSerializationTests
{
using namespace AZ::JsonSerializationResult;
m_jsonDocument->Parse(R"(
{
"{}": {}
})");
ASSERT_FALSE(m_jsonDocument->HasParseError());
TestStringMap values;
ResultCode result = m_unorderedMapSerializer.Load(&values, azrtti_typeid(&values), *m_jsonDocument, *m_jsonDeserializationContext);
EXPECT_EQ(Processing::Completed, result.GetProcessing());
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
EXPECT_EQ(1, values.size());
auto defaultKey = values.find(TestString());
EXPECT_NE(values.end(), defaultKey);
EXPECT_STRCASEEQ(TestString().m_value.c_str(), defaultKey->second.m_value.c_str());
}
TEST_F(JsonMapSerializerTests, Load_DefaultForStringKeyAndAdditionalValue_LoadedBackWithDefaults)
{
using namespace AZ::JsonSerializationResult;
m_jsonDocument->Parse(R"(
{
"{}": {},
@@ -563,13 +623,13 @@ namespace JsonSerializationTests
EXPECT_EQ(Outcomes::Catastrophic, result.GetOutcome());
}
TEST_F(JsonMapSerializerTests, Load_DefaultValueInMultiMap_DefaultUsed)
TEST_F(JsonMapSerializerTests, Load_DefaultObjectInMultiMap_DefaultUsed)
{
using namespace AZ::JsonSerializationResult;
m_jsonDocument->Parse(R"(
{
"Hello": {}
"World": {}
})");
ASSERT_FALSE(m_jsonDocument->HasParseError());
@@ -581,17 +641,40 @@ namespace JsonSerializationTests
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
ASSERT_FALSE(values.empty());
EXPECT_STREQ("Hello", values.begin()->first.m_value.c_str());
EXPECT_STREQ("World", values.begin()->first.m_value.c_str());
EXPECT_STREQ(TestString().m_value.c_str(), values.begin()->second.m_value.c_str());
}
TEST_F(JsonMapSerializerTests, Load_DefaultArrayValueInMultiMap_DefaultUsed)
TEST_F(JsonMapSerializerTests, Load_FullDefaultObjectInMultiMap_DefaultUsed)
{
using namespace AZ::JsonSerializationResult;
m_jsonDocument->Parse(R"(
{
"Hello": [{}]
"{}": {}
})");
ASSERT_FALSE(m_jsonDocument->HasParseError());
TestStringMultiMap values;
ResultCode result =
m_unorderedMultiMapSerializer.Load(&values, azrtti_typeid(&values), *m_jsonDocument, *m_jsonDeserializationContext);
EXPECT_EQ(Processing::Completed, result.GetProcessing());
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
ASSERT_FALSE(values.empty());
EXPECT_STREQ("Hello", values.begin()->first.m_value.c_str());
EXPECT_STREQ("Hello", values.begin()->second.m_value.c_str());
EXPECT_STREQ(TestString().m_value.c_str(), values.begin()->second.m_value.c_str());
}
TEST_F(JsonMapSerializerTests, Load_DefaultObjectValueInMultiMap_DefaultUsed)
{
using namespace AZ::JsonSerializationResult;
m_jsonDocument->Parse(R"(
{
"World": [{}]
})");
ASSERT_FALSE(m_jsonDocument->HasParseError());
@@ -603,7 +686,8 @@ namespace JsonSerializationTests
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
ASSERT_FALSE(values.empty());
EXPECT_STREQ("Hello", values.begin()->first.m_value.c_str());
EXPECT_STREQ("World", values.begin()->first.m_value.c_str());
EXPECT_STREQ("Hello", values.begin()->second.m_value.c_str());
EXPECT_STREQ(TestString().m_value.c_str(), values.begin()->second.m_value.c_str());
}
@@ -636,7 +720,7 @@ namespace JsonSerializationTests
azrtti_typeid(&values), *m_jsonSerializationContext);
EXPECT_EQ(Processing::Completed, result.GetProcessing());
EXPECT_EQ(Outcomes::DefaultsUsed, result.GetOutcome());
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
Expect_DocStrEq(R"(
{
"{}": {}
@@ -654,7 +738,25 @@ namespace JsonSerializationTests
azrtti_typeid(&values), *m_jsonSerializationContext);
EXPECT_EQ(Processing::Completed, result.GetProcessing());
EXPECT_EQ(Outcomes::DefaultsUsed, result.GetOutcome());
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
Expect_DocStrEq(R"(
{
"{}": {}
})");
}
TEST_F(JsonMapSerializerTests, Store_SingleAllDefaulValue_InitializedWithDefaults)
{
using namespace AZ::JsonSerializationResult;
SimpleClassMap values;
values.emplace(SimpleClass(), SimpleClass());
ResultCode result =
m_unorderedMapSerializer.Store(*m_jsonDocument, &values, nullptr, azrtti_typeid(&values), *m_jsonSerializationContext);
EXPECT_EQ(Processing::Completed, result.GetProcessing());
EXPECT_EQ(Outcomes::PartialDefaults, result.GetOutcome());
Expect_DocStrEq(R"(
{
"{}": {}
@@ -48,12 +48,12 @@ namespace JsonSerializationTests
AZStd::shared_ptr<Pair> CreateDefaultInstance() override
{
return AZStd::make_shared<Pair>(142, 242.0);
return AZStd::make_shared<Pair>(0, 0.0);
}
AZStd::shared_ptr<Pair> CreatePartialDefaultInstance() override
{
return AZStd::make_shared<Pair>(142, 288.0);
return AZStd::make_shared<Pair>(0, 288.0);
}
AZStd::shared_ptr<Pair> CreateFullySetInstance() override
@@ -102,12 +102,12 @@ namespace JsonSerializationTests
AZStd::shared_ptr<Tuple> CreateDefaultInstance() override
{
return AZStd::make_shared<Tuple>(142, 242.0, 342.0f);
return AZStd::make_shared<Tuple>(0, 0.0, 0.0f);
}
AZStd::shared_ptr<Tuple> CreatePartialDefaultInstance() override
{
return AZStd::make_shared<Tuple>(142, 288.0, 342.0f);
return AZStd::make_shared<Tuple>(0, 288.0, 0.0f);
}
AZStd::shared_ptr<Tuple> CreateFullySetInstance() override
@@ -345,6 +345,7 @@ namespace JsonSerializationTests
{
TupleSerializerTestsInternal::ConfigureFeatures(features);
features.m_supportsPartialInitialization = true;
features.m_enableNewInstanceTests = false;
}
void Reflect(AZStd::unique_ptr<AZ::SerializeContext>& context) override
@@ -447,14 +448,14 @@ namespace JsonSerializationTests
{
return AZStd::make_shared<Tuple>(
AZStd::vector<int>(),
AZStd::make_pair(442, ""));
AZStd::make_pair(0, ""));
}
AZStd::shared_ptr<Tuple> CreatePartialDefaultInstance() override
{
return AZStd::make_shared<Tuple>(
AZStd::vector<int>(),
AZStd::make_pair(442, "hello"));
AZStd::make_pair(0, "hello"));
}
AZStd::shared_ptr<Tuple> CreateFullySetInstance() override
@@ -36,6 +36,11 @@ namespace JsonSerializationTests
return AZStd::make_shared<Set>();
}
AZStd::shared_ptr<Set> CreateSingleArrayDefaultInstance() override
{
return AZStd::make_shared<Set>(Set{ 0 });
}
AZStd::shared_ptr<Set> CreateFullySetInstance() override
{
return AZStd::make_shared<Set>(Set{42, -88, 342});
@@ -80,6 +85,11 @@ namespace JsonSerializationTests
return AZStd::make_shared<MultiSet>();
}
AZStd::shared_ptr<MultiSet> CreateSingleArrayDefaultInstance() override
{
return AZStd::make_shared<MultiSet>(MultiSet{ 0 });
}
AZStd::shared_ptr<MultiSet> CreateFullySetInstance() override
{
return AZStd::make_shared<MultiSet>(MultiSet{ 42, -88, 42, 342 });
@@ -48,7 +48,8 @@ namespace AzPhysics
using OnPresimulateEvent = AZ::Event<float>;
//! Event triggers at the end of the SystemInterface::Simulate call.
using OnPostsimulateEvent = AZ::Event<>;
//! Parameter is the total time that the physics system will run for during the Simulate call.
using OnPostsimulateEvent = AZ::Event<float>;
//! Event trigger when a Scene is added to the simulation.
//! When triggered will send the handle to the new Scene.
@@ -402,7 +402,7 @@ namespace Physics
->Attribute(AZ_CRC_CE("EditButton"), "")
->Attribute(AZ_CRC_CE("EditDescription"), "Open in Asset Editor")
->Attribute(AZ::Edit::Attributes::DefaultAsset, &MaterialSelection::GetMaterialLibraryId)
->DataElement(AZ::Edit::UIHandlers::Default, &MaterialSelection::m_materialIdsAssignedToSlots, "", "")
->DataElement(AZ::Edit::UIHandlers::Default, &MaterialSelection::m_materialIdsAssignedToSlots, "Slots", "")
->ElementAttribute(Attributes::MaterialLibraryAssetId, &MaterialSelection::GetMaterialLibraryId)
->Attribute(AZ::Edit::Attributes::IndexedChildNameLabelOverride, &MaterialSelection::GetMaterialSlotLabel)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
@@ -43,7 +43,7 @@ namespace AzPhysics
const AZ::BehaviorAzEventDescription postsimulateEventDescription =
{
"Postsimulate event",
{} // Parameters
{"Tick time"} // Parameters
};
behaviorContext->Class<SystemInterface>("System Interface")
@@ -44,6 +44,11 @@ namespace AzNetworking
//! @class IConnection
//! @brief interface class for network connections.
//!
//! IConnection provides a pure-virtual interface for all network connection types. IConnections provide access to
//! a ConnectionMetrics object which provides a variety of metrics on the connection itself such as data rate, RTT and
//! packet statistics.
class IConnection
{
public:
@@ -22,6 +22,12 @@ namespace AzNetworking
{
//! @class IConnectionListener
//! @brief interface class for application layer dealing with connection level events.
//!
//! IConnectionListener defines an abstract interface that the user of AzNetworking is expected to implement to react and
//! handle all IConnection related events, including the handling of any received IPacket derived packets. The AzNetworking
//! user should derive a handler class from IConnectionListener, and provide an instance of that handler to any
//! INetworkInterface the user instantiates. The lifetime of the IConnectionListener must outlive the lifetime of the
//! INetworkInterface.
class IConnectionListener
{
public:
@@ -18,6 +18,11 @@ namespace AzNetworking
{
//! @class IConnectionSet
//! @brief interface class for managing a set of connections.
//!
//! IConnectionSet defines a simple interface for working with an abstract set of IConnections bound to an
//! INetworkInterface. Generally users of AzNetworking will not have reason to interact directly with the IConnectionSet,
//! as its interface is completely wrapped by INetworkInterface.
class IConnectionSet
{
public:
@@ -23,10 +23,10 @@ namespace AzNetworking
//! Collection of compression related error codes
enum class CompressorError
{
Ok, ///< No error, operation finished successfully
InsufficientBuffer, ///< Buffer size is insufficient for the operation to complete, increase the size and try again
CorruptData, ///< Malformed or hacked packet, potentially security issue
Uninitialized ///< Compressor or supplied buffers are uninitialized
Ok, //!< No error, operation finished successfully
InsufficientBuffer, //!< Buffer size is insufficient for the operation to complete, increase the size and try again
CorruptData, //!< Malformed or hacked packet, potentially security issue
Uninitialized //!< Compressor or supplied buffers are uninitialized
};
//! Unique identifier of a given compressor
@@ -34,6 +34,12 @@ namespace AzNetworking
//! @class ICompressor
//! @brief Packet data compressor interface.
//!
//! ICompressor is an abstract compression interface meant for user provided GEMs to implement (such as the [Multiplayer
//! Compression Gem](http://docs.o3de.org/docs/user-guide/gems/reference/multiplayer-compression)).
//! Compression is supported for both TCP and UDP connections. Instantiation of a compressor is controlled by the
//! `net_UdpCompressor` or `net_TcpCompressor` cvar for their respective protocols.
class ICompressor
{
public:
@@ -87,8 +93,16 @@ namespace AzNetworking
) = 0;
};
//! Abstract factory to instantiate compressors.
//! Used by the network interface to create a compressor
//! @class ICompressorFactory
//! @brief Abstract factory to instantiate compressors.
//!
//! ICompressorFactory is an abstract compression interface meant for user provided GEMs to implement. ICompressorFactory
//! implementations can be registered to classes implementing INetworking. Registered factories can then be used to create
//! ICompressor implementations on demand. The [Multiplayer Compression
//! Gem](http://docs.o3de.org/docs/user-guide/gems/reference/multiplayer-compression) is an example of an ICompressorFactory
//! for an LZ4 Compressor. In it, MultiplayerCompressionSystemComponent registers its ICompressorFactory with
//! NetworkingSystemComponent, which is an implementation of INetworking. Registered factories are keyed by their AZ Name
//! which is accessed through the factory's GetFactoryName method.
class ICompressorFactory
{
public:
@@ -22,7 +22,16 @@
namespace AzNetworking
{
//! @class INetworkInterface
//! @brief pure virtual network interface class to abstract client/server and tcp/udp concerns from application code.
//! @brief Network interface class to abstract client/server and protocol concerns from application code.
//!
//! INetworkInterface provides an abstract API capable of receiving and opening IConnection objects, sending IPacket objects with optional
//! reliability, and determining the delivery status of packets that have been sent unreliably (delivery of reliable packets
//! is guaranteed as long as the associated connection remains open). INetworkInterface must be provided an
//! IConnectionListener instance that outlives the INetworkInterface itself. The INetworkInterface also creates and manages
//! the IConnectionSet, which tracks all open connections bound to the interface. INetworkInterface also provides GetMetrics
//! functions which can be used to fetch a struct detailing a variety of metrics relating to send and receive rates for both
//! packets and bytes in addition to the effect of features on those rates (such as packet size reduction due to compression.)
class INetworkInterface
{
public:
@@ -23,6 +23,17 @@ namespace AzNetworking
//! @class INetworking
//! @brief The interface for creating and working with network interfaces.
//!
//! INetworking is an Az::Interface<T> that provides applications access to higher level networking abstractions.
//! AzNetworking::INetworking can be used to instantiate new INetworkInterfaces that can be configured to operate over
//! either TCP or UDP, enable or disable encryption, and be assigned a trust level.
//!
//! INetworking is also responsible for registering ICompressorFactory implementations. This allows a developer to have
//! access to multiple ICompressorFactory implementations by name. The [MultiplayerCompressor
//! Gem](http://docs.o3de.org/docs/user-guide/gems/reference/multiplayer-compression) is an example of this using the
//! [LZ4](https://wikipedia.org/wiki/LZ4_%28compression_algorithm%29) algorithm.
//!
class INetworking
{
public:
@@ -24,6 +24,15 @@ namespace AzNetworking
//! @class IPacket
//! @brief Base class for all packets.
//!
//! IPacket defines an abstract interface that all packets transmitted using AzNetworking must conform to. While there are
//! a number of core packets used internally by AzNetworking, it is fully possible for end-users to define their own custom
//! packets using this interface. PacketType should be distinct, and should be greater than
//! AzNetworking::CorePackets::MAX. The Serialize method allows the IPacket to be used by an
//! ISerializer to move data between hosts safely and efficiently.
//!
//! For more information on the packet format and best practices for extending the packet system, read
//! [Networking Packets](http://docs.o3de.org/docs/user-guide/networking/packets) on the O3DE documentation site.
class IPacket
{
public:
@@ -28,6 +28,19 @@ namespace AzNetworking
//! @class IPacketHeader
//! @brief A packet header that lets us deduce packet type for any incoming packet.
//!
//! IPacketHeader defines an abstract interface for a descriptor of all AzNetworking::IPacket sent through AzNetworking. The
//! PacketHeader is used to identify and describe the contents of a Packet so that transport logic can identify what
//! additional processing steps need to be taken (if any) and what type of Packet is being inspected.
//!
//! The PacketFlags portion of the header represents the first byte of the header. While it can be encrypted it is
//! otherwise not exposed to additional processing (such as an AzNetworking::ICompressor). PacketFlags are a bitfield use to provide up
//! front information about the state of the packet. Currently there is only one flag to indicate if the Packet is
//! compressed or not.
//!
//! The remainder of the header contains the PacketType and the PacketId. While the PacketFlags byte is exempt from most
//! additional forms of processing, the remainder of the header is not.
class IPacketHeader
{
public:
@@ -27,6 +27,18 @@ namespace AzNetworking
//! @class ISerializer
//! @brief Interface class for all serializers to derive from.
//!
//! ISerializer defines an abstract interface for visiting an object hierarchy and performing operations upon that hierarchy,
//! typically reading from or writing data to the object hierarchy for reasons of persistence or network transmission.
//!
//! While the most common types of serializers are provided by the AzNetworking framework, users can implement custom
//! serializers and perform complex operations on any serializable structures. A few types native to AzNetworking, many of which
//! relate to packets, demonstrate this.
//!
//! Provided serializers include NetworkInputSerializer for writing an object model into a bytestream, NetworkOutputSerializer
//! for writing to an object model, TrackChangesSerializer which is used to efficiently serialize objects without incurring significant
//! copy or comparison overhead, and HashSerializer which can be used to generate a hash of all visited data which is important for
//! automated desync detection.
class ISerializer
{
public:
@@ -25,6 +25,44 @@ namespace AzNetworking
//! @class TcpNetworkInterface
//! @brief This class implements a TCP network interface.
//!
//! TcpNetworkInterface is an implementation of AzNetworking::INetworkInterface.
//! Unlike UDP, TCP implements a variety of transport features such as congestion
//! avoidance, flow control, and reliability. These features are valuable, but TCP
//! offers minimal configuration of them. This is why UdpNetworkInterface offers
//! similar features, but with greater flexibility in configuration. If your project doesn't
//! require the low latency of UDP, consider using TCP.
//!
//! ## Packet structure
//!
//! * Flags - A bitfield a receiving endpoint can quickly inspect to learn about configuration of a packet
//! * Header - Details the type of packet and other information related to reliability
//! * Payload - The actual serialized content of the packet
//!
//! For more information, read [Networking Packets](http://docs.o3de.org/docs/user-guide/networking/packets) in the O3DE documentation.
//!
//! ## Reliability
//!
//! TCP packets can only be sent reliably. This is a feature of TCP itself.
//!
//! ## Fragmentation
//!
//! TCP implements fragmentation under the hood. Consumers of TCP packets will never
//! need to worry about reconstructing the contents over multiple transmissions.
//!
//! ## Compression
//!
//! Compression here refers to content insensitive compression using libraries like
//! LZ4. If enabled, the target payload is run through the compressor and replaces
//! the original payload if it's in fact smaller. To tell if compression is enabled
//! on a given packet, we operate on a bit in the packet's Flags. The Sender writes
//! this bit while the Receiver checks it to see if a packet needs to be
//! decompressed.
//!
//! ## Encryption
//!
//! AzNetworking uses the [OpenSSL](https://www.openssl.org/) library to implement TLS encryption. If enabled,
//! the O3DE network layer handles the OpenSSL handshake under the hood using provided certificates.
class TcpNetworkInterface final
: public INetworkInterface
{
@@ -27,12 +27,58 @@ namespace AzNetworking
class IConnectionListener;
class ICompressor;
// 20 byte IPv4 header + 8 byte UDP header
static const uint32_t UdpPacketHeaderSize = 20 + 8;
static const uint32_t DtlsPacketHeaderSize = 13; // DTLS1_RT_HEADER_LENGTH
static const uint32_t UdpPacketHeaderSize = 20 + 8; //!< 20 byte IPv4 header + 8 byte UDP header
static const uint32_t DtlsPacketHeaderSize = 13; //!< DTLS1_RT_HEADER_LENGTH
//! @class UdpNetworkInterface
//! @brief This class implements a UDP network interface.
//!
//! UdpNetworkInterface is an implementation of AzNetworking::INetworkInterface. Since UDP is a very bare bones protocol,
//! the Open 3D Engine implementation has to provide significantly more than its TCP counterpart (since TCP implements a
//! significant number of reliability features.)
//!
//! When sent through UDP, a packet can have additional actions performed on it depending on which features are enabled and
//! configured. Each feature listed in this description is in the order a packet will see them on Send.
//!
//! ### Packet structure
//!
//! The general structure of a UDP packet is:
//!
//! * Flags - A bitfield a receiving endpoint can quickly inspect to learn about configuration of a packet
//! * Header - Details the type of packet and other information related to reliability
//! * Payload - The actual serialized content of the packet
//!
//! For more information, read [Networking Packets](http://docs.o3de.org/docs/user-guide/networking/packets) in the O3DE documentation.
//!
//! ### Reliability
//!
//! UDP packets can be sent reliably or unreliably. Reliably sent packets are registered for tracking first. This causes the
//! reliable packet to be resent if a timeout on the packet is reached. Once the packet is acknowledged, the packet is
//! unregistered.
//!
//! ### Fragmentation
//!
//! If the raw packet size exceeds the configured maximum transmission unit (MTU) then the packet is broken into
//! multiple reliable fragments to avoid fragmentation at the routing level. Fragments are always reliable so the original
//! packet can be reconstructed. Operations that alter the payload generally follow this step so that they can be
//! separately applied to the Fragments in addition to not being applied to both the original and Fragments.
//!
//! ### Compression
//!
//! Compression here refers to content insensitive compression using libraries like LZ4. If enabled, the target payload is
//! run through the compressor and replaces the original payload if it's in fact smaller. To tell if compression is enabled
//! on a given packet, we operate on a bit in the packet's Flags. The Sender writes this bit while the Receiver checks it to
//! see if a packet needs to be decompressed.
//!
//! O3DE could potentially move from over MTU to under with compression, and the UDP interface doesn't check for this. Detecting a change
//! that would reduce the number of fragmented packets would require pre-emptively compressing payloads to tell if that change happened,
//! which could potentially lead to a lot of unnecessary calls to the compressor.
//!
//! ### Encryption
//!
//! AzNetworking uses the [OpenSSL](https://www.openssl.org/) library to implement Datagram Layer Transport Security (DTLS) encryption
//! on UDP traffic. Encryption operates as described in [O3DE Networking Encryption](http://docs.o3de.org/docs/user-guide/networking/encryption)
//! on the documentation website. Once both endpoints have completed their handshake, all traffic is expected to be fully encrypted.
class UdpNetworkInterface final
: public INetworkInterface
{
@@ -1,47 +1,36 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/EBus/EBus.h>
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
namespace AzToolsFramework
{
using EntityIdList = AZStd::vector<AZ::EntityId>;
/*!
* Bus for notifications about entity transform changes from the editor viewport
*/
class EditorTransformChangeNotifications
: public AZ::EBusTraits
//! Notifications about entity transform changes from the editor.
class EditorTransformChangeNotifications : public AZ::EBusTraits
{
public:
virtual ~EditorTransformChangeNotifications() = default;
//! A notification that these entities had their transforms changed due to a user interaction in the editor.
//! @param entityIds Entities that had their transform changed.
virtual void OnEntityTransformChanged([[maybe_unused]] const AzToolsFramework::EntityIdList& entityIds)
{
}
/*!
* Notification that the specified entities are about to have their transforms changed due to user interaction in the editor viewport
*
* \param entityIds Entities about to be changed
*/
virtual void OnEntityTransformChanging(const AzToolsFramework::EntityIdList& /*entityIds*/) {};
/*!
* Notification that the specified entities had their transforms changed due to user interaction in the editor viewport
*
* \param entityIds Entities changed
*/
virtual void OnEntityTransformChanged(const AzToolsFramework::EntityIdList& /*entityIds*/) {};
protected:
~EditorTransformChangeNotifications() = default;
};
using EditorTransformChangeNotificationBus = AZ::EBus<EditorTransformChangeNotifications>;
} // namespace AzToolsFramework
@@ -326,6 +326,16 @@ namespace AzToolsFramework
return *(m_nestedInstances[newInstanceAlias] = std::move(instance));
}
void Instance::DetachNestedInstances(const AZStd::function<void(AZStd::unique_ptr<Instance>)>& callback)
{
for (auto&& [instanceAlias, instance] : m_nestedInstances)
{
instance->m_parent = nullptr;
callback(AZStd::move(instance));
}
m_nestedInstances.clear();
}
AZStd::unique_ptr<Instance> Instance::DetachNestedInstance(const InstanceAlias& instanceAlias)
{
AZStd::unique_ptr<Instance> removedNestedInstance;
@@ -103,6 +103,7 @@ namespace AzToolsFramework
Instance& AddInstance(AZStd::unique_ptr<Instance> instance);
Instance& AddInstance(AZStd::unique_ptr<Instance> instance, InstanceAlias instanceAlias);
AZStd::unique_ptr<Instance> DetachNestedInstance(const InstanceAlias& instanceAlias);
void DetachNestedInstances(const AZStd::function<void(AZStd::unique_ptr<Instance>)>& callback);
/**
* Gets the aliases for the entities in the Instance DOM.
@@ -151,6 +151,10 @@ namespace AzToolsFramework
return InvalidTemplateId;
}
// Add or replace the Source parameter in the dom
PrefabDomPath sourcePath = PrefabDomPath((AZStd::string("/") + PrefabDomUtils::SourceName).c_str());
sourcePath.Set(readPrefabFileResult.GetValue(), relativePath.Native().c_str());
// Create new Template with the Prefab DOM.
TemplateId newTemplateId = m_prefabSystemComponentInterface->AddTemplate(relativePath, readPrefabFileResult.TakeValue());
if (newTemplateId == InvalidTemplateId)
@@ -1211,25 +1211,23 @@ namespace AzToolsFramework
const auto instanceTemplateId = instancePtr->GetTemplateId();
auto parentContainerEntityId = parentInstance.GetContainerEntityId();
instancePtr->GetNestedInstances(
[&](AZStd::unique_ptr<Instance>& nestedInstancePtr)
instancePtr->DetachNestedInstances(
[&](AZStd::unique_ptr<Instance> detachedNestedInstance)
{
//get previous link patch
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstancePtr->GetLinkId());
PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches();
AZ_Assert(
linkPatches.has_value(), "Unable to get patches on link with id '%llu' during prefab creation.",
nestedInstancePtr->GetLinkId());
PrefabDom& nestedInstanceTemplateDom =
m_prefabSystemComponentInterface->FindTemplateDom(detachedNestedInstance->GetTemplateId());
PrefabDom linkPatchesCopy;
linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator());
RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch());
UpdateLinkPatchesWithNewEntityAliases(linkPatchesCopy, oldEntityAliases, parentInstance);
Instance& nestedInstanceUnderNewParent = parentInstance.AddInstance(AZStd::move(detachedNestedInstance));
CreateLink(*nestedInstancePtr, parentTemplateId, undoBatch.GetUndoBatch(),
AZStd::move(linkPatchesCopy), true);
PrefabDom nestedInstanceDomUnderNewParent;
m_instanceToTemplateInterface->GenerateDomForInstance(
nestedInstanceDomUnderNewParent, nestedInstanceUnderNewParent);
PrefabDom reparentPatch;
m_instanceToTemplateInterface->GeneratePatch(
reparentPatch, nestedInstanceTemplateDom, nestedInstanceDomUnderNewParent);
CreateLink(nestedInstanceUnderNewParent, parentTemplateId, undoBatch.GetUndoBatch(), AZStd::move(reparentPatch), true);
});
}
@@ -207,6 +207,10 @@ namespace AzToolsFramework
instanceValue->CopyFrom(linkDom, m_prefabDom.GetAllocator());
}
// Remove Source parameter from the dom. It will be added on file load, and should not be stored to disk.
PrefabDomPath sourcePath = PrefabDomPath((AZStd::string("/") + PrefabDomUtils::SourceName).c_str());
sourcePath.Erase(output);
return true;
}
@@ -27,6 +27,7 @@
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Visibility/EntityBoundsUnionBus.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
#include <AzToolsFramework/Entity/EditorEntityTransformBus.h>
#include <AzToolsFramework/API/EntityPropertyEditorRequestsBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
@@ -944,7 +945,7 @@ namespace AzToolsFramework
return AZ::Success();
}
AZ::u32 TransformComponent::ParentChanged()
AZ::u32 TransformComponent::ParentChangedInspector()
{
AZ::u32 refreshLevel = AZ::Edit::PropertyRefreshLevels::None;
@@ -974,12 +975,23 @@ namespace AzToolsFramework
return refreshLevel;
}
AZ::u32 TransformComponent::TransformChanged()
AZ::u32 TransformComponent::TransformChangedInspector()
{
if (TransformChanged())
{
AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast(
&AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged,
EntityIdList{ GetEntityId() });
}
return AZ::Edit::PropertyRefreshLevels::None;
}
bool TransformComponent::TransformChanged()
{
if (!m_suppressTransformChangedEvent)
{
auto parent = GetParentTransformComponent();
if (parent)
if (auto parent = GetParentTransformComponent())
{
OnTransformChanged(parent->GetLocalTM(), parent->GetWorldTM());
}
@@ -987,13 +999,15 @@ namespace AzToolsFramework
{
OnTransformChanged(AZ::Transform::Identity(), AZ::Transform::Identity());
}
return true;
}
return AZ::Edit::PropertyRefreshLevels::None;
return false;
}
// This is called when our transform changes static state.
AZ::u32 TransformComponent::StaticChanged()
AZ::u32 TransformComponent::StaticChangedInspector()
{
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::Bus::Events::InvalidatePropertyDisplay,
@@ -1175,10 +1189,10 @@ namespace AzToolsFramework
Attribute(AZ::Edit::Attributes::AutoExpand, true)->
DataElement(AZ::Edit::UIHandlers::Default, &TransformComponent::m_parentEntityId, "Parent entity", "")->
Attribute(AZ::Edit::Attributes::ChangeValidate, &TransformComponent::ValidatePotentialParent)->
Attribute(AZ::Edit::Attributes::ChangeNotify, &TransformComponent::ParentChanged)->
Attribute(AZ::Edit::Attributes::ChangeNotify, &TransformComponent::ParentChangedInspector)->
Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::DontGatherReference | AZ::Edit::SliceFlags::NotPushableOnSliceRoot)->
DataElement(AZ::Edit::UIHandlers::Default, &TransformComponent::m_editorTransform, "Values", "")->
Attribute(AZ::Edit::Attributes::ChangeNotify, &TransformComponent::TransformChanged)->
Attribute(AZ::Edit::Attributes::ChangeNotify, &TransformComponent::TransformChangedInspector)->
Attribute(AZ::Edit::Attributes::AutoExpand, true)->
DataElement(AZ::Edit::UIHandlers::Button, &TransformComponent::m_addNonUniformScaleButton, "", "")->
Attribute(AZ::Edit::Attributes::ButtonText, "Add non-uniform scale")->
@@ -1189,7 +1203,7 @@ namespace AzToolsFramework
EnumAttribute(AZ::TransformConfig::ParentActivationTransformMode::MaintainOriginalRelativeTransform, "Original relative transform")->
EnumAttribute(AZ::TransformConfig::ParentActivationTransformMode::MaintainCurrentWorldTransform, "Current world transform")->
DataElement(AZ::Edit::UIHandlers::Default, &TransformComponent::m_isStatic ,"Static", "Static entities are highly optimized and cannot be moved during runtime.")->
Attribute(AZ::Edit::Attributes::ChangeNotify, &TransformComponent::StaticChanged)->
Attribute(AZ::Edit::Attributes::ChangeNotify, &TransformComponent::StaticChangedInspector)->
DataElement(AZ::Edit::UIHandlers::Default, &TransformComponent::m_cachedWorldTransformParent, "Cached Parent Entity", "")->
Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::DontGatherReference | AZ::Edit::SliceFlags::NotPushable)->
Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide)->
@@ -182,9 +182,12 @@ namespace AzToolsFramework
static void Reflect(AZ::ReflectContext* context);
AZ::Outcome<void, AZStd::string> ValidatePotentialParent(void* newValue, const AZ::Uuid& valueType);
AZ::u32 ParentChanged();
AZ::u32 TransformChanged();
AZ::u32 StaticChanged();
AZ::u32 TransformChangedInspector();
AZ::u32 ParentChangedInspector();
AZ::u32 StaticChangedInspector();
bool TransformChanged();
AZ::Transform GetLocalTranslationTM() const;
AZ::Transform GetLocalRotationTM() const;
@@ -165,11 +165,13 @@ namespace UnitTest
void EditorEntityComponentChangeDetector::OnEntityTransformChanged(
const AzToolsFramework::EntityIdList& entityIds)
{
m_entityIds = entityIds;
for (const AZ::EntityId& entityId : entityIds)
{
if (const auto* entity = GetEntityById(entityId))
{
if (AZ::Component * transformComponent = entity->FindComponent<Components::TransformComponent>())
if (AZ::Component* transformComponent = entity->FindComponent<Components::TransformComponent>())
{
OnEntityComponentPropertyChanged(transformComponent->GetId());
}
@@ -239,6 +239,7 @@ namespace UnitTest
bool PropertyDisplayInvalidated() const { return m_propertyDisplayInvalidated; }
AZStd::vector<AZ::ComponentId> m_componentIds;
AzToolsFramework::EntityIdList m_entityIds;
private:
// PropertyEditorEntityChangeNotificationBus ...
@@ -999,7 +999,6 @@ namespace AzToolsFramework
static void RefreshUiAfterChange(const EntityIdList& entitiyIds)
{
EditorTransformChangeNotificationBus::Broadcast(&EditorTransformChangeNotifications::OnEntityTransformChanged, entitiyIds);
ToolsApplicationNotificationBus::Broadcast(&ToolsApplicationNotificationBus::Events::InvalidatePropertyDisplay, Refresh_Values);
}
@@ -1065,7 +1064,7 @@ namespace AzToolsFramework
auto entityBoxSelectData = AZStd::make_shared<EntityBoxSelectData>();
m_boxSelect.InstallLeftMouseDown(
[this, entityBoxSelectData](const ViewportInteraction::MouseInteractionEvent& /*mouseInteraction*/)
[this, entityBoxSelectData]([[maybe_unused]] const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
// begin selection undo/redo command
entityBoxSelectData->m_boxSelectSelectionCommand =
@@ -1263,8 +1262,12 @@ namespace AzToolsFramework
});
translationManipulators->InstallLinearManipulatorMouseUpCallback(
[this]([[maybe_unused]] const LinearManipulator::Action& action) mutable
[this, manipulatorEntityIds]([[maybe_unused]] const LinearManipulator::Action& action) mutable
{
AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast(
&AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged,
manipulatorEntityIds->m_entityIds);
EndRecordManipulatorCommand();
});
@@ -1293,8 +1296,12 @@ namespace AzToolsFramework
});
translationManipulators->InstallPlanarManipulatorMouseUpCallback(
[this, manipulatorEntityIds](const PlanarManipulator::Action& /*action*/)
[this, manipulatorEntityIds]([[maybe_unused]] const PlanarManipulator::Action& action)
{
AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast(
&AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged,
manipulatorEntityIds->m_entityIds);
EndRecordManipulatorCommand();
});
@@ -1322,8 +1329,12 @@ namespace AzToolsFramework
});
translationManipulators->InstallSurfaceManipulatorMouseUpCallback(
[this, manipulatorEntityIds](const SurfaceManipulator::Action& /*action*/)
[this, manipulatorEntityIds]([[maybe_unused]] const SurfaceManipulator::Action& action)
{
AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast(
&AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged,
manipulatorEntityIds->m_entityIds);
EndRecordManipulatorCommand();
});
@@ -1360,7 +1371,7 @@ namespace AzToolsFramework
AZStd::shared_ptr<SharedRotationState> sharedRotationState = AZStd::make_shared<SharedRotationState>();
rotationManipulators->InstallLeftMouseDownCallback(
[this, sharedRotationState](const AngularManipulator::Action& /*action*/) mutable -> void
[this, sharedRotationState]([[maybe_unused]] const AngularManipulator::Action& action) mutable -> void
{
sharedRotationState->m_savedOrientation = AZ::Quaternion::CreateIdentity();
sharedRotationState->m_referenceFrameAtMouseDown = m_referenceFrame;
@@ -1486,8 +1497,12 @@ namespace AzToolsFramework
});
rotationManipulators->InstallLeftMouseUpCallback(
[this](const AngularManipulator::Action& /*action*/)
[this, sharedRotationState]([[maybe_unused]] const AngularManipulator::Action& action)
{
AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast(
&AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged,
sharedRotationState->m_entityIds);
EndRecordManipulatorCommand();
});
@@ -1533,6 +1548,10 @@ namespace AzToolsFramework
auto uniformLeftMouseUpCallback = [this, manipulatorEntityIds]([[maybe_unused]] const LinearManipulator::Action& action)
{
AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast(
&AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged,
manipulatorEntityIds->m_entityIds);
m_entityIdManipulators.m_manipulators->SetLocalTransform(RecalculateAverageManipulatorTransform(
m_entityIdManipulators.m_lookups, m_pivotOverrideFrame, m_pivotMode, m_referenceFrame));
};
@@ -2370,7 +2389,7 @@ namespace AzToolsFramework
AddAction(
m_actions, { QKeySequence(Qt::Key_U) },
/*ID_VIEWPORTUI_VISIBLE=*/50040, "Toggle ViewportUI", "Hide/Unhide Viewport UI",
/*ID_VIEWPORTUI_VISIBLE=*/50040, "Toggle Viewport UI", "Hide/Show Viewport UI",
[this]()
{
SetViewportUiClusterVisible(m_transformModeClusterId, !m_viewportUiVisible);
@@ -3139,7 +3158,7 @@ namespace AzToolsFramework
}
void EditorTransformComponentSelection::AfterEntitySelectionChanged(
const EntityIdList& /*newlySelectedEntities*/, const EntityIdList& /*newlyDeselectedEntities*/)
[[maybe_unused]] const EntityIdList& newlySelectedEntities, [[maybe_unused]] const EntityIdList& newlyDeselectedEntities)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
@@ -3534,17 +3553,17 @@ namespace AzToolsFramework
RegenerateManipulators();
}
void EditorTransformComponentSelection::OnEntityVisibilityChanged(const bool /*visibility*/)
void EditorTransformComponentSelection::OnEntityVisibilityChanged([[maybe_unused]] const bool visibility)
{
m_selectedEntityIdsAndManipulatorsDirty = true;
}
void EditorTransformComponentSelection::OnEntityLockChanged(const bool /*locked*/)
void EditorTransformComponentSelection::OnEntityLockChanged([[maybe_unused]] const bool locked)
{
m_selectedEntityIdsAndManipulatorsDirty = true;
}
void EditorTransformComponentSelection::EnteredComponentMode(const AZStd::vector<AZ::Uuid>& /*componentModeTypes*/)
void EditorTransformComponentSelection::EnteredComponentMode([[maybe_unused]] const AZStd::vector<AZ::Uuid>& componentModeTypes)
{
SetViewportUiClusterVisible(m_transformModeClusterId, false);
@@ -3553,7 +3572,7 @@ namespace AzToolsFramework
ToolsApplicationNotificationBus::Handler::BusDisconnect();
}
void EditorTransformComponentSelection::LeftComponentMode(const AZStd::vector<AZ::Uuid>& /*componentModeTypes*/)
void EditorTransformComponentSelection::LeftComponentMode([[maybe_unused]] const AZStd::vector<AZ::Uuid>& componentModeTypes)
{
SetViewportUiClusterVisible(m_transformModeClusterId, true);
@@ -85,6 +85,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
AZ::AzManipulatorTestFramework.Static
AZ::AzTest
AZ::AzQtComponents
RUNTIME_DEPENDENCIES
3rdParty::Qt::Test
)
ly_add_googletest(
NAME AZ::AzToolsFramework.Tests
@@ -451,6 +451,43 @@ namespace UnitTest
EXPECT_TRUE(finalEntityTransform.IsClose(finalTransformWorld, 0.01f));
}
TEST_F(EditorTransformComponentSelectionManipulatorTestFixture, TranslatingEntityWithLinearManipulatorNotifiesOnEntityTransformChanged)
{
EditorEntityComponentChangeDetector editorEntityChangeDetector(m_entity1);
// the initial starting position of the entity (in front and to the left of the camera)
const auto initialTransformWorld = AZ::Transform::CreateTranslation(AZ::Vector3(-10.0f, 10.0f, 0.0f));
// where the entity should end up (in front and to the right of the camera)
const auto finalTransformWorld = AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 10.0f, 0.0f));
// calculate the position in screen space of the initial position of the entity
const auto initialPositionScreen = AzFramework::WorldToScreen(initialTransformWorld.GetTranslation(), m_cameraState);
// calculate the position in screen space of the final position of the entity
const auto finalPositionScreen = AzFramework::WorldToScreen(finalTransformWorld.GetTranslation(), m_cameraState);
// move the entity to its starting position
AzToolsFramework::SetWorldTransform(m_entity1, initialTransformWorld);
// select the entity (this will cause the manipulators to appear in EditorTransformComponentSelection)
AzToolsFramework::SelectEntity(m_entity1);
// create an offset along the linear manipulator pointing along the x-axis (perpendicular to the camera view)
const auto mouseOffsetOnManipulator = AzFramework::ScreenVector(10, 0);
// store the mouse down position on the manipulator
const auto mouseDownPosition = initialPositionScreen + mouseOffsetOnManipulator;
// final position in screen space of the mouse
const auto mouseMovePosition = finalPositionScreen + mouseOffsetOnManipulator;
m_actionDispatcher->CameraState(m_cameraState)
->MousePosition(mouseDownPosition)
->MouseLButtonDown()
->MousePosition(mouseMovePosition)
->MouseLButtonUp();
// verify a EditorTransformChangeNotificationBus::OnEntityTransformChanged occurred
using ::testing::UnorderedElementsAreArray;
EXPECT_THAT(editorEntityChangeDetector.m_entityIds, UnorderedElementsAreArray(m_entityIds));
}
// simple widget to listen for a mouse wheel event and then forward it on to the ViewportSelectionRequestBus
class WheelEventWidget
: public QWidget
File diff suppressed because it is too large Load Diff
@@ -1,878 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
/*
* Temporary dynamic tree structure used internally by GridMate.
* To be replaced with a general Vis framework when that becomes available.
*/
#ifndef RR_DYNAMIC_BOUNDING_VOLUME_TREE_H
#define RR_DYNAMIC_BOUNDING_VOLUME_TREE_H
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Plane.h>
#include <GridMate/Containers/vector.h>
#include <AzCore/std/containers/fixed_vector.h>
namespace GridMate
{
namespace Internal
{
/**
*
*/
class DynamicTreeAabb : public AZ::Aabb
{
public:
GM_CLASS_ALLOCATOR(DynamicTreeAabb);
AZ_FORCE_INLINE explicit DynamicTreeAabb() {}
AZ_FORCE_INLINE DynamicTreeAabb(const AZ::Aabb& aabb) : AZ::Aabb(aabb) {}
AZ_FORCE_INLINE explicit DynamicTreeAabb(const AZ::Vector3& min,const AZ::Vector3& max) : AZ::Aabb(AZ::Aabb::CreateFromMinMax(min,max)) {}
AZ_FORCE_INLINE static DynamicTreeAabb CreateFromFacePoints(const AZ::Vector3& a, const AZ::Vector3& b, const AZ::Vector3& c)
{
DynamicTreeAabb vol(a,a);
vol.AddPoint(b);
vol.AddPoint(c);
return vol;
}
AZ_FORCE_INLINE void SignedExpand(const AZ::Vector3& e)
{
AZ::Vector3 zero = AZ::Vector3::CreateZero();
AZ::Vector3 mxE = m_max + e;
AZ::Vector3 miE = m_min + e;
m_max = AZ::Vector3::CreateSelectCmpGreater(e,zero,mxE,m_max );
m_min = AZ::Vector3::CreateSelectCmpGreater(e,zero,m_min,miE);
}
AZ_FORCE_INLINE int Classify(const AZ::Vector3& n,const float o,int s) const
{
AZ::Vector3 pi, px;
switch(s)
{
case (0+0+0): px=m_min;
pi=m_max; break;
case (1+0+0): px=AZ::Vector3(m_max.GetX(),m_min.GetY(),m_min.GetZ());
pi=AZ::Vector3(m_min.GetX(),m_max.GetY(),m_max.GetZ());break;
case (0+2+0): px=AZ::Vector3(m_min.GetX(),m_max.GetY(),m_min.GetZ());
pi=AZ::Vector3(m_max.GetX(),m_min.GetY(),m_max.GetZ());break;
case (1+2+0): px=AZ::Vector3(m_max.GetX(),m_max.GetY(),m_min.GetZ());
pi=AZ::Vector3(m_min.GetX(),m_min.GetY(),m_max.GetZ());break;
case (0+0+4): px=AZ::Vector3(m_min.GetX(),m_min.GetY(),m_max.GetZ());
pi=AZ::Vector3(m_max.GetX(),m_max.GetY(),m_min.GetZ());break;
case (1+0+4): px=AZ::Vector3(m_max.GetX(),m_min.GetY(),m_max.GetZ());
pi=AZ::Vector3(m_min.GetX(),m_max.GetY(),m_min.GetZ());break;
case (0+2+4): px=AZ::Vector3(m_min.GetX(),m_max.GetY(),m_max.GetZ());
pi=AZ::Vector3(m_max.GetX(),m_min.GetY(),m_min.GetZ());break;
case (1+2+4): px=m_max;
pi=m_min;break;
}
if (n.Dot(px) + o < 0.0f)
{
return -1;
}
if (n.Dot(pi) + o > 0.0f)
{
return 1;
}
return 0;
}
AZ_FORCE_INLINE float ProjectMinimum(const AZ::Vector3& v, unsigned signs) const
{
const AZ::Vector3* b[]={&m_max,&m_min};
const AZ::Vector3 p( b[(signs>>0)&1]->GetX(),b[(signs>>1)&1]->GetY(),b[(signs>>2)&1]->GetZ());
return p.Dot(v);
}
// Move the code here
AZ_FORCE_INLINE friend bool IntersectAabbAabb(const DynamicTreeAabb& a,const DynamicTreeAabb& b);
AZ_FORCE_INLINE friend bool IntersectAabbPoint(const DynamicTreeAabb& a, const AZ::Vector3& b);
AZ_FORCE_INLINE friend bool IntersectAabbPlane(const DynamicTreeAabb& a, const AZ::Plane& b);
AZ_FORCE_INLINE friend float Proximity(const DynamicTreeAabb& a, const DynamicTreeAabb& b);
AZ_FORCE_INLINE friend int Select(const DynamicTreeAabb& o, const DynamicTreeAabb& a, const DynamicTreeAabb& b);
AZ_FORCE_INLINE friend void Merge(const DynamicTreeAabb& a, const DynamicTreeAabb& b, DynamicTreeAabb& r);
AZ_FORCE_INLINE friend bool NotEqual(const DynamicTreeAabb& a, const DynamicTreeAabb& b);
private:
AZ_FORCE_INLINE void AddSpan(const AZ::Vector3& d, float& smi, float& smx) const
{
AZ::Vector3 vecZero = AZ::Vector3::CreateZero();
AZ::Vector3 mxD = m_max*d;
AZ::Vector3 miD = m_min*d;
AZ::Vector3 smiAdd = AZ::Vector3::CreateSelectCmpGreater(vecZero,d,mxD,miD);
AZ::Vector3 smxAdd = AZ::Vector3::CreateSelectCmpGreater(vecZero,d,miD,mxD);
AZ::Vector3 vecOne = AZ::Vector3::CreateOne();
// sum components
smi += smiAdd.Dot(vecOne);
smx += smxAdd.Dot(vecOne);
}
};
//
AZ_FORCE_INLINE bool IntersectAabbAabb(const DynamicTreeAabb& a, const DynamicTreeAabb& b)
{
return a.Overlaps(b);
}
AZ_FORCE_INLINE bool IntersectAabbPlane(const DynamicTreeAabb& a, const AZ::Plane& b)
{
//use plane normal to quickly select the nearest corner of the aabb
AZ::Vector3 testPoint = AZ::Vector3::CreateSelectCmpGreater(b.GetNormal(), AZ::Vector3::CreateZero(), a.GetMin(), a.GetMax());
//test if nearest point is inside the plane
return b.GetPointDist(testPoint) <= 0.0f;
}
//
AZ_FORCE_INLINE float Proximity(const DynamicTreeAabb& a, const DynamicTreeAabb& b)
{
const AZ::Vector3 d=(a.m_min+a.m_max)-(b.m_min+b.m_max);
// get abs and sum
return d.GetAbs().Dot(AZ::Vector3::CreateOne());
}
//
AZ_FORCE_INLINE int Select( const DynamicTreeAabb& o, const DynamicTreeAabb& a, const DynamicTreeAabb& b)
{
return Proximity(o,a) < Proximity(o,b);
}
//
AZ_FORCE_INLINE void Merge(const DynamicTreeAabb& a, const DynamicTreeAabb& b, DynamicTreeAabb& r)
{
r.m_min = AZ::Vector3::CreateSelectCmpGreater(b.m_min,a.m_min,a.m_min,b.m_min);
r.m_max = AZ::Vector3::CreateSelectCmpGreater(a.m_max,b.m_max,a.m_max,b.m_max);
}
//
AZ_FORCE_INLINE bool NotEqual( const DynamicTreeAabb& a, const DynamicTreeAabb& b)
{
return (a.m_min != b.m_min || a.m_max != b.m_max);
}
/* NodeType */
struct DynamicTreeNode
{
GM_CLASS_ALLOCATOR(DynamicTreeNode);
DynamicTreeAabb m_volume;
DynamicTreeNode* m_parent;
AZ_FORCE_INLINE bool IsLeaf() const { return(m_childs[1]==0); }
AZ_FORCE_INLINE bool IsInternal() const { return(!IsLeaf()); }
union
{
DynamicTreeNode* m_childs[2];
void* m_data;
int m_dataAsInt;
};
};
}
/**
* Implementation of dynamic aabb tree, based on the bullet dynamic tree (btDbvt).
*
* The BvDynamicTree class implements a fast dynamic bounding volume tree based on axis aligned bounding boxes (aabb tree).
* This BvDynamicTree is used for soft body collision detection and for the btDbvtBroadphase. It has a fast insert, remove and update of nodes.
* Unlike the BvTreeQuantized, nodes can be dynamically moved around, which allows for change in topology of the underlying data structure.
*/
class BvDynamicTree
{
public:
using Ptr = AZStd::intrusive_ptr<BvDynamicTree>;
GM_CLASS_ALLOCATOR(BvDynamicTree);
typedef Internal::DynamicTreeAabb VolumeType;
typedef Internal::DynamicTreeNode NodeType;
typedef vector<NodeType*> NodeArrayType;
typedef vector<const NodeType*> ConstNodeArrayType;
private:
/* Stack element */
struct sStkNN
{
const NodeType* a;
const NodeType* b;
sStkNN() {}
sStkNN(const NodeType* na,const NodeType* nb) : a(na), b(nb) {}
};
struct sStkNP
{
const NodeType* node;
int mask;
sStkNP(const NodeType* n, unsigned m) : node(n), mask(m) {}
};
struct sStkNPS
{
const NodeType* node;
int mask;
float value;
sStkNPS() {}
sStkNPS(const NodeType* n, unsigned m, const float v) : node(n), mask(m), value(v) {}
};
struct sStkCLN
{
const NodeType* node;
NodeType* parent;
sStkCLN(const NodeType* n, NodeType* p) : node(n), parent(p) {}
};
public:
/* ICollideCollector templated collectors should implement this functions or inherit from this class */
struct ICollideCollector
{
void Process(const NodeType*, const NodeType*) {}
void Process(const NodeType*) {}
void Process(const NodeType* n, const float) { Process(n); }
bool Descent(const NodeType*) { return true; }
bool AllLeaves(const NodeType*) { return true; }
};
/* IWriter */
struct IWriter
{
virtual ~IWriter() {}
virtual void Prepare(const NodeType* root,int numnodes) = 0;
virtual void WriteNode(const NodeType*, int index, int parent, int child0, int child1) = 0;
virtual void WriteLeaf(const NodeType*, int index, int parent) = 0;
};
/* IClone */
struct IClone
{
virtual ~IClone() {}
virtual void CloneLeaf(NodeType*) {}
};
// Constants
enum
{
SIMPLE_STACKSIZE = 64,
DOUBLE_STACKSIZE = SIMPLE_STACKSIZE * 2
};
// Methods
BvDynamicTree();
~BvDynamicTree();
NodeType* GetRoot() const { return m_root; }
void Clear();
bool Empty() const { return 0 == m_root; }
int GetNumLeaves() const { return m_leaves; }
void OptimizeBottomUp();
void OptimizeTopDown(int bu_treshold = 128);
void OptimizeIncremental(int passes);
NodeType* Insert(const VolumeType& box,void* data);
void Update(NodeType* leaf, int lookahead=-1);
void Update(NodeType* leaf, VolumeType& volume);
bool Update(NodeType* leaf, VolumeType& volume, const AZ::Vector3& velocity, const float margin);
bool Update(NodeType* leaf, VolumeType& volume, const AZ::Vector3& velocity);
bool Update(NodeType* leaf, VolumeType& volume, const float margin);
void Remove(NodeType* leaf);
void Write(IWriter* iwriter) const;
void Clone(BvDynamicTree& dest, IClone* iclone=0) const;
static int GetMaxDepth(const NodeType* node);
static int CountLeaves(const NodeType* node);
static void ExtractLeaves(const NodeType* node, /*btAlignedObjectArray<const NodeType*>&*/vector<const NodeType*>& leaves);
#if DBVT_ENABLE_BENCHMARK
static void Benchmark();
#else
static void Benchmark(){}
#endif
/**
* Collector should inherit from ICollide
*/
template<class Collector>
static inline void enumNodes( const NodeType* root, Collector& collector)
{
collector.Process(root);
if(root->IsInternal())
{
enumNodes(root->m_childs[0],collector);
enumNodes(root->m_childs[1],collector);
}
}
template<class Collector>
static void enumLeaves( const NodeType* root,Collector& collector)
{
if(root->IsInternal())
{
enumLeaves(root->m_childs[0],collector);
enumLeaves(root->m_childs[1],collector);
}
else
{
collector.Process(root);
}
}
template<class Collector>
void collideTT( const NodeType* root0,const NodeType* root1,Collector& collector) const
{
if(root0&&root1)
{
size_t depth=1;
size_t treshold=DOUBLE_STACKSIZE-4;
vector<sStkNN> stkStack;
stkStack.resize(DOUBLE_STACKSIZE);
stkStack[0]=sStkNN(root0,root1);
do {
sStkNN p=stkStack[--depth];
if(depth>treshold)
{
stkStack.resize(stkStack.size()*2);
treshold=stkStack.size()-4;
}
if(p.a==p.b)
{
if(p.a->IsInternal())
{
stkStack[depth++]=sStkNN(p.a->m_childs[0],p.a->m_childs[0]);
stkStack[depth++]=sStkNN(p.a->m_childs[1],p.a->m_childs[1]);
stkStack[depth++]=sStkNN(p.a->m_childs[0],p.a->m_childs[1]);
}
}
else if(IntersectAabbAabb(p.a->m_volume,p.b->m_volume))
{
if(p.a->IsInternal())
{
if(p.b->IsInternal())
{
stkStack[depth++]=sStkNN(p.a->m_childs[0],p.b->m_childs[0]);
stkStack[depth++]=sStkNN(p.a->m_childs[1],p.b->m_childs[0]);
stkStack[depth++]=sStkNN(p.a->m_childs[0],p.b->m_childs[1]);
stkStack[depth++]=sStkNN(p.a->m_childs[1],p.b->m_childs[1]);
}
else
{
stkStack[depth++]=sStkNN(p.a->m_childs[0],p.b);
stkStack[depth++]=sStkNN(p.a->m_childs[1],p.b);
}
}
else
{
if(p.b->IsInternal())
{
stkStack[depth++]=sStkNN(p.a,p.b->m_childs[0]);
stkStack[depth++]=sStkNN(p.a,p.b->m_childs[1]);
}
else
{
collector.Process(p.a,p.b);
}
}
}
} while(depth);
}
}
template<class Collector>
void collideTTpersistentStack( const NodeType* root0, const NodeType* root1,Collector& collector)
{
if(root0&&root1)
{
size_t depth=1;
size_t treshold=DOUBLE_STACKSIZE-4;
m_stkStack.resize(DOUBLE_STACKSIZE);
m_stkStack[0]=sStkNN(root0,root1);
do
{
sStkNN p=m_stkStack[--depth];
if(depth>treshold)
{
m_stkStack.resize(m_stkStack.size()*2);
treshold=m_stkStack.size()-4;
}
if(p.a==p.b)
{
if(p.a->IsInternal())
{
m_stkStack[depth++]=sStkNN(p.a->m_childs[0],p.a->m_childs[0]);
m_stkStack[depth++]=sStkNN(p.a->m_childs[1],p.a->m_childs[1]);
m_stkStack[depth++]=sStkNN(p.a->m_childs[0],p.a->m_childs[1]);
}
}
else if(IntersectAabbAabb(p.a->m_volume,p.b->m_volume))
{
if(p.a->IsInternal())
{
if(p.b->IsInternal())
{
m_stkStack[depth++]=sStkNN(p.a->m_childs[0],p.b->m_childs[0]);
m_stkStack[depth++]=sStkNN(p.a->m_childs[1],p.b->m_childs[0]);
m_stkStack[depth++]=sStkNN(p.a->m_childs[0],p.b->m_childs[1]);
m_stkStack[depth++]=sStkNN(p.a->m_childs[1],p.b->m_childs[1]);
}
else
{
m_stkStack[depth++]=sStkNN(p.a->m_childs[0],p.b);
m_stkStack[depth++]=sStkNN(p.a->m_childs[1],p.b);
}
}
else
{
if(p.b->IsInternal())
{
m_stkStack[depth++]=sStkNN(p.a,p.b->m_childs[0]);
m_stkStack[depth++]=sStkNN(p.a,p.b->m_childs[1]);
}
else
{
collector.Process(p.a,p.b);
}
}
}
} while(depth);
}
}
template<class Collector>
void collideTV( const NodeType* root, const VolumeType& volume, Collector& collector) const
{
if(root)
{
// ATTRIBUTE_ALIGNED16(VolumeType) volume(vol);
// btAlignedObjectArray<const NodeType*> stack;
AZStd::fixed_vector<const NodeType*,SIMPLE_STACKSIZE> stack;
//stack.reserve(SIMPLE_STACKSIZE);
stack.push_back(root);
do {
const NodeType* n=stack[stack.size()-1];
stack.pop_back();
if(IntersectAabbAabb(n->m_volume,volume))
{
if(n->IsInternal())
{
stack.push_back(n->m_childs[0]);
stack.push_back(n->m_childs[1]);
}
else
{
collector.Process(n);
}
}
} while(!stack.empty());
}
}
template<class Collector>
void collideTP(const NodeType* root, const AZ::Plane& plane, Collector& collector) const
{
if (root)
{
AZStd::fixed_vector<const NodeType*,SIMPLE_STACKSIZE> stack;
stack.push_back(root);
do
{
const NodeType* n=stack[stack.size()-1];
stack.pop_back();
if (IntersectAabbPlane(n->m_volume, plane))
{
if(n->IsInternal())
{
stack.push_back(n->m_childs[0]);
stack.push_back(n->m_childs[1]);
}
else
{
collector.Process(n);
}
}
} while (!stack.empty());
}
}
///rayTest is a re-entrant ray test, and can be called in parallel as long as the btAlignedAlloc is thread-safe (uses locking etc)
///rayTest is slower than rayTestInternal, because it builds a local stack, using memory allocations, and it recomputes signs/rayDirectionInverses each time
template<class Collector>
static void rayTest( const NodeType* root, const AZ::Vector3& rayFrom, const AZ::Vector3& rayTo, Collector& collector)
{
if(root)
{
AZ::Vector3 ray = rayTo-rayFrom;
AZ::Vector3 rayDir = ray.GetNormalized();
///what about division by zero? --> just set rayDirection[i] to INF/1e30
AZ::Vector3 rayDirectionInverse = AZ::Vector3::CreateSelectCmpEqual(rayDir,AZ::Vector3::CreateZero(),AZ::Vector3(1e30),rayDir.GetReciprocal());
unsigned int signs[3];// = { rayDirectionInverse[0] < 0.0f, rayDirectionInverse[1] < 0.0f, rayDirectionInverse[2] < 0.0f };
signs[0] = rayDirectionInverse.GetX() < 0.0f;
signs[1] = rayDirectionInverse.GetY() < 0.0f;
signs[2] = rayDirectionInverse.GetZ() < 0.0f;
//float lambda_max = rayDir.Dot(ray);
AZ::Vector3 resultNormal;
//btAlignedObjectArray<const NodeType*> stack;
vector<const NodeType*> stack;
int depth=1;
int treshold=DOUBLE_STACKSIZE-2;
stack.resize(DOUBLE_STACKSIZE);
stack[0]=root;
AZ::Vector3 bounds[2];
do {
const NodeType* node=stack[--depth];
bounds[0] = node->m_volume.GetMin();
bounds[1] = node->m_volume.GetMax();
//float tmin = 1.0f;
//float lambda_min = 0.0f;
// todo..
unsigned int result1 = /*btRayAabb2(rayFrom,rayDirectionInverse,signs,bounds,tmin,lambda_min,lambda_max)*/0;
#ifdef COMPARE_BTRAY_AABB2
float param = 1.0f;
bool result2 = /*btRayAabb(rayFrom,rayTo,node->volume.GetMin(),node->volume.GetMax(),param,resultNormal)*/0;
AZ_Assert(result1 == result2, "");
#endif //TEST_BTRAY_AABB2
if(result1)
{
if(node->IsInternal())
{
if(depth>treshold)
{
stack.resize(stack.size()*2);
treshold=stack.size()-2;
}
stack[depth++]=node->m_childs[0];
stack[depth++]=node->m_childs[1];
}
else
{
collector.Process(node);
}
}
} while(depth);
}
}
///rayTestInternal is faster than rayTest, because it uses a persistent stack (to reduce dynamic memory allocations to a minimum) and it uses precomputed signs/rayInverseDirections
///rayTestInternal is used by btDbvtBroadphase to accelerate world ray casts
template<class Collector>
void rayTestInternal(const NodeType* root, const AZ::Vector3& rayFrom, const AZ::Vector3& rayTo, const AZ::Vector3& rayDirectionInverse, unsigned int signs[3], const float lambda_max, const AZ::Vector3& aabbMin, const AZ::Vector3& aabbMax, Collector& collector) const
{
(void)rayFrom;(void)rayTo;(void)rayDirectionInverse;(void)signs;(void)lambda_max;
if(root)
{
AZ::Vector3 resultNormal;
int depth=1;
int treshold=DOUBLE_STACKSIZE-2;
vector<const NodeType*> stack;
stack.resize(DOUBLE_STACKSIZE);
stack[0]=root;
AZ::Vector3 bounds[2];
do
{
const NodeType* node=stack[--depth];
bounds[0] = node->m_volume.GetMin()+aabbMin;
bounds[1] = node->m_volume.GetMax()+aabbMax;
//float tmin = 1.0f;
//float lambda_min = 0.0f;
unsigned int result1=false;
// todo...
result1 = /*btRayAabb2(rayFrom,rayDirectionInverse,signs,bounds,tmin,lambda_min,lambda_max)*/false;
if(result1)
{
if(node->IsInternal())
{
if(depth>treshold)
{
stack.resize(stack.size()*2);
treshold=stack.size()-2;
}
stack[depth++]=node->m_childs[0];
stack[depth++]=node->m_childs[1];
}
else
{
collector.Process(node);
}
}
} while(depth);
}
}
template<class Collector>
static void collideKDOP(const NodeType* root, const AZ::Vector3* normals, const float* offsets, int count, Collector& collector)
{
(void)root;(void)normals;(void)offsets;(void)count;(void)collector;
/* if(root)
{
const int inside=(1<<count)-1;
btAlignedObjectArray<sStkNP> stack;
int signs[sizeof(unsigned)*8];
btAssert(count<int (sizeof(signs)/sizeof(signs[0])));
for(int i=0;i<count;++i)
{
signs[i]= ((normals[i].x()>=0)?1:0)+
((normals[i].y()>=0)?2:0)+
((normals[i].z()>=0)?4:0);
}
stack.reserve(SIMPLE_STACKSIZE);
stack.push_back(sStkNP(root,0));
do {
sStkNP se=stack[stack.size()-1];
bool out=false;
stack.pop_back();
for(int i=0,j=1;(!out)&&(i<count);++i,j<<=1)
{
if(0==(se.mask&j))
{
const int side=se.node->volume.Classify(normals[i],offsets[i],signs[i]);
switch(side)
{
case -1: out=true;break;
case +1: se.mask|=j;break;
}
}
}
if(!out)
{
if((se.mask!=inside)&&(se.node->isinternal()))
{
stack.push_back(sStkNP(se.node->childs[0],se.mask));
stack.push_back(sStkNP(se.node->childs[1],se.mask));
}
else
{
if(policy.AllLeaves(se.node)) enumLeaves(se.node,policy);
}
}
} while(!stack.empty());
}*/
}
template<class Collector>
static void collideOCL( const NodeType* root, const AZ::Vector3* normals, const float* offsets, const AZ::Vector3& sortaxis, int count, Collector& collector, bool fullsort=true)
{
(void)root;(void)normals;(void)offsets;(void)sortaxis;(void)count;(void)offsets;(void)collector;(void)fullsort;
/* if(root)
{
const unsigned srtsgns=(sortaxis[0]>=0?1:0)+
(sortaxis[1]>=0?2:0)+
(sortaxis[2]>=0?4:0);
const int inside=(1<<count)-1;
btAlignedObjectArray<sStkNPS> stock;
btAlignedObjectArray<int> ifree;
btAlignedObjectArray<int> stack;
int signs[sizeof(unsigned)*8];
btAssert(count<int (sizeof(signs)/sizeof(signs[0])));
for(int i=0;i<count;++i)
{
signs[i]= ((normals[i].x()>=0)?1:0)+
((normals[i].y()>=0)?2:0)+
((normals[i].z()>=0)?4:0);
}
stock.reserve(SIMPLE_STACKSIZE);
stack.reserve(SIMPLE_STACKSIZE);
ifree.reserve(SIMPLE_STACKSIZE);
stack.push_back(allocate(ifree,stock,sStkNPS(root,0,root->volume.ProjectMinimum(sortaxis,srtsgns))));
do {
const int id=stack[stack.size()-1];
sStkNPS se=stock[id];
stack.pop_back();ifree.push_back(id);
if(se.mask!=inside)
{
bool out=false;
for(int i=0,j=1;(!out)&&(i<count);++i,j<<=1)
{
if(0==(se.mask&j))
{
const int side=se.node->volume.Classify(normals[i],offsets[i],signs[i]);
switch(side)
{
case -1: out=true;break;
case +1: se.mask|=j;break;
}
}
}
if(out) continue;
}
if(policy.Descent(se.node))
{
if(se.node->isinternal())
{
const NodeType* pns[]={ se.node->childs[0],se.node->childs[1]};
sStkNPS nes[]={ sStkNPS(pns[0],se.mask,pns[0]->volume.ProjectMinimum(sortaxis,srtsgns)),
sStkNPS(pns[1],se.mask,pns[1]->volume.ProjectMinimum(sortaxis,srtsgns))};
const int q=nes[0].value<nes[1].value?1:0;
int j=stack.size();
if(fsort&&(j>0))
{
// Insert 0
j=nearest(&stack[0],&stock[0],nes[q].value,0,stack.size());
stack.push_back(0);
#if DBVT_USE_MEMMOVE
memmove(&stack[j+1],&stack[j],sizeof(int)*(stack.size()-j-1));
#else
for(int k=stack.size()-1;k>j;--k) stack[k]=stack[k-1];
#endif
stack[j]=allocate(ifree,stock,nes[q]);
// Insert 1
j=nearest(&stack[0],&stock[0],nes[1-q].value,j,stack.size());
stack.push_back(0);
#if DBVT_USE_MEMMOVE
memmove(&stack[j+1],&stack[j],sizeof(int)*(stack.size()-j-1));
#else
for(int k=stack.size()-1;k>j;--k) stack[k]=stack[k-1];
#endif
stack[j]=allocate(ifree,stock,nes[1-q]);
}
else
{
stack.push_back(allocate(ifree,stock,nes[q]));
stack.push_back(allocate(ifree,stock,nes[1-q]));
}
}
else
{
policy.Process(se.node,se.value);
}
}
} while(stack.size());
}*/
}
template<class Collector>
static void collideTU(const NodeType* root, Collector& collector)
{
(void)root;(void)collector;
/* if(root)
{
btAlignedObjectArray<const NodeType*> stack;
stack.reserve(SIMPLE_STACKSIZE);
stack.push_back(root);
do {
const NodeType* n=stack[stack.size()-1];
stack.pop_back();
if(policy.Descent(n))
{
if(n->isinternal())
{ stack.push_back(n->childs[0]);stack.push_back(n->childs[1]); }
else
{ policy.Process(n); }
}
} while(stack.size()>0);
}*/
}
private:
BvDynamicTree(const BvDynamicTree&) {}
// Helpers
//static AZ_FORCE_INLINE int nearest(const int* i,const BvDynamicTree::sStkNPS* a,const float& v,int l,int h)
//{
// int m=0;
// while(l<h)
// {
// m=(l+h)>>1;
// if(a[i[m]].value>=v) l=m+1; else h=m;
// }
// return h;
//}
//static AZ_FORCE_INLINE int allocate( int_fixed_stack_type& ifree, stknps_fixed_stack_type& stock, const sStkNPS& value)
//{
// int i;
// if( !ifree.empty() )
// {
// i=ifree[ifree.size()-1];
// ifree.pop_back();
// stock[i]=value;
// }
// else
// {
// i=stock.size();
// stock.push_back(value);
// }
// return i;
//}
//
AZ_FORCE_INLINE void deletenode( NodeType* node)
{
//btAlignedFree(pdbvt->m_free);
delete m_free;
m_free=node;
}
void recursedeletenode( NodeType* node)
{
if(!node->IsLeaf())
{
recursedeletenode(node->m_childs[0]);
recursedeletenode(node->m_childs[1]);
}
if( node == m_root ) m_root=0;
deletenode(node);
}
AZ_FORCE_INLINE NodeType* createnode( NodeType* parent, void* data)
{
NodeType* node;
if(m_free)
{ node=m_free;m_free=0; }
else
{ node = aznew NodeType(); }
node->m_parent = parent;
node->m_data = data;
node->m_childs[1] = 0;
return node;
}
AZ_FORCE_INLINE NodeType* createnode( BvDynamicTree::NodeType* parent, const VolumeType& volume, void* data)
{
NodeType* node = createnode(parent,data);
node->m_volume=volume;
return node;
}
//
AZ_FORCE_INLINE NodeType* createnode( BvDynamicTree::NodeType* parent, const VolumeType& volume0, const VolumeType& volume1, void* data)
{
NodeType* node = createnode(parent,data);
Merge(volume0,volume1,node->m_volume);
return node;
}
void insertleaf( NodeType* root, NodeType* leaf);
NodeType* removeleaf( NodeType* leaf);
void fetchleaves(NodeType* root,NodeArrayType& leaves,int depth=-1);
void split(const NodeArrayType& leaves,NodeArrayType& left,NodeArrayType& right,const AZ::Vector3& org,const AZ::Vector3& axis);
VolumeType bounds(const NodeArrayType& leaves);
void bottomup( NodeArrayType& leaves );
NodeType* topdown(NodeArrayType& leaves,int bu_treshold);
AZ_FORCE_INLINE NodeType* sort(NodeType* n,NodeType*& r);
NodeType* m_root;
NodeType* m_free;
int m_lkhd;
int m_leaves;
unsigned m_opath;
//btAlignedObjectArray<sStkNN> m_stkStack;
// Profile and choose static or dynamic vector.
typedef AZStd::fixed_vector<sStkNN,DOUBLE_STACKSIZE> stknn_fixed_stack_type;
typedef AZStd::fixed_vector<int,SIMPLE_STACKSIZE> int_fixed_stack_type;
typedef AZStd::fixed_vector<sStkNPS,SIMPLE_STACKSIZE> stknps_fixed_stack_type;
stknn_fixed_stack_type m_stkStack;
};
}
#endif // RR_DYNAMIC_BOUNDING_VOLUME_TREE_H
#pragma once
@@ -1,597 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <GridMate/Replica/Interest/ProximityInterestHandler.h>
#include <GridMate/Replica/Replica.h>
#include <GridMate/Replica/ReplicaFunctions.h>
#include <GridMate/Replica/ReplicaMgr.h>
#include <GridMate/Replica/Interest/InterestManager.h>
#include <GridMate/Replica/Interest/BvDynamicTree.h>
// for highly verbose internal debugging
//#define INTERNAL_DEBUG_PROXIMITY
namespace GridMate
{
void ProximityInterestChunk::OnReplicaActivate(const ReplicaContext& rc)
{
m_interestHandler = static_cast<ProximityInterestHandler*>(rc.m_rm->GetUserContext(AZ_CRC("ProximityInterestHandler", 0x3a90b3e4)));
AZ_Warning("GridMate", m_interestHandler, "No proximity interest handler in the user context");
if (m_interestHandler)
{
m_interestHandler->OnNewRulesChunk(this, rc.m_peer);
}
}
void ProximityInterestChunk::OnReplicaDeactivate(const ReplicaContext& rc)
{
if (rc.m_peer && m_interestHandler)
{
m_interestHandler->OnDeleteRulesChunk(this, rc.m_peer);
}
}
bool ProximityInterestChunk::AddRuleFn(RuleNetworkId netId, AZ::Aabb bbox, const RpcContext& ctx)
{
if (IsProxy())
{
auto rulePtr = m_interestHandler->CreateRule(ctx.m_sourcePeer);
rulePtr->Set(bbox);
m_rules.insert(AZStd::make_pair(netId, rulePtr));
}
return true;
}
bool ProximityInterestChunk::RemoveRuleFn(RuleNetworkId netId, const RpcContext&)
{
if (IsProxy())
{
m_rules.erase(netId);
}
return true;
}
bool ProximityInterestChunk::UpdateRuleFn(RuleNetworkId netId, AZ::Aabb bbox, const RpcContext&)
{
if (IsProxy())
{
auto it = m_rules.find(netId);
if (it != m_rules.end())
{
it->second->Set(bbox);
}
}
return true;
}
bool ProximityInterestChunk::AddRuleForPeerFn(RuleNetworkId netId, PeerId peerId, AZ::Aabb bbox, const RpcContext&)
{
ProximityInterestChunk* peerChunk = m_interestHandler->FindRulesChunkByPeerId(peerId);
if (peerChunk)
{
auto it = peerChunk->m_rules.find(netId);
if (it == peerChunk->m_rules.end())
{
auto rulePtr = m_interestHandler->CreateRule(peerId);
peerChunk->m_rules.insert(AZStd::make_pair(netId, rulePtr));
rulePtr->Set(bbox);
}
}
return false;
}
///////////////////////////////////////////////////////////////////////////
/*
* ProximityInterest
*/
ProximityInterest::ProximityInterest(ProximityInterestHandler* handler)
: m_handler(handler)
, m_bbox(AZ::Aabb::CreateNull())
{
AZ_Assert(m_handler, "Invalid interest handler");
}
///////////////////////////////////////////////////////////////////////////
/*
* ProximityInterestRule
*/
void ProximityInterestRule::Set(const AZ::Aabb& bbox)
{
m_bbox = bbox;
m_handler->UpdateRule(this);
}
void ProximityInterestRule::Destroy()
{
m_handler->DestroyRule(this);
}
///////////////////////////////////////////////////////////////////////////
/*
* ProximityInterestAttribute
*/
void ProximityInterestAttribute::Set(const AZ::Aabb& bbox)
{
m_bbox = bbox;
m_handler->UpdateAttribute(this);
}
void ProximityInterestAttribute::Destroy()
{
m_handler->DestroyAttribute(this);
}
///////////////////////////////////////////////////////////////////////////
/*
* ProximityInterestHandler
*/
ProximityInterestHandler::ProximityInterestHandler()
: m_im(nullptr)
, m_rm(nullptr)
, m_lastRuleNetId(0)
, m_rulesReplica(nullptr)
{
m_attributeWorld = AZStd::make_unique<SpatialIndex>();
AZ_Assert(m_attributeWorld, "Out of memory");
}
ProximityInterestRule::Ptr ProximityInterestHandler::CreateRule(PeerId peerId)
{
ProximityInterestRule* rulePtr = aznew ProximityInterestRule(this, peerId, GetNewRuleNetId());
if (m_rm && peerId == m_rm->GetLocalPeerId())
{
m_rulesReplica->AddRuleRpc(rulePtr->GetNetworkId(), rulePtr->Get());
}
CreateAndInsertIntoSpatialStructure(rulePtr);
return rulePtr;
}
ProximityInterestAttribute::Ptr ProximityInterestHandler::CreateAttribute(ReplicaId replicaId)
{
auto newAttribute = aznew ProximityInterestAttribute(this, replicaId);
AZ_Assert(newAttribute, "Out of memory");
CreateAndInsertIntoSpatialStructure(newAttribute);
return newAttribute;
}
void ProximityInterestHandler::FreeRule(ProximityInterestRule* rule)
{
//TODO: should be pool-allocated
delete rule;
}
void ProximityInterestHandler::DestroyRule(ProximityInterestRule* rule)
{
if (m_rm && rule->GetPeerId() == m_rm->GetLocalPeerId())
{
m_rulesReplica->RemoveRuleRpc(rule->GetNetworkId());
}
MarkAttributesDirtyInRule(rule);
rule->m_bbox = AZ::Aabb::CreateNull();
m_removedRules.insert(rule);
m_localRules.erase(rule);
}
void ProximityInterestHandler::UpdateRule(ProximityInterestRule* rule)
{
if (m_rm && rule->GetPeerId() == m_rm->GetLocalPeerId())
{
m_rulesReplica->UpdateRuleRpc(rule->GetNetworkId(), rule->Get());
}
m_dirtyRules.insert(rule);
}
void ProximityInterestHandler::FreeAttribute(ProximityInterestAttribute* attrib)
{
delete attrib;
}
void ProximityInterestHandler::DestroyAttribute(ProximityInterestAttribute* attrib)
{
RemoveFromSpatialStructure(attrib);
m_attributes.erase(attrib);
m_removedAttributes.insert(attrib);
}
void ProximityInterestHandler::RemoveFromSpatialStructure(ProximityInterestAttribute* attribute)
{
attribute->m_bbox = AZ::Aabb::CreateNull();
m_attributeWorld->Remove(attribute->GetNode());
attribute->SetNode(nullptr);
}
void ProximityInterestHandler::UpdateAttribute(ProximityInterestAttribute* attrib)
{
auto node = attrib->GetNode();
AZ_Assert(node, "Attribute wasn't created correctly");
node->m_volume = attrib->Get();
m_attributeWorld->Update(node);
m_dirtyAttributes.insert(attrib);
}
void ProximityInterestHandler::OnNewRulesChunk(ProximityInterestChunk* chunk, ReplicaPeer* peer)
{
if (chunk != m_rulesReplica) // non-local
{
m_peerChunks.insert(AZStd::make_pair(peer->GetId(), chunk));
for (auto& rule : m_localRules)
{
chunk->AddRuleForPeerRpc(rule->GetNetworkId(), rule->GetPeerId(), rule->Get());
}
}
}
void ProximityInterestHandler::OnDeleteRulesChunk(ProximityInterestChunk* chunk, ReplicaPeer* peer)
{
(void)chunk;
m_peerChunks.erase(peer->GetId());
}
RuleNetworkId ProximityInterestHandler::GetNewRuleNetId()
{
++m_lastRuleNetId;
if (m_rulesReplica)
{
return m_rulesReplica->GetReplicaId() | (static_cast<AZ::u64>(m_lastRuleNetId) << 32);
}
return (static_cast<AZ::u64>(m_lastRuleNetId) << 32);
}
ProximityInterestChunk* ProximityInterestHandler::FindRulesChunkByPeerId(PeerId peerId)
{
auto it = m_peerChunks.find(peerId);
if (it == m_peerChunks.end())
{
return nullptr;
}
return it->second;
}
const InterestMatchResult& ProximityInterestHandler::GetLastResult()
{
return m_resultCache;
}
ProximityInterestHandler::RuleSet& ProximityInterestHandler::GetAffectedRules()
{
/*
* The expectation that lots of attributes will change frequently,
* so there is no point in trying to optimize cases
* where only a few attributes have changed.
*/
if (m_dirtyAttributes.empty() && !m_dirtyRules.empty())
{
return m_dirtyRules;
}
/*
* Assuming all rules might have been affected.
*
* There is an optimization chance here if the number of rules is large, as in 1,000+ rules.
* To handle such scale we would need another spatial structure for rules.
*/
return m_localRules;
}
void ProximityInterestHandler::GetAttributesWithinRule(ProximityInterestRule* rule, SpatialIndex::NodeCollector& nodes)
{
m_attributeWorld->Query(rule->Get(), nodes);
}
void ProximityInterestHandler::ClearDirtyState()
{
m_dirtyAttributes.clear();
m_dirtyRules.clear();
}
void ProximityInterestHandler::CreateAndInsertIntoSpatialStructure(ProximityInterestAttribute* attribute)
{
m_attributes.insert(attribute);
SpatialIndex::Node* node = m_attributeWorld->Insert(attribute->Get(), attribute);
attribute->SetNode(node);
}
void ProximityInterestHandler::CreateAndInsertIntoSpatialStructure(ProximityInterestRule* rule)
{
m_localRules.insert(rule);
}
void ProximityInterestHandler::UpdateInternal(InterestMatchResult& result)
{
/*
* The goal is to return all dirty attributes that were either dirty because:
* 1) they changed which rules have apply to
* 2) rules have changed and no longer apply to those attributes
* and thus resulted in different peer(s) associated with a given replica.
*/
const RuleSet& rules = GetAffectedRules();
for (auto& dirtyAttribute : m_dirtyAttributes)
{
result.insert(dirtyAttribute->GetReplicaId());
}
/*
* The exectation is to have a lot more attributes than rules.
* The amount of rules should grow linear with amount of peers,
* so it should be OK to iterate through all rules each update.
*/
for (auto& rule : rules)
{
CheckChangesForRule(rule, result);
}
for (auto& removedRule : m_removedRules)
{
FreeRule(removedRule);
}
m_removedRules.clear();
// mark removed attribute as having no peers
for (auto& removedAttribute : m_removedAttributes)
{
result.insert(removedAttribute->GetReplicaId());
FreeAttribute(removedAttribute);
}
m_removedAttributes.clear();
}
void ProximityInterestHandler::CheckChangesForRule(ProximityInterestRule* rule, InterestMatchResult& result)
{
SpatialIndex::NodeCollector collector;
GetAttributesWithinRule(rule, collector);
auto peerId = rule->GetPeerId();
for (ProximityInterestAttribute* attr : collector.GetNodes())
{
AZ_Assert(attr, "bad node?");
auto findIt = result.find(attr->GetReplicaId());
if (findIt != result.end())
{
findIt->second.insert(peerId);
}
else
{
auto resultIt = result.insert(attr->GetReplicaId());
AZ_Assert(resultIt.second, "Successfully inserted");
resultIt.first->second.insert(peerId);
}
}
}
void ProximityInterestHandler::MarkAttributesDirtyInRule(ProximityInterestRule* rule)
{
SpatialIndex::NodeCollector collector;
GetAttributesWithinRule(rule, collector);
for (ProximityInterestAttribute* attr : collector.GetNodes())
{
AZ_Assert(attr, "bad node?");
UpdateAttribute(attr);
}
}
void ProximityInterestHandler::ProduceChanges(const InterestMatchResult& before, const InterestMatchResult& after)
{
m_resultCache.clear();
#if defined(INTERNAL_DEBUG_PROXIMITY)
before.PrintMatchResult("before");
after.PrintMatchResult("after");
#endif
/*
* 'after' contains only the stuff that might have changed
*/
for (auto& possiblyDirty : after)
{
ReplicaId repId = possiblyDirty.first;
const InterestPeerSet& peerSet = possiblyDirty.second;
auto foundInBefore = before.find(repId);
if (foundInBefore != before.end())
{
if (!HasSamePeers(foundInBefore->second, peerSet))
{
// was in the last calculation but has a different peer set now
m_resultCache.insert(AZStd::make_pair(repId, peerSet));
}
}
else
{
// since it wasn't present during last calculation
m_resultCache.insert(AZStd::make_pair(repId, peerSet));
}
}
// Mark attributes (replicas) for removal that have not moved but a rule (clients) no longer sees it
for (auto& possiblyDirty : before)
{
ReplicaId repId = possiblyDirty.first;
const auto foundInAfter = after.find(repId);
/*
* If the prior state was a replica A present on peer X: "A{X}", and now A should no longer be present on any peer: "A{}"
* then by the rules of InterestHandlers interacting with InterestManager, we should return in @m_resultCache the following:
*
* A{} - indicating that replica A must be removed all peers.
*
* On the next pass, the prior state would be: "A{}" and the current state would be "A{}" as well. At that point, we have
* already sent the update to remove A from X, so @m_resultCache should no longer mention A at all.
*/
if (foundInAfter == after.end() && !possiblyDirty.second.empty() /* "not A{}" see the above comment */)
{
m_resultCache.insert(AZStd::make_pair(repId, InterestPeerSet()));
}
}
#if defined(INTERNAL_DEBUG_PROXIMITY)
m_resultCache.PrintMatchResult("changes");
#endif
}
bool ProximityInterestHandler::HasSamePeers(const InterestPeerSet& one, const InterestPeerSet& another)
{
if (one.size() != another.size())
{
return false;
}
for (auto& peerFromOne : one)
{
if (another.find(peerFromOne) == another.end())
{
return false;
}
}
// Safe to assume it's the same sets since all entries are unique in a peer sets
return true;
}
void ProximityInterestHandler::Update()
{
InterestMatchResult newResult;
UpdateInternal(newResult);
ProduceChanges(m_lastResult, newResult);
m_lastResult = std::move(newResult);
ClearDirtyState();
}
void ProximityInterestHandler::OnRulesHandlerRegistered(InterestManager* manager)
{
AZ_Assert(m_im == nullptr, "Handler is already registered with manager %p (%p)\n", m_im, manager);
AZ_Assert(m_rulesReplica == nullptr, "Rules replica is already created\n");
AZ_TracePrintf("GridMate", "Proximity interest handler is registered\n");
m_im = manager;
m_rm = m_im->GetReplicaManager();
m_rm->RegisterUserContext(AZ_CRC("ProximityInterestHandler", 0x3a90b3e4), this);
auto replica = Replica::CreateReplica("ProximityInterestHandlerRules");
m_rulesReplica = CreateAndAttachReplicaChunk<ProximityInterestChunk>(replica);
m_rm->AddPrimary(replica);
}
void ProximityInterestHandler::OnRulesHandlerUnregistered(InterestManager* manager)
{
(void)manager;
AZ_Assert(m_im == manager, "Handler was not registered with manager %p (%p)\n", manager, m_im);
AZ_TracePrintf("GridMate", "Proximity interest handler is unregistered\n");
m_rulesReplica = nullptr;
m_im = nullptr;
m_rm->UnregisterUserContext(AZ_CRC("ProximityInterestHandler", 0x3a90b3e4));
m_rm = nullptr;
for (auto& chunk : m_peerChunks)
{
chunk.second->m_interestHandler = nullptr;
}
m_peerChunks.clear();
ClearDirtyState();
DestroyAll();
m_resultCache.clear();
}
void ProximityInterestHandler::DestroyAll()
{
for (ProximityInterestRule* rule : m_localRules)
{
FreeRule(rule);
}
m_localRules.clear();
for (ProximityInterestAttribute* attr : m_attributes)
{
FreeAttribute(attr);
}
m_attributes.clear();
for (auto& removedRule : m_removedRules)
{
FreeRule(removedRule);
}
m_removedRules.clear();
for (auto& removedAttribute : m_removedAttributes)
{
FreeAttribute(removedAttribute);
}
m_removedAttributes.clear();
}
///////////////////////////////////////////////////////////////////////////
ProximityInterestHandler::~ProximityInterestHandler()
{
/*
* If a handler was registered with a InterestManager, then InterestManager ought to have called OnRulesHandlerUnregistered
* but this is a safety pre-caution.
*/
DestroyAll();
}
SpatialIndex::SpatialIndex()
{
m_tree.reset(aznew GridMate::BvDynamicTree());
}
void SpatialIndex::Remove(Node* node)
{
m_tree->Remove(node);
}
void SpatialIndex::Update(Node* node)
{
m_tree->Update(node);
}
SpatialIndex::Node* SpatialIndex::Insert(const AZ::Aabb& get, ProximityInterestAttribute* attribute)
{
return m_tree->Insert(get, attribute);
}
void SpatialIndex::Query(const AZ::Aabb& shape, NodeCollector& nodes)
{
m_tree->collideTV(m_tree->GetRoot(), shape, nodes);
}
}
@@ -1,314 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef GM_REPLICA_PROXIMITYINTERESTHANDLER_H
#define GM_REPLICA_PROXIMITYINTERESTHANDLER_H
#include <GridMate/Replica/RemoteProcedureCall.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <GridMate/Replica/Interest/RulesHandler.h>
#include <GridMate/Replica/Interest/BvDynamicTree.h>
#include <GridMate/Serialize/UtilityMarshal.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace GridMate
{
class ProximityInterestHandler;
class ProximityInterestAttribute;
/*
* Base interest
*/
class ProximityInterest
{
friend class ProximityInterestHandler;
public:
const AZ::Aabb& Get() const { return m_bbox; }
protected:
explicit ProximityInterest(ProximityInterestHandler* handler);
ProximityInterestHandler* m_handler;
AZ::Aabb m_bbox;
};
///////////////////////////////////////////////////////////////////////////
/*
* Proximity rule
*/
class ProximityInterestRule
: public InterestRule
, public ProximityInterest
{
friend class ProximityInterestHandler;
public:
using Ptr = AZStd::intrusive_ptr<ProximityInterestRule>;
GM_CLASS_ALLOCATOR(ProximityInterestRule);
void Set(const AZ::Aabb& bbox);
private:
// Intrusive ptr
template<class T>
friend struct AZStd::IntrusivePtrCountPolicy;
unsigned int m_refCount = 0;
AZ_FORCE_INLINE void add_ref() { ++m_refCount; }
AZ_FORCE_INLINE void release() { --m_refCount; if (!m_refCount) Destroy(); }
AZ_FORCE_INLINE bool IsDeleted() const { return m_refCount == 0; }
///////////////////////////////////////////////////////////////////////////
ProximityInterestRule(ProximityInterestHandler* handler, PeerId peerId, RuleNetworkId netId)
: InterestRule(peerId, netId)
, ProximityInterest(handler)
{}
void Destroy();
};
///////////////////////////////////////////////////////////////////////////
class SpatialIndex
{
public:
typedef Internal::DynamicTreeNode Node;
class NodeCollector
{
typedef AZStd::vector<ProximityInterestAttribute*> Type;
public:
void Process(const Internal::DynamicTreeNode* node)
{
m_nodes.push_back(reinterpret_cast<ProximityInterestAttribute*>(node->m_data));
}
const Type& GetNodes() const
{
return m_nodes;
}
private:
Type m_nodes;
};
SpatialIndex();
~SpatialIndex() = default;
AZ_FORCE_INLINE void Remove(Node* node);
AZ_FORCE_INLINE void Update(Node* node);
AZ_FORCE_INLINE Node* Insert(const AZ::Aabb& get, ProximityInterestAttribute* attribute);
AZ_FORCE_INLINE void Query(const AZ::Aabb& get, NodeCollector& nodes);
private:
AZStd::unique_ptr<BvDynamicTree> m_tree;
};
/*
* Proximity attribute
*/
class ProximityInterestAttribute
: public InterestAttribute
, public ProximityInterest
{
friend class ProximityInterestHandler;
template<class T> friend class InterestPtr;
public:
using Ptr = AZStd::intrusive_ptr<ProximityInterestAttribute>;
GM_CLASS_ALLOCATOR(ProximityInterestAttribute);
void Set(const AZ::Aabb& bbox);
private:
// Intrusive ptr
template<class T>
friend struct AZStd::IntrusivePtrCountPolicy;
unsigned int m_refCount = 0;
AZ_FORCE_INLINE void add_ref() { ++m_refCount; }
AZ_FORCE_INLINE void release() { Destroy(); }
AZ_FORCE_INLINE bool IsDeleted() const { return m_refCount == 0; }
///////////////////////////////////////////////////////////////////////////
ProximityInterestAttribute(ProximityInterestHandler* handler, ReplicaId repId)
: InterestAttribute(repId)
, ProximityInterest(handler)
, m_worldNode(nullptr)
{}
void Destroy();
void SetNode(SpatialIndex::Node* node) { m_worldNode = node; }
SpatialIndex::Node* GetNode() const { return m_worldNode; }
SpatialIndex::Node* m_worldNode; ///< non-owning pointer
};
///////////////////////////////////////////////////////////////////////////
class ProximityInterestChunk
: public ReplicaChunk
{
public:
GM_CLASS_ALLOCATOR(ProximityInterestChunk);
// ReplicaChunk
typedef AZStd::intrusive_ptr<ProximityInterestChunk> Ptr;
bool IsReplicaMigratable() override { return false; }
bool IsBroadcast() override { return true; }
static const char* GetChunkName() { return "ProximityInterestChunk"; }
ProximityInterestChunk()
: AddRuleRpc("AddRule")
, RemoveRuleRpc("RemoveRule")
, UpdateRuleRpc("UpdateRule")
, AddRuleForPeerRpc("AddRuleForPeerRpc")
, m_interestHandler(nullptr)
{
}
void OnReplicaActivate(const ReplicaContext& rc) override;
void OnReplicaDeactivate(const ReplicaContext& rc) override;
bool AddRuleFn(RuleNetworkId netId, AZ::Aabb bbox, const RpcContext& ctx);
bool RemoveRuleFn(RuleNetworkId netId, const RpcContext&);
bool UpdateRuleFn(RuleNetworkId netId, AZ::Aabb bbox, const RpcContext&);
bool AddRuleForPeerFn(RuleNetworkId netId, PeerId peerId, AZ::Aabb bbox, const RpcContext&);
Rpc<RpcArg<RuleNetworkId>, RpcArg<AZ::Aabb>>::BindInterface<ProximityInterestChunk, &ProximityInterestChunk::AddRuleFn> AddRuleRpc;
Rpc<RpcArg<RuleNetworkId>>::BindInterface<ProximityInterestChunk, &ProximityInterestChunk::RemoveRuleFn> RemoveRuleRpc;
Rpc<RpcArg<RuleNetworkId>, RpcArg<AZ::Aabb>>::BindInterface<ProximityInterestChunk, &ProximityInterestChunk::UpdateRuleFn> UpdateRuleRpc;
Rpc<RpcArg<RuleNetworkId>, RpcArg<PeerId>, RpcArg<AZ::Aabb>>::BindInterface<ProximityInterestChunk, &ProximityInterestChunk::AddRuleForPeerFn> AddRuleForPeerRpc;
unordered_map<RuleNetworkId, ProximityInterestRule::Ptr> m_rules;
ProximityInterestHandler* m_interestHandler;
};
/*
* Rules handler
*/
class ProximityInterestHandler
: public BaseRulesHandler
{
friend class ProximityInterestRule;
friend class ProximityInterestAttribute;
friend class ProximityInterestChunk;
public:
typedef unordered_set<ProximityInterestAttribute*> AttributeSet;
typedef unordered_set<ProximityInterestRule*> RuleSet;
GM_CLASS_ALLOCATOR(ProximityInterestHandler);
ProximityInterestHandler();
~ProximityInterestHandler();
/*
* Creates new proximity rule and binds it to the peer.
* Note: the lifetime of the created rule is tied to the lifetime of this handler.
*/
ProximityInterestRule::Ptr CreateRule(PeerId peerId);
/*
* Creates new proximity attribute and binds it to the replica.
* Note: the lifetime of the created attribute is tied to the lifetime of this handler.
*/
ProximityInterestAttribute::Ptr CreateAttribute(ReplicaId replicaId);
// Calculates rules and attributes matches
void Update() override;
// Returns last recalculated results
const InterestMatchResult& GetLastResult() override;
// Returns the manager it's bound to
InterestManager* GetManager() override { return m_im; }
// Rules that this handler is aware of
const RuleSet& GetLocalRules() const { return m_localRules; }
private:
// BaseRulesHandler
void OnRulesHandlerRegistered(InterestManager* manager) override;
void OnRulesHandlerUnregistered(InterestManager* manager) override;
void DestroyRule(ProximityInterestRule* rule);
void FreeRule(ProximityInterestRule* rule);
void UpdateRule(ProximityInterestRule* rule);
void DestroyAttribute(ProximityInterestAttribute* attrib);
void FreeAttribute(ProximityInterestAttribute* attrib);
void UpdateAttribute(ProximityInterestAttribute* attrib);
void OnNewRulesChunk(ProximityInterestChunk* chunk, ReplicaPeer* peer);
void OnDeleteRulesChunk(ProximityInterestChunk* chunk, ReplicaPeer* peer);
RuleNetworkId GetNewRuleNetId();
ProximityInterestChunk* FindRulesChunkByPeerId(PeerId peerId);
void DestroyAll();
InterestManager* m_im;
ReplicaManager* m_rm;
AZ::u32 m_lastRuleNetId;
unordered_map<PeerId, ProximityInterestChunk*> m_peerChunks;
RuleSet m_localRules;
RuleSet m_removedRules;
RuleSet m_dirtyRules;
AttributeSet m_attributes;
AttributeSet m_removedAttributes;
AttributeSet m_dirtyAttributes;
ProximityInterestChunk* m_rulesReplica;
// collection of all known attributes
AZStd::unique_ptr<SpatialIndex> m_attributeWorld;
InterestMatchResult m_resultCache;
///////////////////////////////////////////////////////////////////////////////////////////////////
// internal processing helpers
AZ_FORCE_INLINE RuleSet& GetAffectedRules();
AZ_FORCE_INLINE void GetAttributesWithinRule(ProximityInterestRule* rule, SpatialIndex::NodeCollector& nodes);
AZ_FORCE_INLINE void ClearDirtyState();
AZ_FORCE_INLINE void CreateAndInsertIntoSpatialStructure(ProximityInterestAttribute* attribute);
AZ_FORCE_INLINE void RemoveFromSpatialStructure(ProximityInterestAttribute* attribute);
AZ_FORCE_INLINE void CreateAndInsertIntoSpatialStructure(ProximityInterestRule* rule);
void UpdateInternal(InterestMatchResult& result);
void CheckChangesForRule(ProximityInterestRule* rule, InterestMatchResult& result);
void MarkAttributesDirtyInRule(ProximityInterestRule* rule);
static bool HasSamePeers(const InterestPeerSet& one, const InterestPeerSet& another);
void ProduceChanges(const InterestMatchResult& before, const InterestMatchResult& after);
InterestMatchResult m_lastResult;
///////////////////////////////////////////////////////////////////////////////////////////////////
};
///////////////////////////////////////////////////////////////////////////
}
#endif // GM_REPLICA_PROXIMITYINTERESTHANDLER_H
@@ -100,14 +100,10 @@ set(FILES
Replica/Tasks/ReplicaPriorityPolicy.h
Replica/Interest/BitmaskInterestHandler.cpp
Replica/Interest/BitmaskInterestHandler.h
Replica/Interest/ProximityInterestHandler.cpp
Replica/Interest/ProximityInterestHandler.h
Replica/Interest/InterestDefs.h
Replica/Interest/InterestManager.cpp
Replica/Interest/InterestManager.h
Replica/Interest/InterestQueryResult.h
Replica/Interest/BvDynamicTree.cpp
Replica/Interest/BvDynamicTree.h
Replica/Interest/RulesHandler.h
Serialize/Buffer.cpp
Serialize/Buffer.h
File diff suppressed because it is too large Load Diff
@@ -24,5 +24,4 @@ set(FILES
StreamSocketDriverTests.cpp
CarrierStreamSocketDriverTests.cpp
Carrier.cpp
Interest.cpp
)
+37 -35
View File
@@ -9,7 +9,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
@@ -27,40 +27,42 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
AZ::AzFramework
)
ly_add_target(
NAME ProcessLaunchTest EXECUTABLE
NAMESPACE AZ
FILES_CMAKE
process_launch_test_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
AZ::AzFramework
)
if(PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target(
NAME ProcessLaunchTest EXECUTABLE
NAMESPACE AZ
FILES_CMAKE
process_launch_test_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
AZ::AzFramework
)
ly_add_target(
NAME Framework.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE AZ
FILES_CMAKE
frameworktests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
${pal_dir}
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::AzToolsFramework
AZ::AzTestShared
AZ::AzFrameworkTestShared
RUNTIME_DEPENDENCIES
AZ::ProcessLaunchTest
)
ly_add_googletest(
NAME AZ::Framework.Tests
)
ly_add_target(
NAME Framework.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE AZ
FILES_CMAKE
frameworktests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
${pal_dir}
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::AzToolsFramework
AZ::AzTestShared
AZ::AzFrameworkTestShared
RUNTIME_DEPENDENCIES
AZ::ProcessLaunchTest
)
ly_add_googletest(
NAME AZ::Framework.Tests
)
endif()
endif()
@@ -0,0 +1,84 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/UnitTest/UnitTest.h>
#include <gmock/gmock.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
#include <AzFramework/Spawnable/SpawnableEntitiesManager.h>
namespace AzFramework
{
class MockSpawnableEntitiesInterface;
using NiceSpawnableEntitiesInterfaceMock = ::testing::NiceMock<MockSpawnableEntitiesInterface>;
class MockSpawnableEntitiesInterface : public SpawnableEntitiesDefinition
{
public:
AZ_RTTI(MockSpawnableEntitiesInterface, "{2A20FF73-C445-4F32-ABB9-5CF0A5778404}", SpawnableEntitiesDefinition);
MockSpawnableEntitiesInterface()
{
AZ::Interface<SpawnableEntitiesDefinition>::Register(this);
}
virtual ~MockSpawnableEntitiesInterface()
{
AZ::Interface<SpawnableEntitiesDefinition>::Unregister(this);
}
MOCK_METHOD2(SpawnAllEntities, void(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs));
MOCK_METHOD3(
SpawnEntities,
void(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs));
MOCK_METHOD2(DespawnAllEntities, void(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs));
MOCK_METHOD3(
ReloadSpawnable,
void(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs));
MOCK_METHOD3(
ListEntities, void(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs));
MOCK_METHOD3(
ListIndicesAndEntities,
void(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs));
MOCK_METHOD3(
ClaimEntities,
void(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs));
MOCK_METHOD3(Barrier, void(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs));
MOCK_METHOD1(CreateTicket, AZStd::pair<EntitySpawnTicket::Id, void*>(AZ::Data::Asset<Spawnable>&& spawnable));
MOCK_METHOD1(DestroyTicket, void(void* ticket));
/** Installs some default result values for the above functions.
* Note that you can always override these in scope of your test by adding additional ON_CALL / EXPECT_CALL
* statements in the body of your test or after calling this function, and yours will take precedence.
**/
static void InstallDefaultReturns(NiceSpawnableEntitiesInterfaceMock& target)
{
using namespace ::testing;
// The ID and pointer are completely arbitrary, they just need to both be non-zero to look like a valid ticket.
constexpr EntitySpawnTicket::Id ticketId(1);
static int ticketPayload = 0;
ON_CALL(target, CreateTicket(_)).WillByDefault(
Return(AZStd::make_pair<AzFramework::EntitySpawnTicket::Id, void*>(ticketId, &ticketPayload)));
}
};
} // namespace AzFramework
@@ -10,6 +10,7 @@
#
set(FILES
Mocks/MockSpawnableEntitiesInterface.h
Utils/Utils.h
Utils/Utils.cpp
)
@@ -26,7 +26,7 @@ foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJEC
message(FATAL_ERROR "The specified project path of ${project_real_path} does not contain a project.json file")
else()
# Add the project_name to global LY_PROJECTS_TARGET_NAME property
file(READ "${project_real_path}/project.json" project_json)
ly_file_read("${project_real_path}/project.json" project_json)
string(JSON project_name ERROR_VARIABLE json_error GET ${project_json} "project_name")
if(json_error)
message(FATAL_ERROR "There is an error reading the \"project_name\" key from the '${project_real_path}/project.json' file: ${json_error}")
-1
View File
@@ -104,7 +104,6 @@ ly_add_target(
3rdParty::Qt::Gui
3rdParty::Qt::Widgets
3rdParty::Qt::Concurrent
3rdParty::Qt::WebEngineWidgets
3rdParty::tiff
3rdParty::squish-ccr
3rdParty::zlib
+1 -8
View File
@@ -4060,17 +4060,10 @@ void CCryEditApp::SetEditorWindowTitle(QString sTitleStr, QString sPreTitleStr,
{
if (MainWindow::instance() || m_pConsoleDialog)
{
QString platform = "";
#ifdef WIN64
platform = "[x64]";
#else
platform = "[x86]";
#endif //WIN64
if (sTitleStr.isEmpty())
{
sTitleStr = QObject::tr("Open 3D Engine Editor Beta %1 - Build %2").arg(platform).arg(LY_BUILD);
sTitleStr = QObject::tr("O3DE Editor [Developer Preview]");
}
if (!sPreTitleStr.isEmpty())
@@ -28,8 +28,6 @@
#define EDITORPREFS_EVENTVALTOGGLE "operation"
#define UNDOSLICESAVE_VALON "UndoSliceSaveValueOn"
#define UNDOSLICESAVE_VALOFF "UndoSliceSaveValueOff"
#define EDITORUI10_ENABLED "EditorUI10On"
#define EDITORUI10_DISABLED "EditorUI10Off"
void CEditorPreferencesPage_General::Reflect(AZ::SerializeContext& serialize)
{
@@ -45,8 +43,7 @@ void CEditorPreferencesPage_General::Reflect(AZ::SerializeContext& serialize)
->Field("StylusMode", &GeneralSettings::m_stylusMode)
->Field("ShowNews", &GeneralSettings::m_bShowNews)
->Field("EnableSceneInspector", &GeneralSettings::m_enableSceneInspector)
->Field("RestoreViewportCamera", &GeneralSettings::m_restoreViewportCamera)
->Field("PrefabSystem", &GeneralSettings::m_enablePrefabSystem);
->Field("RestoreViewportCamera", &GeneralSettings::m_restoreViewportCamera);
serialize.Class<Messaging>()
->Version(2)
@@ -94,8 +91,7 @@ void CEditorPreferencesPage_General::Reflect(AZ::SerializeContext& serialize)
->EnumAttribute(AzQtComponents::ToolBar::ToolBarIconSize::IconLarge, "Large")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_stylusMode, "Stylus Mode", "Stylus Mode for tablets and other pointing devices")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_restoreViewportCamera, EditorPreferencesGeneralRestoreViewportCameraSettingName, "Keep the original editor viewport transform when exiting game mode.")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_enableSceneInspector, "Enable Scene Inspector (EXPERIMENTAL)", "Enable the option to inspect the internal data loaded from scene files like .fbx. This is an experimental feature. Restart the Scene Settings if the option is not visible under the Help menu.")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_enablePrefabSystem, "Enable Prefab System (EXPERIMENTAL)", "Enable this option to preview Open 3D Engine's new prefab system. Enabling this setting removes slice support for level entities; you will need to restart the Editor for the change to take effect.");
->DataElement(AZ::Edit::UIHandlers::CheckBox, &GeneralSettings::m_enableSceneInspector, "Enable Scene Inspector (EXPERIMENTAL)", "Enable the option to inspect the internal data loaded from scene files like .fbx. This is an experimental feature. Restart the Scene Settings if the option is not visible under the Help menu.");
editContext->Class<Messaging>("Messaging", "")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Messaging::m_showDashboard, "Show Welcome to Open 3D Engine at startup", "Show Welcome to Open 3D Engine at startup")
@@ -159,8 +155,6 @@ void CEditorPreferencesPage_General::OnApply()
gSettings.restoreViewportCamera = m_generalSettings.m_restoreViewportCamera;
gSettings.enableSceneInspector = m_generalSettings.m_enableSceneInspector;
gSettings.prefabSystem = m_generalSettings.m_enablePrefabSystem;
if (static_cast<int>(m_generalSettings.m_toolbarIconSize) != gSettings.gui.nToolbarIconSize)
{
gSettings.gui.nToolbarIconSize = static_cast<int>(m_generalSettings.m_toolbarIconSize);
@@ -178,16 +172,6 @@ void CEditorPreferencesPage_General::OnApply()
//slices
gSettings.sliceSettings.dynamicByDefault = m_sliceSettings.m_slicesDynamicByDefault;
// if the user enabled/disabled the prefab context - notify them that a restart
// is required in order to see the effect of the change
if (gSettings.prefabSystem != m_generalSettings.m_enablePrefabSystemInitialValue)
{
QMessageBox::warning(
AzToolsFramework::GetActiveWindow(), QObject::tr("Restart required"),
QObject::tr("Restart the Editor in order for the Prefab/Slice system changes to take effect.")
);
}
}
void CEditorPreferencesPage_General::InitializeSettings()
@@ -202,8 +186,6 @@ void CEditorPreferencesPage_General::InitializeSettings()
m_generalSettings.m_stylusMode = gSettings.stylusMode;
m_generalSettings.m_restoreViewportCamera = gSettings.restoreViewportCamera;
m_generalSettings.m_enableSceneInspector = gSettings.enableSceneInspector;
m_generalSettings.m_enablePrefabSystem = gSettings.prefabSystem;
m_generalSettings.m_enablePrefabSystemInitialValue = gSettings.prefabSystem;
m_generalSettings.m_toolbarIconSize = static_cast<AzQtComponents::ToolBar::ToolBarIconSize>(gSettings.gui.nToolbarIconSize);
@@ -58,10 +58,6 @@ private:
bool m_restoreViewportCamera;
bool m_bShowNews;
bool m_enableSceneInspector;
bool m_enablePrefabSystem;
// Only used to tell if the user has changed this value since it requires a restart
bool m_enablePrefabSystemInitialValue;
};
struct Messaging
+3 -3
View File
@@ -33,13 +33,13 @@ BEGIN
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Amazon.com, Inc."
VALUE "FileDescription", "Lumberyard Editor"
VALUE "CompanyName", "Open 3D Foundation"
VALUE "FileDescription", "O3DE Editor"
VALUE "FileVersion", "0.1.0.1"
VALUE "InternalName", "Editor"
VALUE "LegalCopyright", "Portions of this file Copyright (c) Amazon.com, Inc. or its affiliates. All Rights Reserved. Original file Copyright (c) Crytek GMBH. Used under license by Amazon.com, Inc. and its affiliates."
VALUE "OriginalFilename", "Editor.exe"
VALUE "ProductName", "Lumberyard Editor"
VALUE "ProductName", "O3DE Editor"
VALUE "ProductVersion", "0.1.0.1"
END
END
-460
View File
@@ -1,460 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include "EditorDefs.h"
#include "ImageHDR.h"
// Editor
#include "Util/Image.h"
// We need globals because of the callbacks (they don't allow us to pass state)
static CryMutex globalFileMutex;
static size_t globalFileBufferOffset = 0;
static size_t globalFileBufferSize = 0;
static char* fgets(char* _Buf, [[maybe_unused]] int _MaxCount, CCryFile* _File)
{
while (globalFileBufferOffset < globalFileBufferSize)
{
char chr;
_File->ReadRaw(&chr, 1);
globalFileBufferOffset++;
*_Buf++ = chr;
if (chr == '\n')
{
break;
}
}
*_Buf = '\0';
return _Buf;
}
static size_t fread(void* _DstBuf, size_t _ElementSize, size_t _Count, CCryFile* _File)
{
size_t cpy = min(_ElementSize * _Count, globalFileBufferSize - globalFileBufferOffset);
_File->ReadRaw(_DstBuf, cpy);
globalFileBufferOffset += cpy;
return cpy;
}
/* THIS CODE CARRIES NO GUARANTEE OF USABILITY OR FITNESS FOR ANY PURPOSE.
* WHILE THE AUTHORS HAVE TRIED TO ENSURE THE PROGRAM WORKS CORRECTLY,
* IT IS STRICTLY USE AT YOUR OWN RISK. */
/* utility for reading and writing Ward's rgbe image format.
See rgbe.txt file for more details.
*/
#include <stdio.h>
typedef struct
{
int valid; /* indicate which fields are valid */
char programtype[16]; /* listed at beginning of file to identify it
* after "#?". defaults to "RGBE" */
float gamma; /* image has already been gamma corrected with
* given gamma. defaults to 1.0 (no correction) */
float exposure; /* a value of 1.0 in an image corresponds to
* <exposure> watts/steradian/m^2.
* defaults to 1.0 */
char instructions[512];
} rgbe_header_info;
/* flags indicating which fields in an rgbe_header_info are valid */
#define RGBE_VALID_PROGRAMTYPE 0x01
#define RGBE_VALID_GAMMA 0x02
#define RGBE_VALID_EXPOSURE 0x04
#define RGBE_VALID_INSTRUCTIONS 0x08
/* return codes for rgbe routines */
#define RGBE_RETURN_SUCCESS 0
#define RGBE_RETURN_FAILURE -1
/* read or write headers */
/* you may set rgbe_header_info to null if you want to */
int RGBE_ReadHeader(CCryFile* fp, uint32* width, uint32* height, rgbe_header_info* info);
/* read or write pixels */
/* can read or write pixels in chunks of any size including single pixels*/
int RGBE_ReadPixels(CCryFile* fp, float* data, int numpixels);
/* read or write run length encoded files */
/* must be called to read or write whole scanlines */
int RGBE_ReadPixels_RLE(CCryFile* fp, float* data, uint32 scanline_width,
uint32 num_scanlines);
/* THIS CODE CARRIES NO GUARANTEE OF USABILITY OR FITNESS FOR ANY PURPOSE.
* WHILE THE AUTHORS HAVE TRIED TO ENSURE THE PROGRAM WORKS CORRECTLY,
* IT IS STRICTLY USE AT YOUR OWN RISK. */
#include <math.h>
#include <string.h>
#include <ctype.h>
/* This file contains code to read and write four byte rgbe file format
developed by Greg Ward. It handles the conversions between rgbe and
pixels consisting of floats. The data is assumed to be an array of floats.
By default there are three floats per pixel in the order red, green, blue.
(RGBE_DATA_??? values control this.) Only the mimimal header reading and
writing is implemented. Each routine does error checking and will return
a status value as defined below. This code is intended as a skeleton so
feel free to modify it to suit your needs.
(Place notice here if you modified the code.)
posted to http://www.graphics.cornell.edu/~bjw/
written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95
based on code written by Greg Ward
*/
#ifndef INLINE
#ifdef _CPLUSPLUS
/* define if your compiler understands inline commands */
#define INLINE inline
#else
#define INLINE
#endif
#endif
/* offsets to red, green, and blue components in a data (float) pixel */
#define RGBE_DATA_RED 0
#define RGBE_DATA_GREEN 1
#define RGBE_DATA_BLUE 2
#define RGBE_DATA_ALPHA 3
/* number of floats per pixel */
#define RGBE_DATA_SIZE 4
enum rgbe_error_codes
{
rgbe_read_error,
rgbe_write_error,
rgbe_format_error,
rgbe_memory_error,
};
/* default error routine. change this to change error handling */
static int rgbe_error(int rgbe_error_code, const char* msg)
{
switch (rgbe_error_code)
{
case rgbe_read_error:
CLogFile::FormatLine("RGBE read error");
break;
case rgbe_write_error:
CLogFile::FormatLine("RGBE write error");
break;
case rgbe_format_error:
CLogFile::FormatLine("RGBE bad file format: %s\n", msg);
break;
default:
case rgbe_memory_error:
CLogFile::FormatLine("RGBE error: %s\n", msg);
}
return RGBE_RETURN_FAILURE;
}
/* standard conversion from rgbe to float pixels */
/* note: Ward uses ldexp(col+0.5,exp-(128+8)). However we wanted pixels */
/* in the range [0,1] to map back into the range [0,1]. */
static INLINE void
rgbe2type(char* red, char* green, char* blue, unsigned char rgbe[4])
{
float f;
if (rgbe[3]) /*nonzero pixel*/
{
f = ldexp(1.0f, rgbe[3] - (int)(128 + 8)) * 255.0f;
*red = (unsigned char) max(0.0f, min(rgbe[0] * f, 255.0f));
*green = (unsigned char) max(0.0f, min(rgbe[1] * f, 255.0f));
*blue = (unsigned char) max(0.0f, min(rgbe[2] * f, 255.0f));
}
else
{
*red = *green = *blue = 0;
}
}
/* minimal header reading. modify if you want to parse more information */
int RGBE_ReadHeader(CCryFile* fp, uint32* width, uint32* height, rgbe_header_info* info)
{
char buf[512];
int found_format;
float tempf;
int i;
found_format = 0;
if (info)
{
info->valid = 0;
info->programtype[0] = 0;
info->gamma = info->exposure = 1.0;
}
if (fgets(buf, sizeof(buf) / sizeof(buf[0]), fp) == NULL)
{
return rgbe_error(rgbe_read_error, NULL);
}
if ((buf[0] != '#') || (buf[1] != '?'))
{
/* if you want to require the magic token then uncomment the next line */
/*return rgbe_error(rgbe_format_error,"bad initial token"); */
}
else if (info)
{
info->valid |= RGBE_VALID_PROGRAMTYPE;
for (i = 0; i < sizeof(info->programtype) - 1; i++)
{
if ((buf[i + 2] == 0) || isspace(buf[i + 2]))
{
break;
}
info->programtype[i] = buf[i + 2];
}
info->programtype[i] = 0;
if (fgets(buf, sizeof(buf) / sizeof(buf[0]), fp) == 0)
{
return rgbe_error(rgbe_read_error, NULL);
}
}
for (;; )
{
if ((buf[0] == 0) || (buf[0] == '\n'))
{
return rgbe_error(rgbe_format_error, "no FORMAT specifier found");
}
else if (strcmp(buf, "FORMAT=32-bit_rle_rgbe\n") == 0)
{
break; /* format found so break out of loop */
}
else if (info && (azsscanf(buf, "GAMMA=%g", &tempf) == 1))
{
info->gamma = tempf;
info->valid |= RGBE_VALID_GAMMA;
}
else if (info && (azsscanf(buf, "EXPOSURE=%g", &tempf) == 1))
{
info->exposure = tempf;
info->valid |= RGBE_VALID_EXPOSURE;
}
else if (info && (!strncmp(buf, "INSTRUCTIONS=", 13)))
{
info->valid |= RGBE_VALID_INSTRUCTIONS;
for (i = 0; i < sizeof(info->instructions) - 1; i++)
{
if ((buf[i + 13] == 0) || isspace(buf[i + 13]))
{
break;
}
info->instructions[i] = buf[i + 13];
}
}
if (fgets(buf, sizeof(buf) / sizeof(buf[0]), fp) == 0)
{
return rgbe_error(rgbe_read_error, NULL);
}
}
if (fgets(buf, sizeof(buf) / sizeof(buf[0]), fp) == 0)
{
return rgbe_error(rgbe_read_error, NULL);
}
if (strcmp(buf, "\n") != 0)
{
return rgbe_error(rgbe_format_error,
"missing blank line after FORMAT specifier");
}
if (fgets(buf, sizeof(buf) / sizeof(buf[0]), fp) == 0)
{
return rgbe_error(rgbe_read_error, NULL);
}
if (azsscanf(buf, "-Y %d +X %d", height, width) < 2)
{
return rgbe_error(rgbe_format_error, "missing image size specifier");
}
return RGBE_RETURN_SUCCESS;
}
/* simple read routine. will not correctly handle run length encoding */
int RGBE_ReadPixels(CCryFile* fp, char* data, int numpixels)
{
unsigned char rgbe[4];
while (numpixels-- > 0)
{
if (fread(rgbe, sizeof(rgbe), 1, fp) < 1)
{
return rgbe_error(rgbe_read_error, NULL);
}
rgbe2type(&data[RGBE_DATA_RED], &data[RGBE_DATA_GREEN],
&data[RGBE_DATA_BLUE], rgbe);
data[RGBE_DATA_ALPHA] = 0.0f;
data += RGBE_DATA_SIZE;
}
return RGBE_RETURN_SUCCESS;
}
int RGBE_ReadPixels_RLE(CCryFile* fp, char* data, uint32 scanline_width,
uint32 num_scanlines)
{
unsigned char rgbe[4], * scanline_buffer, * ptr, * ptr_end;
int i, count;
unsigned char buf[2];
if ((scanline_width < 8) || (scanline_width > 0x7fff))
{
/* run length encoding is not allowed so read flat*/
return RGBE_ReadPixels(fp, data, scanline_width * num_scanlines);
}
scanline_buffer = NULL;
/* read in each successive scanline */
while (num_scanlines > 0)
{
if (fread(rgbe, sizeof(rgbe), 1, fp) < 1)
{
free(scanline_buffer);
return rgbe_error(rgbe_read_error, NULL);
}
if ((rgbe[0] != 2) || (rgbe[1] != 2) || (rgbe[2] & 0x80))
{
/* this file is not run length encoded */
rgbe2type(&data[0], &data[1], &data[2], rgbe);
data += RGBE_DATA_SIZE;
free(scanline_buffer);
return RGBE_ReadPixels(fp, data, scanline_width * num_scanlines - 1);
}
if ((((int)rgbe[2]) << 8 | rgbe[3]) != scanline_width)
{
free(scanline_buffer);
return rgbe_error(rgbe_format_error, "wrong scanline width");
}
if (scanline_buffer == NULL)
{
scanline_buffer = (unsigned char*)
malloc(sizeof(unsigned char) * 4 * scanline_width);
}
if (scanline_buffer == NULL)
{
return rgbe_error(rgbe_memory_error, "unable to allocate buffer space");
}
ptr = &scanline_buffer[0];
/* read each of the four channels for the scanline into the buffer */
for (i = 0; i < 4; i++)
{
ptr_end = &scanline_buffer[(i + 1) * scanline_width];
while (ptr < ptr_end)
{
if (fread(buf, sizeof(buf[0]) * 2, 1, fp) < 1)
{
free(scanline_buffer);
return rgbe_error(rgbe_read_error, NULL);
}
if (buf[0] > 128)
{
/* a run of the same value */
count = buf[0] - 128;
if ((count == 0) || (count > ptr_end - ptr))
{
free(scanline_buffer);
return rgbe_error(rgbe_format_error, "bad scanline data");
}
while (count-- > 0)
{
*ptr++ = buf[1];
}
}
else
{
/* a non-run */
count = buf[0];
if ((count == 0) || (count > ptr_end - ptr))
{
free(scanline_buffer);
return rgbe_error(rgbe_format_error, "bad scanline data");
}
*ptr++ = buf[1];
if (--count > 0)
{
if (fread(ptr, sizeof(*ptr) * count, 1, fp) < 1)
{
free(scanline_buffer);
return rgbe_error(rgbe_read_error, NULL);
}
ptr += count;
}
}
}
}
/* now convert data from buffer into floats */
for (i = 0; i < scanline_width; i++)
{
rgbe[0] = scanline_buffer[i];
rgbe[1] = scanline_buffer[i + scanline_width];
rgbe[2] = scanline_buffer[i + 2 * scanline_width];
rgbe[3] = scanline_buffer[i + 3 * scanline_width];
rgbe2type(&data[RGBE_DATA_RED], &data[RGBE_DATA_GREEN],
&data[RGBE_DATA_BLUE], rgbe);
data[RGBE_DATA_ALPHA] = 0.0f;
data += RGBE_DATA_SIZE;
}
num_scanlines--;
}
free(scanline_buffer);
return RGBE_RETURN_SUCCESS;
}
///////////////////////////////////////////////////////////////////////////////////
bool CImageHDR::Load(const QString& fileName, CImageEx& outImage)
{
CCryFile file;
if (!file.Open(fileName.toUtf8().data(), "rb"))
{
CLogFile::FormatLine("File not found %s", fileName.toUtf8().data());
return false;
}
// We use some global variables in callbacks, so we must
// prevent multithread access to the data
CryAutoLock<CryMutex> tifAutoLock(globalFileMutex);
globalFileBufferSize = file.GetLength();
globalFileBufferOffset = 0;
bool bRet = false;
uint32 dwWidth, dwHeight;
rgbe_header_info info;
if (RGBE_RETURN_SUCCESS == RGBE_ReadHeader(&file, &dwWidth, &dwHeight, &info))
{
if (outImage.Allocate(dwWidth, dwHeight))
{
char* pDst = (char*)outImage.GetData();
if (RGBE_RETURN_SUCCESS == RGBE_ReadPixels_RLE(&file, (char*)pDst, dwWidth, dwHeight))
{
bRet = true;
}
}
}
if (!bRet)
{
outImage.Detach();
}
return bRet;
}
-22
View File
@@ -1,22 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#pragma once
class CImageEx;
class CImageHDR
{
public:
bool Load(const QString& fileName, CImageEx& outImage);
};
-5
View File
@@ -21,7 +21,6 @@
// Editor
#include "Util/ImageGif.h"
#include "Util/ImageTIF.h"
#include "Util/ImageHDR.h"
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::Save(const QString& strFileName, CImageEx& inImage)
@@ -275,10 +274,6 @@ bool CImageUtil::LoadImage(const QString& fileName, CImageEx& image, bool* pQual
{
return CImageUtil::Load(fileName, image);
}
else if (azstricmp(ext, ".hdr") == 0)
{
return CImageHDR().Load(fileName, image);
}
else
{
return CImageUtil::Load(fileName, image);
@@ -737,8 +737,6 @@ set(FILES
Util/GeometryUtil.cpp
Util/GuidUtil.cpp
Util/GuidUtil.h
Util/ImageHDR.cpp
Util/ImageHDR.h
Util/IObservable.h
Util/IndexedFiles.cpp
Util/IndexedFiles.h
@@ -10,5 +10,6 @@
"version_number" : 1,
"version_name" : "1.0.0.0",
"orientation" : "landscape"
}
},
"engine" : "o3de"
}
@@ -63,7 +63,7 @@ namespace AssetProcessor
}
AZ::IO::Path fullPath = AZ::IO::Path(scanFolder.m_scanFolder, AZ::IO::PosixPathSeparator) / source.m_sourceName;
AZ::IO::Path fullPath = AZ::IO::Path(scanFolder.m_scanFolder) / source.m_sourceName;
// It's common for Open 3D Engine game projects and scan folders to be in a subfolder
// of the engine install. To improve readability of the source files, strip out
@@ -74,7 +74,7 @@ namespace AssetProcessor
}
if (m_assetRootSet)
{
AzFramework::StringFunc::Replace(fullPath.Native(), m_assetRoot.absolutePath().toUtf8(), "");
fullPath = fullPath.LexicallyProximate(m_assetRoot.absolutePath().toUtf8().constData());
}
if (fullPath.empty())
@@ -88,11 +88,12 @@ namespace AssetProcessor
QModelIndex newIndicesStart;
AssetTreeItem* parentItem = m_root.get();
AZ::IO::Path currentFullFolderPath;
const AZ::IO::PathView filename = fullPath.Filename();
const AZ::IO::PathView fullPathWithoutFilename = fullPath.RemoveFilename();
// Use posix path separator for each child item
AZ::IO::Path currentFullFolderPath(AZ::IO::PosixPathSeparator);
const AZ::IO::FixedMaxPath filename = fullPath.Filename();
fullPath.RemoveFilename();
AZStd::fixed_string<AZ::IO::MaxPathLength> currentPath;
for (auto pathIt = fullPathWithoutFilename.begin(); pathIt != fullPathWithoutFilename.end(); ++pathIt)
for (auto pathIt = fullPath.begin(); pathIt != fullPath.end(); ++pathIt)
{
currentPath = pathIt->FixedMaxPathString();
currentFullFolderPath /= currentPath;
@@ -77,6 +77,7 @@ namespace O3DE::ProjectManager
return ProjectManagerScreen::CreateProject;
}
// Called when pressing "Create New Project"
void CreateProjectCtrl::NotifyCurrentScreen()
{
ScreenWidget* currentScreen = reinterpret_cast<ScreenWidget*>(m_stack->currentWidget());
@@ -84,6 +85,11 @@ namespace O3DE::ProjectManager
{
currentScreen->NotifyCurrentScreen();
}
// Gather the gems from the project template. When we will have multiple project templates, we need to re-gather them
// on changing the template and let the user know that any further changes on top of the template will be lost.
QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath();
m_gemCatalogScreen->ReinitForProject(projectTemplatePath + "/Template", /*isNewProject=*/true);
}
void CreateProjectCtrl::HandleBackButton()
@@ -151,9 +157,6 @@ namespace O3DE::ProjectManager
{
m_stack->setCurrentIndex(m_stack->currentIndex() + 1);
QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath();
m_gemCatalogScreen->ReinitForProject(projectTemplatePath + "/Template", /*isNewProject=*/true);
Update();
}
else
@@ -89,17 +89,18 @@ namespace O3DE::ProjectManager
return ProjectManagerScreen::UpdateProject;
}
// Called when pressing "Edit Project Settings..."
void UpdateProjectCtrl::NotifyCurrentScreen()
{
m_stack->setCurrentIndex(ScreenOrder::Settings);
Update();
// Gather the available gems that will be shown in the gem catalog.
m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/false);
}
void UpdateProjectCtrl::HandleGemsButton()
{
// The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog.
m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/false);
m_stack->setCurrentWidget(m_gemCatalogScreen);
Update();
}