Add support for loading JsonAsset instance objects if they implement ISerializable interface

This commit is contained in:
Wojtek Figat
2021-01-04 14:19:51 +01:00
parent 8dc5b11f51
commit c5568c8eae
4 changed files with 110 additions and 88 deletions

View File

@@ -10,7 +10,10 @@
#include "Engine/Core/Log.h"
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Content/Factories/JsonAssetFactory.h"
#include "Engine/Core/Cache.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Utilities/StringConverter.h"
JsonAssetBase::JsonAssetBase(const SpawnParams& params, const AssetInfo* info)
: Asset(params, info)
@@ -175,51 +178,54 @@ void JsonAssetBase::onRename(const StringView& newPath)
REGISTER_JSON_ASSET(JsonAsset, "FlaxEngine.JsonAsset");
////////////////////////////////////////////////////////////////////////////////////
#include "Engine/Physics/PhysicalMaterial.h"
// Unmanaged json asset types that are serialized to JsonAsset and should be created by auto by asset.
// This allows to reuse JsonAsset without creating dedicated asset types. It has been designed for lightweight resources.
typedef ISerializable* (*UnmanagedJsonInstanceCreator)();
template<typename T>
ISerializable* Create()
{
return New<T>();
}
// Key: managed class typename, Value: unmanaged instance spawner function
Dictionary<String, UnmanagedJsonInstanceCreator> UnmanagedTypes(32);
void InitUnmanagedJsonTypes()
{
UnmanagedTypes[TEXT("FlaxEngine.PhysicalMaterial")] = &Create<PhysicalMaterial>;
}
////////////////////////////////////////////////////////////////////////////////////
JsonAsset::JsonAsset(const SpawnParams& params, const AssetInfo* info)
: JsonAssetBase(params, info)
, Instance(nullptr)
{
if (UnmanagedTypes.IsEmpty())
InitUnmanagedJsonTypes();
}
Asset::LoadResult JsonAsset::loadAsset()
{
// Base
auto result = JsonAssetBase::loadAsset();
if (result != LoadResult::Ok)
if (result != LoadResult::Ok || IsInternalType())
return result;
UnmanagedJsonInstanceCreator instanceSpawner = nullptr;
if (UnmanagedTypes.TryGet(DataTypeName, instanceSpawner))
// Try to scripting type for this data
const StringAsANSI<> dataTypeNameAnsi(DataTypeName.Get(), DataTypeName.Length());
const auto typeHandle = Scripting::FindScriptingType(StringAnsiView(dataTypeNameAnsi.Get(), DataTypeName.Length()));
if (typeHandle)
{
Instance = instanceSpawner();
Instance->Deserialize(*Data, nullptr);
auto& type = typeHandle.GetType();
switch (type.Type)
{
case ScriptingTypes::Class:
{
// Ensure that object can deserialized
const ScriptingType::InterfaceImplementation* interfaces = type.GetInterface(&ISerializable::TypeInitializer);
if (!interfaces)
{
LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString());
break;
}
// Allocate object
const auto instance = Allocator::Allocate(type.Size);
if (!instance)
return LoadResult::Failed;
Instance = instance;
_dtor = type.Class.Dtor;
type.Class.Ctor(instance);
// Deserialize object
auto modifier = Cache::ISerializeModifier.Get();
modifier->EngineBuild = DataEngineBuild;
((ISerializable*)((byte*)instance + interfaces->VTableOffset))->Deserialize(*Data, modifier.Value);
// TODO: delete object when containing BinaryModule gets unloaded
break;
}
default: ;
}
}
return result;
@@ -230,5 +236,11 @@ void JsonAsset::unload(bool isReloading)
// Base
JsonAssetBase::unload(isReloading);
SAFE_DELETE(Instance);
if (Instance)
{
_dtor(Instance);
Allocator::Free(Instance);
Instance = nullptr;
_dtor = nullptr;
}
}

View File

@@ -78,13 +78,15 @@ protected:
API_CLASS(NoSpawn) class JsonAsset : public JsonAssetBase
{
DECLARE_ASSET_HEADER(JsonAsset);
private:
ScriptingType::Dtor _dtor;
public:
/// <summary>
/// The deserialized unmanaged object instance (e.g. PhysicalMaterial).
/// </summary>
ISerializable* Instance;
void* Instance;
protected:

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Serialization/JsonFwd.h"
#include "Engine/Core/Compiler.h"
#include "Engine/Core/Config.h"
class JsonWriter;
class ISerializeModifier;
/// <summary>
/// Interface for objects that can be serialized/deserialized to/from JSON format.
/// </summary>
API_INTERFACE() class FLAXENGINE_API ISerializable
{
DECLARE_SCRIPTING_TYPE_MINIMAL(ISerializable);
public:
typedef rapidjson_flax::Document SerializeDocument;
/// <summary>
/// Serialization output stream
/// </summary>
typedef rapidjson_flax::Value DeserializeStream;
/// <summary>
/// Serialization input stream
/// </summary>
typedef JsonWriter SerializeStream;
public:
/// <summary>
/// Finalizes an instance of the <see cref="ISerializable"/> class.
/// </summary>
virtual ~ISerializable() = default;
/// <summary>
/// Serialize object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties.
/// </summary>
/// <param name="stream">The output stream.</param>
/// <param name="otherObj">The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties.</param>
virtual void Serialize(SerializeStream& stream, const void* otherObj) = 0;
/// <summary>
/// Deserialize object from the input stream
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="modifier">The deserialization modifier object. Always valid.</param>
virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0;
/// <summary>
/// Deserialize object from the input stream child member. Won't deserialize it if member is missing.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="memberName">The input stream member to lookup.</param>
/// <param name="modifier">The deserialization modifier object. Always valid.</param>
void DeserializeIfExists(DeserializeStream& stream, const char* memberName, ISerializeModifier* modifier);
};

View File

@@ -2,57 +2,5 @@
#pragma once
#include "JsonFwd.h"
#include "Engine/Core/Compiler.h"
class JsonWriter;
class ISerializeModifier;
/// <summary>
/// Interface for objects that can be serialized/deserialized to/from JSON format.
/// </summary>
class FLAXENGINE_API ISerializable
{
public:
typedef rapidjson_flax::Document SerializeDocument;
/// <summary>
/// Serialization output stream
/// </summary>
typedef rapidjson_flax::Value DeserializeStream;
/// <summary>
/// Serialization input stream
/// </summary>
typedef JsonWriter SerializeStream;
public:
/// <summary>
/// Finalizes an instance of the <see cref="ISerializable"/> class.
/// </summary>
virtual ~ISerializable() = default;
/// <summary>
/// Serialize object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties.
/// </summary>
/// <param name="stream">The output stream.</param>
/// <param name="otherObj">The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties.</param>
virtual void Serialize(SerializeStream& stream, const void* otherObj) = 0;
/// <summary>
/// Deserialize object from the input stream
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="modifier">The deserialization modifier object. Always valid.</param>
virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0;
/// <summary>
/// Deserialize object from the input stream child member. Won't deserialize it if member is missing.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="memberName">The input stream member to lookup.</param>
/// <param name="modifier">The deserialization modifier object. Always valid.</param>
void DeserializeIfExists(DeserializeStream& stream, const char* memberName, ISerializeModifier* modifier);
};
// ISerializable moved to Core module
#include "Engine/Core/ISerializable.h"