Merge remote-tracking branch 'origin/master' into 1.8

# Conflicts:
#	Source/Editor/Utilities/EditorUtilities.cpp
#	Source/Editor/Utilities/EditorUtilities.h
This commit is contained in:
Wojtek Figat
2024-02-19 22:26:16 +01:00
219 changed files with 4189 additions and 2372 deletions

View File

@@ -413,10 +413,10 @@ bool Animation::Save(const StringView& path)
MemoryWriteStream stream(4096);
// Info
stream.WriteInt32(102);
stream.WriteInt32(103);
stream.WriteDouble(Data.Duration);
stream.WriteDouble(Data.FramesPerSecond);
stream.WriteBool(Data.EnableRootMotion);
stream.WriteByte((byte)Data.RootMotionFlags);
stream.WriteString(Data.RootNodeName, 13);
// Animation channels
@@ -532,17 +532,22 @@ Asset::LoadResult Animation::load()
int32 headerVersion = *(int32*)stream.GetPositionHandle();
switch (headerVersion)
{
case 100:
case 101:
case 102:
{
case 103:
stream.ReadInt32(&headerVersion);
stream.ReadDouble(&Data.Duration);
stream.ReadDouble(&Data.FramesPerSecond);
Data.EnableRootMotion = stream.ReadBool();
stream.ReadByte((byte*)&Data.RootMotionFlags);
stream.ReadString(&Data.RootNodeName, 13);
break;
case 100:
case 101:
case 102:
stream.ReadInt32(&headerVersion);
stream.ReadDouble(&Data.Duration);
stream.ReadDouble(&Data.FramesPerSecond);
Data.RootMotionFlags = stream.ReadBool() ? AnimationRootMotionFlags::RootPositionXZ : AnimationRootMotionFlags::None;
stream.ReadString(&Data.RootNodeName, 13);
break;
}
default:
stream.ReadDouble(&Data.Duration);
stream.ReadDouble(&Data.FramesPerSecond);

View File

@@ -67,7 +67,7 @@ void AnimationGraph::OnDependencyModified(BinaryAsset* asset)
#endif
bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop)
bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop, bool rootMotion)
{
if (!IsVirtual())
{
@@ -89,7 +89,7 @@ bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, b
rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1);
rootNode.ID = 1;
rootNode.Values.Resize(1);
rootNode.Values[0] = (int32)RootMotionMode::NoExtraction;
rootNode.Values[0] = (int32)(rootMotion ? RootMotionExtraction::Enable : RootMotionExtraction::Ignore);
rootNode.Boxes.Resize(1);
rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void);
auto& animNode = graph.Nodes[1];

View File

@@ -37,8 +37,9 @@ public:
/// <param name="baseModel">The base model asset.</param>
/// <param name="anim">The animation to play.</param>
/// <param name="loop">True if play animation in a loop.</param>
/// <param name="rootMotion">True if apply root motion. Otherwise it will be ignored.</param>
/// <returns>True if failed, otherwise false.</returns>
API_FUNCTION() bool InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop = true);
API_FUNCTION() bool InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop = true, bool rootMotion = false);
/// <summary>
/// Tries to load surface graph from the asset.

View File

@@ -277,6 +277,8 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
// Find asset in registry
if (Cache.FindAsset(path, info))
return true;
if (!FileSystem::FileExists(path))
return false;
PROFILE_CPU();
const auto extension = FileSystem::GetExtension(path).ToLower();

View File

@@ -393,34 +393,57 @@ bool JsonAsset::CreateInstance()
if (typeHandle)
{
auto& type = typeHandle.GetType();
// Ensure that object can deserialized
const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer);
if (!interface)
{
LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString());
return false;
}
auto modifier = Cache::ISerializeModifier.Get();
modifier->EngineBuild = DataEngineBuild;
// Create object
switch (type.Type)
{
case ScriptingTypes::Class:
case ScriptingTypes::Structure:
{
// Ensure that object can deserialized
const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer);
if (!interface)
{
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 true;
Instance = instance;
InstanceType = typeHandle;
_dtor = type.Class.Dtor;
type.Class.Ctor(instance);
if (type.Type == ScriptingTypes::Class)
{
_dtor = type.Class.Dtor;
type.Class.Ctor(instance);
}
else
{
_dtor = type.Struct.Dtor;
type.Struct.Ctor(instance);
}
// Deserialize object
auto modifier = Cache::ISerializeModifier.Get();
modifier->EngineBuild = DataEngineBuild;
((ISerializable*)((byte*)instance + interface->VTableOffset))->Deserialize(*Data, modifier.Value);
break;
}
case ScriptingTypes::Script:
{
const ScriptingObjectSpawnParams params(Guid::New(), typeHandle);
const auto instance = type.Script.Spawn(params);
if (!instance)
return true;
Instance = instance;
_dtor = nullptr;
// Deserialize object
ToInterface<ISerializable>(instance)->Deserialize(*Data, modifier.Value);
break;
}
}
InstanceType = typeHandle;
}
return false;
@@ -441,13 +464,20 @@ void JsonAsset::DeleteInstance()
}
// C++ instance
if (!Instance || !_dtor)
if (!Instance)
return;
_dtor(Instance);
if (_dtor)
{
_dtor(Instance);
_dtor = nullptr;
Allocator::Free(Instance);
}
else
{
Delete((ScriptingObject*)Instance);
}
InstanceType = ScriptingTypeHandle();
Allocator::Free(Instance);
Instance = nullptr;
_dtor = nullptr;
}
#if USE_EDITOR

