Merge pull request #37 from aws-lumberyard-dev/hultonha_LYN-2528_whitebox_prefab

Add Json serialization support for ByteStream

Fixes serialization issue with the White Box component when Prefabs are enabled.
This commit is contained in:
Tom Hulton-Harrop
2021-04-16 18:28:12 +01:00
committed by GitHub
17 changed files with 276 additions and 36 deletions
@@ -0,0 +1,100 @@
/*
* 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 "ByteStreamSerializer.h"
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/StringFunc/StringFunc.h>
namespace AZ
{
namespace ByteSerializerInternal
{
static JsonSerializationResult::Result Load(void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
using JsonSerializationResult::Outcomes;
using JsonSerializationResult::Tasks;
AZ_Assert(outputValue, "Expected a valid pointer to load from json value.");
switch (inputValue.GetType())
{
case rapidjson::kStringType: {
JsonByteStream buffer;
if (AZ::StringFunc::Base64::Decode(buffer, inputValue.GetString(), inputValue.GetStringLength()))
{
JsonByteStream* valAsByteStream = static_cast<JsonByteStream*>(outputValue);
*valAsByteStream = AZStd::move(buffer);
return context.Report(Tasks::ReadField, Outcomes::Success, "Successfully read ByteStream.");
}
return context.Report(Tasks::ReadField, Outcomes::Invalid, "Decode of Base64 encoded ByteStream failed.");
}
case rapidjson::kArrayType:
case rapidjson::kObjectType:
case rapidjson::kNullType:
case rapidjson::kFalseType:
case rapidjson::kTrueType:
case rapidjson::kNumberType:
return context.Report(
Tasks::ReadField, Outcomes::Unsupported,
"Unsupported type. ByteStream values cannot be read from arrays, objects, nulls, booleans or numbers.");
default:
return context.Report(Tasks::ReadField, Outcomes::Unknown, "Unknown json type encountered for ByteStream value.");
}
}
static JsonSerializationResult::Result StoreWithDefault(
rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, JsonSerializerContext& context)
{
using JsonSerializationResult::Outcomes;
using JsonSerializationResult::Tasks;
const JsonByteStream& valAsByteStream = *static_cast<const JsonByteStream*>(inputValue);
if (context.ShouldKeepDefaults() || !defaultValue || (valAsByteStream != *static_cast<const JsonByteStream*>(defaultValue)))
{
const auto base64ByteStream = AZ::StringFunc::Base64::Encode(valAsByteStream.data(), valAsByteStream.size());
outputValue.SetString(base64ByteStream.c_str(), base64ByteStream.size(), context.GetJsonAllocator());
return context.Report(Tasks::WriteValue, Outcomes::Success, "ByteStream successfully stored.");
}
return context.Report(Tasks::WriteValue, Outcomes::DefaultsUsed, "Default ByteStream used.");
}
} // namespace ByteSerializerInternal
AZ_CLASS_ALLOCATOR_IMPL(JsonByteStreamSerializer, SystemAllocator, 0);
JsonSerializationResult::Result JsonByteStreamSerializer::Load(
void* outputValue, [[maybe_unused]] const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
{
AZ_Assert(
azrtti_typeid<JsonByteStream>() == outputValueTypeId,
"Unable to deserialize AZStd::vector<AZ::u8>> to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
return ByteSerializerInternal::Load(outputValue, inputValue, context);
}
JsonSerializationResult::Result JsonByteStreamSerializer::Store(
rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, [[maybe_unused]] const Uuid& valueTypeId,
JsonSerializerContext& context)
{
AZ_Assert(
azrtti_typeid<JsonByteStream>() == valueTypeId,
"Unable to serialize AZStd::vector<AZ::u8> to json because the provided type is %s",
valueTypeId.ToString<AZStd::string>().c_str());
return ByteSerializerInternal::StoreWithDefault(outputValue, inputValue, defaultValue, context);
}
} // namespace AZ
@@ -0,0 +1,38 @@
/*
* 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/Serialization/Json/BaseJsonSerializer.h>
#include <AzCore/std/containers/vector.h>
namespace AZ
{
using JsonByteStream = AZStd::vector<AZ::u8>; //!< Alias for AZStd::vector<AZ::u8>.
//! Serialize a stream of bytes (usually binary data) as a json string value.
//! @note Related to GenericClassByteStream (part of SerializeGenericTypeInfo<AZStd::vector<AZ::u8>> - see AZStdContainers.inl for more
//! details).
class JsonByteStreamSerializer : public BaseJsonSerializer
{
public:
AZ_RTTI(JsonByteStreamSerializer, "{30F0EA5A-CD13-4BA7-BAE1-D50D851CAC45}", 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;
};
} // namespace AZ
@@ -14,6 +14,7 @@
#include <AzCore/Serialization/Json/ArraySerializer.h>
#include <AzCore/Serialization/Json/BasicContainerSerializer.h>
#include <AzCore/Serialization/Json/BoolSerializer.h>
#include <AzCore/Serialization/Json/ByteStreamSerializer.h>
#include <AzCore/Serialization/Json/DoubleSerializer.h>
#include <AzCore/Serialization/Json/IntSerializer.h>
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
@@ -68,6 +69,8 @@ namespace AZ
jsonContext->Serializer<JsonStringSerializer>()->HandlesType<AZStd::string>();
jsonContext->Serializer<JsonOSStringSerializer>()->HandlesType<OSString>();
jsonContext->Serializer<JsonByteStreamSerializer>()->HandlesType<JsonByteStream>();
jsonContext->Serializer<JsonBasicContainerSerializer>()
->HandlesType<AZStd::fixed_vector>()
->HandlesType<AZStd::forward_list>()
@@ -505,6 +505,8 @@ set(FILES
Serialization/Json/BasicContainerSerializer.cpp
Serialization/Json/BoolSerializer.h
Serialization/Json/BoolSerializer.cpp
Serialization/Json/ByteStreamSerializer.h
Serialization/Json/ByteStreamSerializer.cpp
Serialization/Json/CastingHelpers.h
Serialization/Json/DoubleSerializer.h
Serialization/Json/DoubleSerializer.cpp
@@ -0,0 +1,59 @@
/*
* 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/Serialization/Json/ByteStreamSerializer.h>
#include <Tests/Serialization/Json/BaseJsonSerializerFixture.h>
#include <Tests/Serialization/Json/JsonSerializerConformityTests.h>
namespace JsonSerializationTests
{
class ByteStreamSerializerTestDescription : public JsonSerializerConformityTestDescriptor<AZ::JsonByteStream>
{
public:
AZStd::shared_ptr<AZ::BaseJsonSerializer> CreateSerializer() override
{
return AZStd::make_shared<AZ::JsonByteStreamSerializer>();
}
AZStd::shared_ptr<AZ::JsonByteStream> CreateDefaultInstance() override
{
return AZStd::make_shared<AZ::JsonByteStream>();
}
AZStd::shared_ptr<AZ::JsonByteStream> CreateFullySetInstance() override
{
// create a JsonByteStream (AZStd::vector<u8>) with ten 'a's
return AZStd::make_shared<AZ::JsonByteStream>(10, 'a');
}
AZStd::string_view GetJsonForFullySetInstance() override
{
// Base64 encoded version of 'aaaaaaaaaa' (see CreateFullySetInstance)
return R"("YWFhYWFhYWFhYQ==")";
}
void ConfigureFeatures(JsonSerializerConformityTestDescriptorFeatures& features) override
{
features.EnableJsonType(rapidjson::kStringType);
features.m_supportsPartialInitialization = false;
features.m_supportsInjection = false;
}
bool AreEqual(const AZ::JsonByteStream& lhs, const AZ::JsonByteStream& rhs) override
{
return lhs == rhs;
}
};
using ByteStreamConformityTestTypes = ::testing::Types<ByteStreamSerializerTestDescription>;
INSTANTIATE_TYPED_TEST_CASE_P(JsonByteStreamSerialzier, JsonSerializerConformityTests, ByteStreamConformityTestTypes);
} // namespace JsonSerializationTests
@@ -98,6 +98,7 @@ set(FILES
Serialization/Json/BaseJsonSerializerTests.cpp
Serialization/Json/BasicContainerSerializerTests.cpp
Serialization/Json/BoolSerializerTests.cpp
Serialization/Json/ByteStreamSerializerTests.cpp
Serialization/Json/ColorSerializerTests.cpp
Serialization/Json/DoubleSerializerTests.cpp
Serialization/Json/IntSerializerTests.cpp
@@ -102,6 +102,9 @@ namespace WhiteBox
//! Alias for a collection of faces.
using Faces = AZStd::vector<Face>;
//! Underlying representation of the White Box mesh (serialized halfedge data).
using WhiteBoxMeshStream = AZStd::vector<AZ::u8>;
//! Represents the vertex handles to be used to form a new face.
struct FaceVertHandles
{
@@ -726,19 +729,33 @@ namespace WhiteBox
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Serialization
//! The result of attempting to deserialize a white box mesh from a white box mesh stream.
enum class ReadResult
{
Full, //!< The white box mesh stream was full and was read into white box mesh (it is now initialized).
Empty, //!< The white box mesh stream was empty so no white box mesh was loaded.
Error //!< An error occurred while trying to deserialize white box mesh stream.
};
//! Take an input stream of bytes and create a white box mesh from the deserialized data.
//! @return Will return false if any error was encountered during deserialization, true otherwise.
//! @return Will return ReadResult::Full if the white box mesh stream was filled with data and
//! the white box mesh was initialized, ReadResult::Empty if white box mesh stream did not contain
//! any data (white box mesh will be left empty) or ReadResult::Error if any error was encountered
//! during deserialization.
//! @note A white box mesh must have been created first.
bool ReadMesh(WhiteBoxMesh& whiteBox, const AZStd::vector<AZ::u8>& input);
ReadResult ReadMesh(WhiteBoxMesh& whiteBox, const WhiteBoxMeshStream& input);
//! Take an input stream and create a white box mesh from the deserialized data.
//! @return Will return false if any error was encountered during deserialization, true otherwise.
//! @return Will return ReadResult::Full if the white box mesh stream was filled with data and
//! the white box mesh was initialized, ReadResult::Empty if white box mesh stream did not contain
//! any data (white box mesh will be left empty) or ReadResult::Error if any error was encountered
//! during deserialization.
//! @note The input stream must not skip white space characters (std::noskipws must be set on the stream).
bool ReadMesh(WhiteBoxMesh& whiteBox, std::istream& input);
ReadResult ReadMesh(WhiteBoxMesh& whiteBox, std::istream& input);
//! Take a white box mesh and write it out to a stream of bytes.
//! @return Will return false if any error was encountered during serialization, true otherwise.
bool WriteMesh(const WhiteBoxMesh& whiteBox, AZStd::vector<AZ::u8>& output);
bool WriteMesh(const WhiteBoxMesh& whiteBox, WhiteBoxMeshStream& output);
//! Clones the white box mesh object into a new mesh.
//! @return Will return null if any error was encountered during serialization, otherwise the cloned mesh.
@@ -86,7 +86,7 @@ namespace WhiteBox
{
success = assetHandler->SaveAssetData(meshAsset, &fileStream);
AZ_Printf(
"EditorWhiteBoxComponent", "Save %s. Location: %s", success ? "succeeded" : "failed",
"EditorWhiteBoxMeshAsset", "Save %s. Location: %s", success ? "succeeded" : "failed",
absoluteFilePath.c_str());
}
}
@@ -229,7 +229,15 @@ namespace WhiteBox
{
if (asset == m_meshAsset)
{
AZ_Warning("EditorWhiteBoxComponent", false, "OnAssetError: %s", asset.GetHint().c_str());
AZ_Warning("EditorWhiteBoxMeshAsset", false, "OnAssetError: %s", asset.GetHint().c_str());
}
}
void EditorWhiteBoxMeshAsset::OnAssetReloadError(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
if (asset == m_meshAsset)
{
AZ_Warning("EditorWhiteBoxMeshAsset", false, "OnAssetReloadError: %s", asset.GetHint().c_str());
}
}
@@ -82,6 +82,7 @@ namespace WhiteBox
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloadError(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
// WhiteBoxMeshAssetNotificationBus ...
void OnWhiteBoxMeshAssetModified(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
@@ -51,12 +51,12 @@ namespace WhiteBox
return AZStd::move(m_mesh);
}
void SetWhiteBoxData(AZStd::vector<AZ::u8> whiteBoxData)
void SetWhiteBoxData(Api::WhiteBoxMeshStream whiteBoxData)
{
m_whiteBoxData = AZStd::move(whiteBoxData);
}
const AZStd::vector<AZ::u8>& GetWhiteBoxData() const
const Api::WhiteBoxMeshStream& GetWhiteBoxData() const
{
return m_whiteBoxData;
}
@@ -73,7 +73,7 @@ namespace WhiteBox
}
Api::WhiteBoxMeshPtr m_mesh;
AZStd::vector<AZ::u8> m_whiteBoxData; //! Data used for creating undo commands.
Api::WhiteBoxMeshStream m_whiteBoxData; //! Data used for creating undo commands.
};
} // namespace Pipeline
} // namespace WhiteBox
@@ -112,16 +112,21 @@ namespace WhiteBox
const auto size = stream->GetLength();
AZStd::vector<AZ::u8> whiteBoxData(size);
Api::WhiteBoxMeshStream whiteBoxData;
whiteBoxData.resize(size);
stream->Read(size, whiteBoxData.data());
auto whiteBoxMesh = WhiteBox::Api::CreateWhiteBoxMesh();
const bool success = WhiteBox::Api::ReadMesh(*whiteBoxMesh, whiteBoxData);
const auto result = WhiteBox::Api::ReadMesh(*whiteBoxMesh, whiteBoxData);
// if result is not 'Full', then whiteBoxMeshAsset could be empty which is most likely an error
// as no data was loaded from the asset, or it was not correctly read in stream->Read(..)
const auto success = result == Api::ReadResult::Full;
if (success)
{
whiteBoxMeshAsset->SetMesh(AZStd::move(whiteBoxMesh));
whiteBoxMeshAsset->SetWhiteBoxData(whiteBoxData);
whiteBoxMeshAsset->SetWhiteBoxData(AZStd::move(whiteBoxData));
}
return success ? AZ::Data::AssetHandler::LoadResult::LoadComplete
@@ -29,12 +29,12 @@ namespace WhiteBox
m_asset = asset;
}
void WhiteBoxMeshAssetUndoCommand::SetUndoState(const AZStd::vector<AZ::u8>& undoState)
void WhiteBoxMeshAssetUndoCommand::SetUndoState(const Api::WhiteBoxMeshStream& undoState)
{
m_undoState = undoState;
}
void WhiteBoxMeshAssetUndoCommand::SetRedoState(const AZStd::vector<AZ::u8>& redoState)
void WhiteBoxMeshAssetUndoCommand::SetRedoState(const Api::WhiteBoxMeshStream& redoState)
{
m_redoState = redoState;
}
@@ -34,8 +34,8 @@ namespace WhiteBox
~WhiteBoxMeshAssetUndoCommand() override = default;
void SetAsset(AZ::Data::Asset<Pipeline::WhiteBoxMeshAsset> asset);
void SetUndoState(const AZStd::vector<AZ::u8>& undoState);
void SetRedoState(const AZStd::vector<AZ::u8>& redoState);
void SetUndoState(const Api::WhiteBoxMeshStream& undoState);
void SetRedoState(const Api::WhiteBoxMeshStream& redoState);
// AzToolsFramework::UndoSystem::URSequencePoint ...
void Undo() override;
@@ -44,7 +44,7 @@ namespace WhiteBox
protected:
AZ::Data::Asset<Pipeline::WhiteBoxMeshAsset> m_asset;
AZStd::vector<AZ::u8> m_undoState;
AZStd::vector<AZ::u8> m_redoState;
Api::WhiteBoxMeshStream m_undoState;
Api::WhiteBoxMeshStream m_redoState;
};
} // namespace WhiteBox
@@ -3379,7 +3379,7 @@ namespace WhiteBox
CalculatePlanarUVs(whiteBox);
}
bool WriteMesh(const WhiteBoxMesh& whiteBox, AZStd::vector<AZ::u8>& output)
bool WriteMesh(const WhiteBoxMesh& whiteBox, WhiteBoxMeshStream& output)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
@@ -3403,10 +3403,15 @@ namespace WhiteBox
return false;
}
bool ReadMesh(WhiteBoxMesh& whiteBox, const AZStd::vector<AZ::u8>& input)
ReadResult ReadMesh(WhiteBoxMesh& whiteBox, const WhiteBoxMeshStream& input)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (input.empty())
{
return ReadResult::Empty;
}
std::string inputStr;
inputStr.reserve(input.size());
AZStd::copy(input.cbegin(), input.cend(), AZStd::back_inserter(inputStr));
@@ -3418,33 +3423,33 @@ namespace WhiteBox
return ReadMesh(whiteBox, whiteBoxStream);
}
bool ReadMesh(WhiteBoxMesh& whiteBox, std::istream& input)
ReadResult ReadMesh(WhiteBoxMesh& whiteBox, std::istream& input)
{
const auto skipws = input.flags() & std::ios_base::skipws;
AZ_Assert(skipws == 0, "Input stream must not skip white space characters");
if (skipws != 0)
{
return false;
return ReadResult::Error;
}
AZStd::lock_guard lg(g_omSerializationLock);
OpenMesh::IO::Options options{OpenMesh::IO::Options::FaceTexCoord | OpenMesh::IO::Options::FaceNormal};
return OpenMesh::IO::read_mesh(whiteBox.mesh, input, ".om", options);
return OpenMesh::IO::read_mesh(whiteBox.mesh, input, ".om", options) ? ReadResult::Full : ReadResult::Error;
}
WhiteBoxMeshPtr CloneMesh(const WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
AZStd::vector<AZ::u8> clonedData;
WhiteBoxMeshStream clonedData;
if (!WriteMesh(whiteBox, clonedData))
{
return nullptr;
}
WhiteBoxMeshPtr newMesh = CreateWhiteBoxMesh();
if (!ReadMesh(*newMesh, clonedData))
if (ReadMesh(*newMesh, clonedData) != ReadResult::Full)
{
return nullptr;
}
@@ -3461,7 +3466,7 @@ namespace WhiteBox
bool SaveToWbm(const WhiteBoxMesh& whiteBox, AZ::IO::GenericStream& stream)
{
AZStd::vector<AZ::u8> buffer;
WhiteBoxMeshStream buffer;
const bool success = WhiteBox::Api::WriteMesh(whiteBox, buffer);
const auto bytesWritten = stream.Write(buffer.size(), buffer.data());
@@ -348,14 +348,14 @@ namespace WhiteBox
else
{
// attempt to load the mesh
if (Api::ReadMesh(*m_whiteBox, m_whiteBoxData))
const auto result = Api::ReadMesh(*m_whiteBox, m_whiteBoxData);
AZ_Error("EditorWhiteBoxComponent", result != WhiteBox::Api::ReadResult::Error, "Error deserializing white box mesh stream");
// if the read was successful but the byte stream is empty
// (there was nothing to load), create a default mesh
if (result == Api::ReadResult::Empty)
{
// if the read was successful but the byte stream is empty
// (there was nothing to load), create a default mesh
if (m_whiteBoxData.empty())
{
Api::InitializeAsUnitCube(*m_whiteBox);
}
Api::InitializeAsUnitCube(*m_whiteBox);
}
}
}
@@ -124,7 +124,7 @@ namespace WhiteBox
AZStd::optional<AZStd::unique_ptr<RenderMeshInterface>>
m_renderMesh; //!< The render mesh to use for the White Box mesh data.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); //!< Cached world transform of Entity.
AZStd::vector<AZ::u8> m_whiteBoxData; //!< Serialized White Box mesh data.
Api::WhiteBoxMeshStream m_whiteBoxData; //!< Serialized White Box mesh data.
//! Holds a reference to an optional WhiteBoxMeshAsset and manages the lifecycle of adding/removing an asset.
EditorWhiteBoxMeshAsset* m_editorMeshAsset = nullptr;
AZStd::optional<AZ::Aabb> m_worldAabb; //!< Cached world aabb (used for selection/view determination).
+2 -1
View File
@@ -470,6 +470,7 @@ namespace UnitTest
TEST_F(WhiteBoxTestFixture, MeshNotDeserializedWithSkipWhiteSpaceStream)
{
namespace Api = WhiteBox::Api;
using testing::Eq;
Api::InitializeAsUnitCube(*m_whiteBox);
AZStd::vector<AZ::u8> serializedWhiteBox;
@@ -485,7 +486,7 @@ namespace UnitTest
// note: std::stringstream will default to skip white space characters
AZ_TEST_START_TRACE_SUPPRESSION;
EXPECT_FALSE(Api::ReadMesh(*m_whiteBox, whiteBoxStream));
EXPECT_THAT(Api::ReadMesh(*m_whiteBox, whiteBoxStream), Eq(Api::ReadResult::Error));
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}