View File

@@ -139,7 +139,8 @@ public:
T* GetInstance() const
{
const_cast<JsonAsset*>(this)->CreateInstance();
return Instance && InstanceType.IsAssignableFrom(T::TypeInitializer) ? (T*)Instance : nullptr;
const ScriptingTypeHandle& type = T::TypeInitializer;
return Instance && type.IsAssignableFrom(InstanceType) ? (T*)Instance : nullptr;
}
public:

View File

@@ -0,0 +1,133 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Runtime.CompilerServices;
namespace FlaxEngine
{
/// <summary>
/// Json asset reference utility. References resource with a typed data type.
/// </summary>
/// <typeparam name="T">Type of the asset instance type.</typeparam>
#if FLAX_EDITOR
[CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))]
#endif
public struct JsonAssetReference<T> : IComparable, IComparable<JsonAssetReference<T>>, IEquatable<JsonAssetReference<T>>
{
/// <summary>
/// Gets or sets the referenced asset.
/// </summary>
public JsonAsset Asset;
/// <summary>
/// Gets the instance of the serialized object from the json asset data. Cached internally.
/// </summary>
public T Instance => (T)Asset?.Instance;
/// <summary>
/// Initializes a new instance of the <see cref="JsonAssetReference{T}"/> structure.
/// </summary>
/// <param name="asset">The Json Asset.</param>
public JsonAssetReference(JsonAsset asset)
{
Asset = asset;
}
/// <summary>
/// Implicit cast operator.
/// </summary>
public static implicit operator JsonAsset(JsonAssetReference<T> value)
{
return value.Asset;
}
/// <summary>
/// Implicit cast operator.
/// </summary>
public static implicit operator IntPtr(JsonAssetReference<T> value)
{
return Object.GetUnmanagedPtr(value.Asset);
}
/// <summary>
/// Implicit cast operator.
/// </summary>
public static implicit operator JsonAssetReference<T>(JsonAsset value)
{
return new JsonAssetReference<T>(value);
}
/// <summary>
/// Implicit cast operator.
/// </summary>
public static implicit operator JsonAssetReference<T>(IntPtr valuePtr)
{
return new JsonAssetReference<T>(Object.FromUnmanagedPtr(valuePtr) as JsonAsset);
}
/// <summary>
/// Checks if the object exists (reference is not null and the unmanaged object pointer is valid).
/// </summary>
/// <param name="obj">The object to check.</param>
/// <returns>True if object is valid, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator bool(JsonAssetReference<T> obj)
{
return obj.Asset;
}
/// <summary>
/// Checks whether the two objects are equal.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(JsonAssetReference<T> left, JsonAssetReference<T> right)
{
return left.Asset == right.Asset;
}
/// <summary>
/// Checks whether the two objects are not equal.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(JsonAssetReference<T> left, JsonAssetReference<T> right)
{
return left.Asset != right.Asset;
}
/// <inheritdoc />
public bool Equals(JsonAssetReference<T> other)
{
return Asset == other.Asset;
}
/// <inheritdoc />
public int CompareTo(JsonAssetReference<T> other)
{
return Object.GetUnmanagedPtr(Asset).CompareTo(Object.GetUnmanagedPtr(other.Asset));
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is JsonAssetReference<T> other && Asset == other.Asset;
}
/// <inheritdoc />
public override string ToString()
{
return Asset?.ToString();
}
/// <inheritdoc />
public int CompareTo(object obj)
{
return obj is JsonAssetReference<T> other ? CompareTo(other) : 1;
}
/// <inheritdoc />
public override int GetHashCode()
{
return (Asset != null ? Asset.GetHashCode() : 0);
}
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Content/JsonAsset.h"
#include "Engine/Content/AssetReference.h"
/// <summary>
/// Json asset reference utility. References resource with a typed data type.
/// </summary>
/// <typeparam name="T">Type of the asset instance type.</typeparam>
template<typename T>
API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference : AssetReference<JsonAsset>
{
JsonAssetReference() = default;
JsonAssetReference(JsonAsset* asset)
{
OnSet(asset);
}
/// <summary>
/// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type.
/// </summary>
/// <returns>The asset instance object or null.</returns>
FORCE_INLINE T* GetInstance() const
{
return _asset ? Get()->template GetInstance<T>() : nullptr;
}
JsonAssetReference& operator=(JsonAsset* asset) noexcept
{
OnSet(asset);
return *this;
}
operator JsonAsset*() const
{
return Get();
}
};

View File

@@ -58,8 +58,8 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b
Locker.Lock();
// Try fast lookup
FlaxStorage* result;
if (!StorageMap.TryGet(path, result))
FlaxStorage* storage;
if (!StorageMap.TryGet(path, storage))
{
// Detect storage type and create object
const bool isPackage = path.EndsWith(StringView(PACKAGE_FILES_EXTENSION));
@@ -67,39 +67,42 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b
{
auto package = New<FlaxPackage>(path);
Packages.Add(package);
result = package;
storage = package;
}
else
{
auto file = New<FlaxFile>(path);
Files.Add(file);
result = file;
storage = file;
}
// Register storage container
StorageMap.Add(path, result);
StorageMap.Add(path, storage);
}
// Build reference (before releasing the lock so ContentStorageSystem::Job won't delete it when running from async thread)
FlaxStorageReference result(storage);
Locker.Unlock();
if (loadIt)
{
// Initialize storage container
result->LockChunks();
const bool loadFailed = result->Load();
result->UnlockChunks();
storage->LockChunks();
const bool loadFailed = storage->Load();
storage->UnlockChunks();
if (loadFailed)
{
LOG(Error, "Failed to load {0}.", path);
Locker.Lock();
StorageMap.Remove(path);
if (result->IsPackage())
Packages.Remove((FlaxPackage*)result);
if (storage->IsPackage())
Packages.Remove((FlaxPackage*)storage);
else
Files.Remove((FlaxFile*)result);
Files.Remove((FlaxFile*)storage);
Locker.Unlock();
Delete(result);
return nullptr;
result = nullptr;
Delete(storage);
}
}

View File

@@ -211,7 +211,13 @@ FlaxStorage::~FlaxStorage()
#if USE_EDITOR
// Ensure to close any outstanding file handles to prevent file locking in case it failed to load
_file.DeleteAll();
Array<FileReadStream*> streams;
_file.GetValues(streams);
for (FileReadStream* stream : streams)
{
if (stream)
Delete(stream);
}
#endif
}
@@ -1266,7 +1272,6 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data)
}
#if ASSETS_LOADING_EXTRA_VERIFICATION
// Validate loaded header (asset ID and type ID must be the same)
if (e.ID != data.Header.ID)
{
@@ -1276,7 +1281,6 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data)
{
LOG(Error, "Loading asset header data mismatch! Expected Type Name: {0}, loaded header: {1}.\nSource: {2}", e.TypeName, data.Header.ToString(), ToString());
}
#endif
return false;
@@ -1339,7 +1343,14 @@ bool FlaxStorage::CloseFileHandles()
return true; // Failed, someone is still accessing the file
// Close file handles (from all threads)
_file.DeleteAll();
Array<FileReadStream*> streams;
_file.GetValues(streams);
for (FileReadStream* stream : streams)
{
if (stream)
Delete(stream);
}
_file.Clear();
return false;
}

View File

@@ -93,7 +93,7 @@ protected:
CriticalSection _loadLocker;
// Storage
ThreadLocalObject<FileReadStream> _file;
ThreadLocal<FileReadStream*> _file;
Array<FlaxChunk*> _chunks;
// Metadata

View File

@@ -58,17 +58,17 @@ public:
return _storage != nullptr;
}
FORCE_INLINE bool operator ==(const FlaxStorageReference& other) const
FORCE_INLINE bool operator==(const FlaxStorageReference& other) const
{
return _storage == other._storage;
}
FORCE_INLINE bool operator !=(const FlaxStorageReference& other) const
FORCE_INLINE bool operator!=(const FlaxStorageReference& other) const
{
return _storage != other._storage;
}
FORCE_INLINE FlaxStorage* operator ->() const
FORCE_INLINE FlaxStorage* operator->() const
{
return _storage;
}