Merge remote-tracking branch 'origin/1.6'

This commit is contained in:
Wojtek Figat
2023-06-16 18:43:40 +02:00
3625 changed files with 212272 additions and 247876 deletions

View File

@@ -2,7 +2,7 @@
#include "AnimEvent.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/ManagedSerialization.h"
#include "Engine/Scripting/Internal/ManagedSerialization.h"
#include "Engine/Serialization/SerializationFwd.h"
#include "Engine/Serialization/Serialization.h"

View File

@@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
namespace FlaxEngine
{
@@ -44,6 +45,7 @@ namespace FlaxEngine
/// The node evaluation context structure.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
[NativeMarshalling(typeof(ContextMarshaller))]
public struct Context
{
/// <summary>
@@ -92,6 +94,61 @@ namespace FlaxEngine
public AnimatedModel Instance;
}
[CustomMarshaller(typeof(Context), MarshalMode.Default, typeof(ContextMarshaller))]
internal static class ContextMarshaller
{
[StructLayout(LayoutKind.Sequential)]
public struct ContextNative
{
public IntPtr Graph;
public IntPtr GraphExecutor;
public IntPtr Node;
public uint NodeId;
public int BoxId;
public float DeltaTime;
public ulong CurrentFrameIndex;
public IntPtr BaseModel;
public IntPtr Instance;
}
internal static Context ConvertToManaged(ContextNative unmanaged) => ToManaged(unmanaged);
internal static ContextNative ConvertToUnmanaged(Context managed) => ToNative(managed);
internal static Context ToManaged(ContextNative managed)
{
return new Context()
{
Graph = managed.Graph,
GraphExecutor = managed.GraphExecutor,
Node = managed.Node,
NodeId = managed.NodeId,
BoxId = managed.BoxId,
DeltaTime = managed.DeltaTime,
CurrentFrameIndex = managed.CurrentFrameIndex,
BaseModel = SkinnedModelMarshaller.ConvertToManaged(managed.BaseModel),
Instance = AnimatedModelMarshaller.ConvertToManaged(managed.Instance),
};
}
internal static ContextNative ToNative(Context managed)
{
return new ContextNative()
{
Graph = managed.Graph,
GraphExecutor = managed.GraphExecutor,
Node = managed.Node,
NodeId = managed.NodeId,
BoxId = managed.BoxId,
DeltaTime = managed.DeltaTime,
CurrentFrameIndex = managed.CurrentFrameIndex,
BaseModel = SkinnedModelMarshaller.ConvertToUnmanaged(managed.BaseModel),
Instance = AnimatedModelMarshaller.ConvertToUnmanaged(managed.Instance),
};
}
internal static void Free(ContextNative unmanaged)
{
}
}
/// <summary>
/// The animation graph 'impulse' connections data container (the actual transfer is done via pointer as it gives better performance).
/// Container for skeleton nodes transformation hierarchy and any other required data.
@@ -118,12 +175,7 @@ namespace FlaxEngine
/// <summary>
/// The root motion data.
/// </summary>
public Vector3 RootMotionTranslation;
/// <summary>
/// The root motion data.
/// </summary>
public Quaternion RootMotionRotation;
public Transform RootMotion;
/// <summary>
/// The animation time position (in seconds).
@@ -194,8 +246,7 @@ namespace FlaxEngine
destination->NodesCount = source->NodesCount;
destination->Unused = source->Unused;
Utils.MemoryCopy(new IntPtr(destination->Nodes), new IntPtr(source->Nodes), (ulong)(source->NodesCount * sizeof(Transform)));
destination->RootMotionTranslation = source->RootMotionTranslation;
destination->RootMotionRotation = source->RootMotionRotation;
destination->RootMotion = source->RootMotion;
destination->Position = source->Position;
destination->Length = source->Length;
}
@@ -203,14 +254,16 @@ namespace FlaxEngine
#region Internal Calls
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_HasConnection(ref CustomNode.Context context, int boxId);
[LibraryImport("FlaxEngine", EntryPoint = "AnimGraphInternal_HasConnection")]
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_HasConnection(ref AnimationGraph.CustomNode.Context context, int boxId);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object Internal_GetInputValue(ref CustomNode.Context context, int boxId);
[LibraryImport("FlaxEngine", EntryPoint = "AnimGraphInternal_GetInputValue")]
[return: MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))]
internal static partial object Internal_GetInputValue(ref AnimationGraph.CustomNode.Context context, int boxId);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern IntPtr Internal_GetOutputImpulseData(ref CustomNode.Context context);
[LibraryImport("FlaxEngine", EntryPoint = "AnimGraphInternal_GetOutputImpulseData")]
internal static partial IntPtr Internal_GetOutputImpulseData(ref AnimationGraph.CustomNode.Context context);
#endregion
}

View File

@@ -29,6 +29,22 @@ public:
void PostExecute(TaskGraph* graph) override;
};
namespace
{
FORCE_INLINE bool CanUpdateModel(AnimatedModel* animatedModel)
{
auto skinnedModel = animatedModel->SkinnedModel.Get();
auto animGraph = animatedModel->AnimationGraph.Get();
return animGraph && animGraph->IsLoaded()
&& skinnedModel && skinnedModel->IsLoaded()
#if USE_EDITOR
// It may happen in editor so just add safe check to prevent any crashes
&& animGraph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count()
#endif
&& animGraph->Graph.IsReady();
}
}
AnimationsService AnimationManagerInstance;
Array<AnimatedModel*> UpdateList;
TaskGraphSystem* Animations::System = nullptr;
@@ -53,14 +69,9 @@ void AnimationsSystem::Job(int32 index)
{
PROFILE_CPU_NAMED("Animations.Job");
auto animatedModel = UpdateList[index];
auto skinnedModel = animatedModel->SkinnedModel.Get();
auto graph = animatedModel->AnimationGraph.Get();
if (graph && graph->IsLoaded() && graph->Graph.CanUseWithSkeleton(skinnedModel)
#if USE_EDITOR
&& graph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes
#endif
)
if (CanUpdateModel(animatedModel))
{
auto graph = animatedModel->AnimationGraph.Get();
#if COMPILE_WITH_PROFILER && TRACY_ENABLE
const StringView graphName(graph->GetPath());
ZoneName(*graphName, graphName.Length());
@@ -120,13 +131,7 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
for (int32 index = 0; index < UpdateList.Count(); index++)
{
auto animatedModel = UpdateList[index];
auto skinnedModel = animatedModel->SkinnedModel.Get();
auto animGraph = animatedModel->AnimationGraph.Get();
if (animGraph && animGraph->IsLoaded() && animGraph->Graph.CanUseWithSkeleton(skinnedModel)
#if USE_EDITOR
&& animGraph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes
#endif
)
if (CanUpdateModel(animatedModel))
{
animatedModel->OnAnimationUpdated_Sync();
}

View File

@@ -23,7 +23,7 @@ AnimSubGraph* AnimGraphBase::LoadSubGraph(const void* data, int32 dataLength, co
auto subGraph = New<AnimSubGraph>(_graph);
// Load graph
MemoryReadStream stream((byte*)data, dataLength);
MemoryReadStream stream((const byte*)data, dataLength);
if (subGraph->Load(&stream, false))
{
// Load failed
@@ -180,8 +180,6 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
const Float4 range = n->Values[0].AsFloat4();
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++)
{
auto data0 = n->Values[i * 2 + 4].AsFloat4();
data0.X = Math::Clamp(data0.X, range.X, range.Y);
n->Assets[i] = Content::LoadAsync<Animation>((Guid)n->Values[i * 2 + 5]);
n->Data.MultiBlend1D.IndicesSorted[i] = n->Assets[i] ? i : ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS;
}
@@ -200,9 +198,6 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
const Float4 range = n->Values[0].AsFloat4();
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++)
{
auto data0 = n->Values[i * 2 + 4].AsFloat4();
data0.X = Math::Clamp(data0.X, range.X, range.Y);
data0.Y = Math::Clamp(data0.Y, range.Z, range.W);
n->Assets[i] = (Asset*)Content::LoadAsync<Animation>((Guid)n->Values[i * 2 + 5]);
if (n->Assets[i])
{
@@ -283,91 +278,11 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
Value& surfaceData = n->Values[1];
data.Graph = LoadSubGraph(surfaceData.AsBlob.Data, surfaceData.AsBlob.Length, (const Char*)name.AsBlob.Data);
// Initialize transitions
Value& transitionsData = n->Values[2];
int32 validTransitions = 0;
if (transitionsData.Type == VariantType::Blob && transitionsData.AsBlob.Length)
{
MemoryReadStream stream((byte*)transitionsData.AsBlob.Data, transitionsData.AsBlob.Length);
int32 version;
stream.ReadInt32(&version);
if (version != 1)
{
LOG(Warning, "Invalid version of the Anim Graph state transitions data.");
return true;
}
int32 transitionsCount;
stream.ReadInt32(&transitionsCount);
StateTransitions.EnsureCapacity(StateTransitions.Count() + transitionsCount);
AnimGraphStateTransition transition;
for (int32 i = 0; i < transitionsCount; i++)
{
struct Data
{
int32 Destination;
int32 Flags;
int32 Order;
float BlendDuration;
int32 BlendMode;
int32 Unused0;
int32 Unused1;
int32 Unused2;
};
Data transitionData;
stream.ReadBytes(&transitionData, sizeof(transitionData));
transition.Flags = (AnimGraphStateTransition::FlagTypes)transitionData.Flags;
transition.BlendDuration = transitionData.BlendDuration;
transition.BlendMode = (AlphaBlendMode)transitionData.BlendMode;
transition.Destination = GetNode(transitionData.Destination);
transition.RuleGraph = nullptr;
int32 ruleSize;
stream.ReadInt32(&ruleSize);
const auto ruleBytes = (byte*)stream.Move(ruleSize);
if (static_cast<int32>(transition.Flags & AnimGraphStateTransition::FlagTypes::Enabled) == 0)
{
// Skip disabled transitions
continue;
}
if (ruleSize != 0)
{
transition.RuleGraph = LoadSubGraph(ruleBytes, ruleSize, TEXT("Rule"));
if (transition.RuleGraph && transition.RuleGraph->GetRootNode() == nullptr)
{
LOG(Warning, "Missing root node for the state machine transition rule graph.");
continue;
}
}
if (transition.Destination == nullptr)
{
LOG(Warning, "Missing target node for the state machine transition.");
continue;
}
if (validTransitions == ANIM_GRAPH_MAX_STATE_TRANSITIONS)
{
LOG(Warning, "State uses too many transitions.");
continue;
}
data.Transitions[validTransitions++] = (uint16)StateTransitions.Count();
StateTransitions.Add(transition);
}
}
if (validTransitions != ANIM_GRAPH_MAX_STATE_TRANSITIONS)
data.Transitions[validTransitions] = AnimGraphNode::StateData::InvalidTransitionIndex;
// Release data to don't use that memory
surfaceData = Value::Null;
transitionsData = Value::Null;
// Initialize transitions
LoadStateTransitions(data, n->Values[2]);
break;
}
@@ -443,6 +358,10 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
case 33:
ADD_BUCKET(InstanceDataBucketInit);
break;
// Any State
case 34:
LoadStateTransitions(n->Data.AnyState, n->Values[0]);
break;
}
break;
// Custom
@@ -465,4 +384,91 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
return VisjectGraph::onNodeLoaded(n);
}
void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Value& transitionsData)
{
int32 validTransitions = 0;
if (transitionsData.Type == VariantType::Blob && transitionsData.AsBlob.Length)
{
MemoryReadStream stream((byte*)transitionsData.AsBlob.Data, transitionsData.AsBlob.Length);
int32 version;
stream.ReadInt32(&version);
if (version != 1)
{
LOG(Warning, "Invalid version of the Anim Graph state transitions data.");
return;
}
int32 transitionsCount;
stream.ReadInt32(&transitionsCount);
StateTransitions.EnsureCapacity(StateTransitions.Count() + transitionsCount);
AnimGraphStateTransition transition;
for (int32 i = 0; i < transitionsCount; i++)
{
// Must match StateMachineTransition.Data in C#
struct Data
{
int32 Destination;
int32 Flags;
int32 Order;
float BlendDuration;
int32 BlendMode;
int32 Unused0;
int32 Unused1;
int32 Unused2;
};
Data transitionData;
stream.ReadBytes(&transitionData, sizeof(transitionData));
transition.Flags = (AnimGraphStateTransition::FlagTypes)transitionData.Flags;
transition.BlendDuration = transitionData.BlendDuration;
transition.BlendMode = (AlphaBlendMode)transitionData.BlendMode;
transition.Destination = GetNode(transitionData.Destination);
transition.RuleGraph = nullptr;
int32 ruleSize;
stream.ReadInt32(&ruleSize);
const auto ruleBytes = (byte*)stream.Move(ruleSize);
if (static_cast<int32>(transition.Flags & AnimGraphStateTransition::FlagTypes::Enabled) == 0)
{
// Skip disabled transitions
continue;
}
if (ruleSize != 0)
{
transition.RuleGraph = LoadSubGraph(ruleBytes, ruleSize, TEXT("Rule"));
if (transition.RuleGraph && transition.RuleGraph->GetRootNode() == nullptr)
{
LOG(Warning, "Missing root node for the state machine transition rule graph.");
continue;
}
}
if (transition.Destination == nullptr)
{
LOG(Warning, "Missing target node for the state machine transition.");
continue;
}
if (validTransitions == ANIM_GRAPH_MAX_STATE_TRANSITIONS)
{
LOG(Warning, "State uses too many transitions.");
continue;
}
data.Transitions[validTransitions++] = (uint16)StateTransitions.Count();
StateTransitions.Add(transition);
}
}
if (validTransitions != ANIM_GRAPH_MAX_STATE_TRANSITIONS)
data.Transitions[validTransitions] = AnimGraphNode::StateData::InvalidTransitionIndex;
// Release data to don't use that memory
transitionsData = AnimGraphExecutor::Value::Null;
}
#undef ADD_BUCKET

View File

@@ -2,24 +2,23 @@
#include "AnimGraph.h"
#include "Engine/Debug/DebugLog.h"
#include "Engine/Scripting/InternalCalls.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MDomain.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/MException.h"
#include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Scripting/Internal/InternalCalls.h"
#include "Engine/Content/Assets/SkinnedModel.h"
#if USE_MONO
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
#if USE_CSHARP
struct InternalInitData
{
MonoArray* Values;
MonoObject* BaseModel;
MArray* Values;
MObject* BaseModel;
};
struct InternalContext
@@ -31,8 +30,8 @@ struct InternalContext
int32 BoxId;
float DeltaTime;
uint64 CurrentFrameIndex;
MonoObject* BaseModel;
MonoObject* Instance;
MObject* BaseModel;
MObject* Instance;
};
struct InternalImpulse
@@ -40,63 +39,57 @@ struct InternalImpulse
int32 NodesCount;
int32 Unused;
Transform* Nodes;
Vector3 RootMotionTranslation;
Quaternion RootMotionRotation;
Transform RootMotion;
float Position;
float Length;
};
static_assert(sizeof(InternalImpulse) == sizeof(AnimGraphImpulse), "Please update managed impulse type for Anim Graph to match the C++ backend data layout.");
namespace AnimGraphInternal
DEFINE_INTERNAL_CALL(bool) AnimGraphInternal_HasConnection(InternalContext* context, int32 boxId)
{
bool HasConnection(InternalContext* context, int32 boxId)
{
const auto box = context->Node->TryGetBox(boxId);
if (box == nullptr)
DebugLog::ThrowArgumentOutOfRange("boxId");
return box->HasConnection();
}
const auto box = context->Node->TryGetBox(boxId);
if (box == nullptr)
DebugLog::ThrowArgumentOutOfRange("boxId");
return box->HasConnection();
}
MonoObject* GetInputValue(InternalContext* context, int32 boxId)
{
const auto box = context->Node->TryGetBox(boxId);
if (box == nullptr)
DebugLog::ThrowArgumentOutOfRange("boxId");
if (!box->HasConnection())
DebugLog::ThrowArgument("boxId", "This box has no connection. Use HasConnection to check if can get input value.");
DEFINE_INTERNAL_CALL(MObject*) AnimGraphInternal_GetInputValue(InternalContext* context, int32 boxId)
{
const auto box = context->Node->TryGetBox(boxId);
if (box == nullptr)
DebugLog::ThrowArgumentOutOfRange("boxId");
if (!box->HasConnection())
DebugLog::ThrowArgument("boxId", "This box has no connection. Use HasConnection to check if can get input value.");
Variant value = Variant::Null;
context->GraphExecutor->GetInputValue(box, value);
Variant value = Variant::Null;
context->GraphExecutor->GetInputValue(box, value);
// Cast value to prevent implicit value conversion issues and handling this on C# side
if (!(box->Type.Type == VariantType::Void && value.Type.Type == VariantType::Pointer))
value = Variant::Cast(value, box->Type);
return MUtils::BoxVariant(value);
}
// Cast value to prevent implicit value conversion issues and handling this on C# side
if (!(box->Type.Type == VariantType::Void && value.Type.Type == VariantType::Pointer))
value = Variant::Cast(value, box->Type);
return MUtils::BoxVariant(value);
}
AnimGraphImpulse* GetOutputImpulseData(InternalContext* context)
{
const auto nodes = context->Node->GetNodes(context->GraphExecutor);
context->GraphExecutor->InitNodes(nodes);
return nodes;
}
DEFINE_INTERNAL_CALL(AnimGraphImpulse*) AnimGraphInternal_GetOutputImpulseData(InternalContext* context)
{
const auto nodes = context->Node->GetNodes(context->GraphExecutor);
context->GraphExecutor->InitNodes(nodes);
return nodes;
}
#endif
void AnimGraphExecutor::initRuntime()
{
#if USE_MONO
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_HasConnection", &AnimGraphInternal::HasConnection);
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetInputValue", &AnimGraphInternal::GetInputValue);
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetOutputImpulseData", &AnimGraphInternal::GetOutputImpulseData);
#endif
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_HasConnection", &AnimGraphInternal_HasConnection);
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetInputValue", &AnimGraphInternal_GetInputValue);
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetOutputImpulseData", &AnimGraphInternal_GetOutputImpulseData);
}
void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value)
{
#if USE_MONO
#if USE_CSHARP
auto& context = Context.Get();
if (context.ValueCache.TryGet(boxBase, value))
return;
@@ -122,7 +115,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value&
internalContext.Instance = context.Data->Object ? context.Data->Object->GetOrCreateManagedInstance() : nullptr;
// Peek managed object
const auto obj = mono_gchandle_get_target(data.Handle);
const auto obj = MCore::GCHandle::GetTarget(data.Handle);
if (obj == nullptr)
{
LOG(Warning, "Custom node instance is null.");
@@ -133,7 +126,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value&
void* params[1];
params[0] = &internalContext;
MObject* exception = nullptr;
MonoObject* result = data.Evaluate->Invoke(obj, params, &exception);
MObject* result = data.Evaluate->Invoke(obj, params, &exception);
if (exception)
{
MException ex(exception);
@@ -152,12 +145,6 @@ bool AnimGraph::IsReady() const
return BaseModel && BaseModel->IsLoaded();
}
bool AnimGraph::CanUseWithSkeleton(SkinnedModel* other) const
{
// All data loaded and nodes count the same
return IsReady() && other && other->IsLoaded() && other->Skeleton.Nodes.Count() == BaseModel->Skeleton.Nodes.Count();
}
void AnimGraph::ClearCustomNode(Node* node)
{
// Clear data
@@ -165,16 +152,14 @@ void AnimGraph::ClearCustomNode(Node* node)
data.Evaluate = nullptr;
if (data.Handle)
{
#if USE_MONO
mono_gchandle_free(data.Handle);
#endif
MCore::GCHandle::Free(data.Handle);
data.Handle = 0;
}
}
bool AnimGraph::InitCustomNode(Node* node)
{
#if USE_MONO
#if USE_CSHARP
// Fetch the node logic controller type
if (node->Values.Count() < 2 || node->Values[0].Type.Type != ValueType::String)
{
@@ -182,7 +167,7 @@ bool AnimGraph::InitCustomNode(Node* node)
return false;
}
const StringView typeName(node->Values[0]);
const MString typeNameStd = typeName.ToStringAnsi();
const StringAnsi typeNameStd = typeName.ToStringAnsi();
MClass* type = Scripting::FindClass(typeNameStd);
if (type == nullptr)
{
@@ -205,18 +190,15 @@ bool AnimGraph::InitCustomNode(Node* node)
}
// Create node values managed array
if (mono_domain_get() == nullptr)
Scripting::GetScriptsDomain()->Dispatch();
const auto values = mono_array_new(mono_domain_get(), mono_get_object_class(), node->Values.Count());
MCore::Thread::Attach();
MArray* values = MCore::Array::New( MCore::TypeCache::Object, node->Values.Count());
MObject** valuesPtr = MCore::Array::GetAddress<MObject*>(values);
for (int32 i = 0; i < node->Values.Count(); i++)
{
const auto v = MUtils::BoxVariant(node->Values[i]);
mono_array_set(values, MonoObject*, i, v);
}
valuesPtr[i] = MUtils::BoxVariant(node->Values[i]);
// Allocate managed node object (create GC handle to prevent destruction)
const auto obj = type->CreateInstance();
const auto handleGC = mono_gchandle_new(obj, false);
MObject* obj = type->CreateInstance();
const MGCHandle handleGC = MCore::GCHandle::New(obj);
// Initialize node
InternalInitData initData;
@@ -228,7 +210,7 @@ bool AnimGraph::InitCustomNode(Node* node)
load->Invoke(obj, params, &exception);
if (exception)
{
mono_gchandle_free(handleGC);
MCore::GCHandle::Free(handleGC);
MException ex(exception);
ex.Log(LogType::Warning, TEXT("AnimGraph"));

View File

@@ -7,55 +7,10 @@
#include "Engine/Graphics/Models/SkeletonData.h"
#include "Engine/Scripting/Scripting.h"
extern void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 i);
ThreadLocal<AnimGraphContext> AnimGraphExecutor::Context;
RootMotionData RootMotionData::Identity = { Vector3(0.0f), Quaternion(0.0f, 0.0f, 0.0f, 1.0f) };
RootMotionData& RootMotionData::operator+=(const RootMotionData& b)
{
Translation += b.Translation;
Rotation *= b.Rotation;
return *this;
}
RootMotionData& RootMotionData::operator+=(const Transform& b)
{
Translation += b.Translation;
Rotation *= b.Orientation;
return *this;
}
RootMotionData& RootMotionData::operator-=(const Transform& b)
{
Translation -= b.Translation;
Quaternion invRotation = Rotation;
invRotation.Invert();
Quaternion::Multiply(invRotation, b.Orientation, Rotation);
return *this;
}
RootMotionData RootMotionData::operator+(const RootMotionData& b) const
{
RootMotionData result;
result.Translation = Translation + b.Translation;
result.Rotation = Rotation * b.Rotation;
return result;
}
RootMotionData RootMotionData::operator-(const RootMotionData& b) const
{
RootMotionData result;
result.Rotation = Rotation;
result.Rotation.Invert();
Vector3::Transform(b.Translation - Translation, result.Rotation, result.Translation);
Quaternion::Multiply(result.Rotation, b.Rotation, result.Rotation);
return result;
}
Transform AnimGraphImpulse::GetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex) const
{
const int32 parentIndex = skeleton.Nodes[nodeIndex].ParentIndex;
@@ -87,7 +42,7 @@ void AnimGraphInstanceData::Clear()
LastUpdateTime = -1;
CurrentFrame = 0;
RootTransform = Transform::Identity;
RootMotion = RootMotionData::Identity;
RootMotion = Transform::Identity;
Parameters.Resize(0);
State.Resize(0);
NodesPose.Resize(0);
@@ -103,7 +58,7 @@ void AnimGraphInstanceData::ClearState()
LastUpdateTime = -1;
CurrentFrame = 0;
RootTransform = Transform::Identity;
RootMotion = RootMotionData::Identity;
RootMotion = Transform::Identity;
State.Resize(0);
NodesPose.Resize(0);
Slots.Clear();
@@ -257,15 +212,12 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
e.Hit = false;
// Init empty nodes data
context.EmptyNodes.RootMotion = RootMotionData::Identity;
context.EmptyNodes.RootMotion = Transform::Identity;
context.EmptyNodes.Position = 0.0f;
context.EmptyNodes.Length = 0.0f;
context.EmptyNodes.Nodes.Resize(_skeletonNodesCount, false);
for (int32 i = 0; i < _skeletonNodesCount; i++)
{
auto& node = skeleton.Nodes[i];
context.EmptyNodes.Nodes[i] = node.LocalTransform;
}
context.EmptyNodes.Nodes[i] = skeleton.Nodes[i].LocalTransform;
}
// Update the animation graph and gather skeleton nodes transformations in nodes local space
@@ -301,6 +253,44 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
}
}
}
SkeletonData* animResultSkeleton = &skeleton;
// Retarget animation when using output pose from other skeleton
AnimGraphImpulse retargetNodes;
if (_graph.BaseModel != data.NodesSkeleton)
{
ANIM_GRAPH_PROFILE_EVENT("Retarget");
// Init nodes for the target skeleton
auto& targetSkeleton = data.NodesSkeleton->Skeleton;
retargetNodes = *animResult;
retargetNodes.Nodes.Resize(targetSkeleton.Nodes.Count());
Transform* targetNodes = retargetNodes.Nodes.Get();
for (int32 i = 0; i < retargetNodes.Nodes.Count(); i++)
targetNodes[i] = targetSkeleton.Nodes[i].LocalTransform;
// Use skeleton mapping
const SkinnedModel::SkeletonMapping mapping = data.NodesSkeleton->GetSkeletonMapping(_graph.BaseModel);
if (mapping.NodesMapping.IsValid())
{
const auto& sourceSkeleton = _graph.BaseModel->Skeleton;
Transform* sourceNodes = animResult->Nodes.Get();
for (int32 i = 0; i < retargetNodes.Nodes.Count(); i++)
{
const int32 nodeToNode = mapping.NodesMapping[i];
if (nodeToNode != -1)
{
Transform node = sourceNodes[nodeToNode];
RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, i);
targetNodes[i] = node;
}
}
}
animResult = &retargetNodes;
animResultSkeleton = &targetSkeleton;
}
// Allow for external override of the local pose (eg. by the ragdoll)
data.LocalPoseOverride(animResult);
@@ -309,13 +299,14 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
{
ANIM_GRAPH_PROFILE_EVENT("Global Pose");
data.NodesPose.Resize(_skeletonNodesCount, false);
ASSERT(animResultSkeleton->Nodes.Count() == animResult->Nodes.Count());
data.NodesPose.Resize(animResultSkeleton->Nodes.Count(), false);
Transform* nodesTransformations = animResult->Nodes.Get();
// Note: this assumes that nodes are sorted (parents first)
for (int32 nodeIndex = 0; nodeIndex < _skeletonNodesCount; nodeIndex++)
for (int32 nodeIndex = 0; nodeIndex < animResultSkeleton->Nodes.Count(); nodeIndex++)
{
const int32 parentIndex = skeleton.Nodes[nodeIndex].ParentIndex;
const int32 parentIndex = animResultSkeleton->Nodes[nodeIndex].ParentIndex;
if (parentIndex != -1)
{
nodesTransformations[parentIndex].LocalToWorld(nodesTransformations[nodeIndex], nodesTransformations[nodeIndex]);

View File

@@ -26,58 +26,6 @@ class AnimGraphExecutor;
class SkinnedModel;
class SkeletonData;
/// <summary>
/// The root motion data container. Supports displacement and rotation (no scale component).
/// </summary>
struct RootMotionData
{
static RootMotionData Identity;
Vector3 Translation;
Quaternion Rotation;
RootMotionData()
{
}
RootMotionData(const Vector3& translation, const Quaternion& rotation)
{
Translation = translation;
Rotation = rotation;
}
RootMotionData(const RootMotionData& other)
{
Translation = other.Translation;
Rotation = other.Rotation;
}
explicit RootMotionData(const Transform& other)
{
Translation = other.Translation;
Rotation = other.Orientation;
}
RootMotionData& operator=(const Transform& other)
{
Translation = other.Translation;
Rotation = other.Orientation;
return *this;
}
RootMotionData& operator+=(const RootMotionData& b);
RootMotionData& operator+=(const Transform& b);
RootMotionData& operator-=(const Transform& b);
RootMotionData operator+(const RootMotionData& b) const;
RootMotionData operator-(const RootMotionData& b) const;
static void Lerp(const RootMotionData& t1, const RootMotionData& t2, float amount, RootMotionData& result)
{
Vector3::Lerp(t1.Translation, t2.Translation, amount, result.Translation);
Quaternion::Slerp(t1.Rotation, t2.Rotation, amount, result.Rotation);
}
};
/// <summary>
/// The animation graph 'impulse' connections data container (the actual transfer is done via pointer as it gives better performance).
/// Container for skeleton nodes transformation hierarchy and any other required data.
@@ -93,7 +41,7 @@ struct FLAXENGINE_API AnimGraphImpulse
/// <summary>
/// The root motion extracted from the animation to apply on animated object.
/// </summary>
RootMotionData RootMotion = RootMotionData::Identity;
Transform RootMotion = Transform::Identity;
/// <summary>
/// The animation time position (in seconds).
@@ -172,25 +120,12 @@ public:
/// </summary>
enum class FlagTypes
{
/// <summary>
/// The none.
/// </summary>
None = 0,
/// <summary>
/// The enabled flag.
/// </summary>
Enabled = 1,
/// <summary>
/// The solo flag.
/// </summary>
Solo = 2,
/// <summary>
/// The use default rule flag.
/// </summary>
UseDefaultRule = 4,
InterruptionRuleRechecking = 8,
InterruptionInstant = 16,
};
public:
@@ -361,7 +296,7 @@ public:
/// <summary>
/// The current root motion delta to apply on a target object.
/// </summary>
RootMotionData RootMotion = RootMotionData::Identity;
Transform RootMotion = Transform::Identity;
/// <summary>
/// The animation graph parameters collection (instanced, override the default values).
@@ -384,7 +319,12 @@ public:
ScriptingObject* Object;
/// <summary>
/// The custom event called after local pose evaluation.
/// The output nodes pose skeleton asset to use. Allows to remap evaluated animation pose for Base Model of the Anim Graph to the target Animated Model that plays it. Nodes Pose will match its skeleton. Use null if disable retargetting.
/// </summary>
SkinnedModel* NodesSkeleton = nullptr;
/// <summary>
/// The custom event called after local pose evaluation and retargetting.
/// </summary>
Delegate<AnimGraphImpulse*> LocalPoseOverride;
@@ -495,24 +435,31 @@ public:
AnimSubGraph* Graph;
};
struct StateData
struct StateBaseData
{
/// <summary>
/// The invalid transition valid used in Transitions to indicate invalid transition linkage.
/// </summary>
const static uint16 InvalidTransitionIndex = MAX_uint16;
/// <summary>
/// The graph of the state. Contains the state animation evaluation graph. Its root node is the state output node with an input box for the state blend pose sampling.
/// </summary>
AnimSubGraph* Graph;
/// <summary>
/// The outgoing transitions from this state to the other states. Each array item contains index of the transition data from the state node graph transitions cache. Value InvalidTransitionIndex is used for last transition to indicate the transitions amount.
/// </summary>
uint16 Transitions[ANIM_GRAPH_MAX_STATE_TRANSITIONS];
};
struct StateData : StateBaseData
{
/// <summary>
/// The graph of the state. Contains the state animation evaluation graph. Its root node is the state output node with an input box for the state blend pose sampling.
/// </summary>
AnimSubGraph* Graph;
};
struct AnyStateData : StateBaseData
{
};
struct CustomData
{
/// <summary>
@@ -523,7 +470,7 @@ public:
/// <summary>
/// The GC handle to the managed instance of the node object.
/// </summary>
uint32 Handle;
MGCHandle Handle;
};
struct CurveData
@@ -564,6 +511,7 @@ public:
MultiBlend2DData MultiBlend2D;
StateMachineData StateMachine;
StateData State;
AnyStateData AnyState;
CustomData Custom;
CurveData Curve;
AnimationGraphFunctionData AnimationGraphFunction;
@@ -681,6 +629,9 @@ public:
protected:
// [Graph]
bool onNodeLoaded(Node* n) override;
private:
void LoadStateTransitions(AnimGraphNode::StateBaseData& data, Value& transitionsData);
};
/// <summary>
@@ -763,13 +714,6 @@ public:
/// </summary>
bool IsReady() const;
/// <summary>
/// Determines whether this graph can be used with the specified skeleton.
/// </summary>
/// <param name="other">The other skinned model to check.</param>
/// <returns>True if can perform the update, otherwise false.</returns>
bool CanUseWithSkeleton(SkinnedModel* other) const;
private:
void ClearCustomNode(Node* node);
bool InitCustomNode(Node* node);
@@ -887,7 +831,6 @@ private:
};
int32 GetRootNodeIndex(Animation* anim);
void ExtractRootMotion(const Animation::NodeToChannel* mapping, int32 rootNodeIndex, Animation* anim, float pos, float prevPos, Transform& rootNode, RootMotionData& rootMotion);
void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed);
void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override);
Variant SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed);
@@ -895,4 +838,5 @@ private:
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);
Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode);
Variant SampleState(AnimGraphNode* state);
void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData);
};

View File

@@ -11,7 +11,7 @@
namespace
{
void BlendAdditiveWeightedRotation(Quaternion& base, Quaternion& additive, float weight)
FORCE_INLINE void BlendAdditiveWeightedRotation(Quaternion& base, Quaternion& additive, float weight)
{
// Pick a shortest path between rotation to fix blending artifacts
additive *= weight;
@@ -19,6 +19,36 @@ namespace
additive *= -1;
base += additive;
}
FORCE_INLINE void NormalizeRotations(AnimGraphImpulse* nodes, RootMotionMode rootMotionMode)
{
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
nodes->Nodes[i].Orientation.Normalize();
}
if (rootMotionMode != RootMotionMode::NoExtraction)
{
nodes->RootMotion.Orientation.Normalize();
}
}
}
void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, Transform& node, int32 i)
{
const int32 nodeToNode = mapping.NodesMapping[i];
if (nodeToNode == -1)
return;
// Map source skeleton node to the target skeleton (use ref pose difference)
const auto& sourceNode = sourceSkeleton.Nodes[nodeToNode];
const auto& targetNode = targetSkeleton.Nodes[i];
Transform value = node;
const Transform sourceToTarget = targetNode.LocalTransform - sourceNode.LocalTransform;
value.Translation += sourceToTarget.Translation;
value.Scale *= sourceToTarget.Scale;
value.Orientation = sourceToTarget.Orientation * value.Orientation; // TODO: find out why this doesn't match referenced animation when played on that skeleton originally
value.Orientation.Normalize();
node = value;
}
int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim)
@@ -40,52 +70,6 @@ int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim)
return rootNodeIndex;
}
void AnimGraphExecutor::ExtractRootMotion(const Animation::NodeToChannel* mapping, int32 rootNodeIndex, Animation* anim, float pos, float prevPos, Transform& rootNode, RootMotionData& rootMotion)
{
const Transform& refPose = GetEmptyNodes()->Nodes[rootNodeIndex];
const int32 nodeToChannel = mapping->At(rootNodeIndex);
if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1)
{
// Get the root bone transformation
Transform rootBefore = refPose;
const NodeAnimationData& rootChannel = anim->Data.Channels[nodeToChannel];
rootChannel.Evaluate(prevPos, &rootBefore, false);
// Check if animation looped
if (pos < prevPos)
{
const float length = anim->GetLength();
const float endPos = length * static_cast<float>(anim->Data.FramesPerSecond);
const float timeToEnd = endPos - prevPos;
Transform rootBegin = refPose;
rootChannel.Evaluate(0, &rootBegin, false);
Transform rootEnd = refPose;
rootChannel.Evaluate(endPos, &rootEnd, false);
//rootChannel.Evaluate(pos - timeToEnd, &rootNow, true);
// Complex motion calculation to preserve the looped movement
// (end - before + now - begin)
// It sums the motion since the last update to anim end and since the start to now
rootMotion.Translation = rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation;
rootMotion.Rotation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated());
//rootMotion.Rotation = Quaternion::Identity;
}
else
{
// Simple motion delta
// (now - before)
rootMotion.Translation = rootNode.Translation - rootBefore.Translation;
rootMotion.Rotation = rootBefore.Orientation.Conjugated() * rootNode.Orientation;
}
}
// Remove root node motion after extraction
rootNode = refPose;
}
void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed)
{
if (anim->Events.Count() == 0)
@@ -185,13 +169,15 @@ float GetAnimPos(float& timePos, float startTimePos, bool loop, float length)
{
// Animation looped
result = Math::Mod(result, length);
// Remove start time offset to properly loop from animation start during the next frame
timePos = result - startTimePos;
}
else
{
// Animation ended
result = length;
timePos = result = length;
}
timePos = result;
}
return result;
}
@@ -257,19 +243,33 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
}
}
// Get skeleton nodes mapping descriptor
const SkinnedModel::SkeletonMapping mapping = _graph.BaseModel->GetSkeletonMapping(anim);
if (mapping.NodesMapping.IsInvalid())
return;
// Evaluate nodes animations
const auto mapping = anim->GetMapping(_graph.BaseModel);
const bool weighted = weight < 1.0f;
const bool retarget = mapping.SourceSkeleton && mapping.SourceSkeleton != mapping.TargetSkeleton;
const auto emptyNodes = GetEmptyNodes();
SkinnedModel::SkeletonMapping sourceMapping;
if (retarget)
sourceMapping = _graph.BaseModel->GetSkeletonMapping(mapping.SourceSkeleton);
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
const int32 nodeToChannel = mapping->At(i);
const int32 nodeToChannel = mapping.NodesMapping[i];
Transform& dstNode = nodes->Nodes[i];
Transform srcNode = emptyNodes->Nodes[i];
if (nodeToChannel != -1)
{
// Calculate the animated node transformation
anim->Data.Channels[nodeToChannel].Evaluate(animPos, &srcNode, false);
// Optionally retarget animation into the skeleton used by the Anim Graph
if (retarget)
{
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, i);
}
}
// Blend node
@@ -302,26 +302,76 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
{
// Calculate the root motion node transformation
const int32 rootNodeIndex = GetRootNodeIndex(anim);
Transform rootNode = emptyNodes->Nodes[rootNodeIndex];
RootMotionData& dstNode = nodes->RootMotion;
RootMotionData srcNode(rootNode);
ExtractRootMotion(mapping, rootNodeIndex, anim, animPos, animPrevPos, rootNode, srcNode);
const Transform& refPose = emptyNodes->Nodes[rootNodeIndex];
Transform& rootNode = nodes->Nodes[rootNodeIndex];
Transform& dstNode = nodes->RootMotion;
Transform srcNode = Transform::Identity;
const int32 nodeToChannel = mapping.NodesMapping[rootNodeIndex];
if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1)
{
// Get the root bone transformation
Transform rootBefore = refPose;
const NodeAnimationData& rootChannel = anim->Data.Channels[nodeToChannel];
rootChannel.Evaluate(animPrevPos, &rootBefore, false);
// Check if animation looped
if (animPos < animPrevPos)
{
const float endPos = anim->GetLength() * static_cast<float>(anim->Data.FramesPerSecond);
const float timeToEnd = endPos - animPrevPos;
Transform rootBegin = refPose;
rootChannel.Evaluate(0, &rootBegin, false);
Transform rootEnd = refPose;
rootChannel.Evaluate(endPos, &rootEnd, false);
//rootChannel.Evaluate(animPos - timeToEnd, &rootNow, true);
// Complex motion calculation to preserve the looped movement
// (end - before + now - begin)
// It sums the motion since the last update to anim end and since the start to now
srcNode.Translation = rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation;
srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated());
//srcNode.Orientation = Quaternion::Identity;
}
else
{
// Simple motion delta
// (now - before)
srcNode.Translation = rootNode.Translation - rootBefore.Translation;
srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation;
}
// Convert root motion from local-space to the actor-space (eg. if root node is not actually a root and its parents have rotation/scale)
auto& skeleton = _graph.BaseModel->Skeleton;
int32 parentIndex = skeleton.Nodes[rootNodeIndex].ParentIndex;
while (parentIndex != -1)
{
const Transform& parentNode = nodes->Nodes[parentIndex];
srcNode.Translation = parentNode.LocalToWorld(srcNode.Translation);
parentIndex = skeleton.Nodes[parentIndex].ParentIndex;
}
}
// Remove root node motion after extraction
rootNode = refPose;
// Blend root motion
if (mode == ProcessAnimationMode::BlendAdditive)
{
dstNode.Translation += srcNode.Translation * weight;
BlendAdditiveWeightedRotation(dstNode.Rotation, srcNode.Rotation, weight);
BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight);
}
else if (mode == ProcessAnimationMode::Add)
{
dstNode.Translation += srcNode.Translation * weight;
dstNode.Rotation += srcNode.Rotation * weight;
dstNode.Orientation += srcNode.Orientation * weight;
}
else if (weighted)
{
dstNode.Translation = srcNode.Translation * weight;
dstNode.Rotation = srcNode.Rotation * weight;
dstNode.Orientation = srcNode.Orientation * weight;
}
else
{
@@ -349,6 +399,7 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float
nodes->Position = pos;
nodes->Length = length;
ProcessAnimation(nodes, node, loop, length, pos, prevPos, anim, speed);
NormalizeRotations(nodes, _rootMotionMode);
return nodes;
}
@@ -370,16 +421,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
nodes->Length = length;
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animA, speedA, 1.0f - alpha, ProcessAnimationMode::Override);
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animB, speedB, alpha, ProcessAnimationMode::BlendAdditive);
// Normalize rotations
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
nodes->Nodes[i].Orientation.Normalize();
}
if (_rootMotionMode != RootMotionMode::NoExtraction)
{
nodes->RootMotion.Rotation.Normalize();
}
NormalizeRotations(nodes, _rootMotionMode);
return nodes;
}
@@ -404,16 +446,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animA, speedA, alphaA, ProcessAnimationMode::Override);
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animB, speedB, alphaB, ProcessAnimationMode::BlendAdditive);
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animC, speedC, alphaC, ProcessAnimationMode::BlendAdditive);
// Normalize rotations
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
nodes->Nodes[i].Orientation.Normalize();
}
if (_rootMotionMode != RootMotionMode::NoExtraction)
{
nodes->RootMotion.Rotation.Normalize();
}
NormalizeRotations(nodes, _rootMotionMode);
return nodes;
}
@@ -437,7 +470,7 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
{
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
}
RootMotionData::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
nodes->Position = Math::Lerp(nodesA->Position, nodesB->Position, alpha);
nodes->Length = Math::Lerp(nodesA->Length, nodesB->Length, alpha);
@@ -463,6 +496,79 @@ Variant AnimGraphExecutor::SampleState(AnimGraphNode* state)
return result;
}
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
{
int32 transitionIndex = 0;
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
{
const uint16 idx = stateData.Transitions[transitionIndex];
ASSERT(idx < stateMachineData.Graph->StateTransitions.Count());
auto& transition = stateMachineData.Graph->StateTransitions[idx];
if (transition.Destination == stateMachineBucket.CurrentState)
{
// Ignore transition to the current state
transitionIndex++;
continue;
}
const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
if (transition.RuleGraph && !useDefaultRule)
{
// Execute transition rule
auto rootNode = transition.RuleGraph->GetRootNode();
ASSERT(rootNode);
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
{
transitionIndex++;
continue;
}
}
// Evaluate source state transition data (position, length, etc.)
const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState);
auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
{
// Use source state as data provider
const auto sourceState = (AnimGraphImpulse*)sourceStatePtr.AsPointer;
auto sourceLength = Math::Max(sourceState->Length, 0.0f);
transitionData.Position = Math::Clamp(sourceState->Position, 0.0f, sourceLength);
transitionData.Length = sourceLength;
}
else
{
// Reset
transitionData.Position = 0;
transitionData.Length = ZeroTolerance;
}
// Check if can trigger the transition
bool canEnter = false;
if (useDefaultRule)
{
// Start transition when the current state animation is about to end (split blend duration evenly into two states)
const auto transitionDurationHalf = transition.BlendDuration * 0.5f + ZeroTolerance;
const auto endPos = transitionData.Length - transitionDurationHalf;
canEnter = transitionData.Position >= endPos;
}
else if (transition.RuleGraph)
canEnter = true;
if (canEnter)
{
// Start transition
stateMachineBucket.ActiveTransition = &transition;
stateMachineBucket.TransitionPosition = 0.0f;
break;
}
// Skip after Solo transition
// TODO: don't load transitions after first enabled Solo transition and remove this check here
if (EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::Solo))
break;
transitionIndex++;
}
}
void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
{
ANIM_GRAPH_PROFILE_EVENT("Setup Mutli Blend Length");
@@ -643,9 +749,6 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
{
const auto anim = node->Assets[0].As<Animation>();
auto& bucket = context.Data->State[node->BucketIndex].Animation;
const float speed = (float)tryGetValue(node->GetBox(5), node->Values[1]);
const bool loop = (bool)tryGetValue(node->GetBox(6), node->Values[2]);
const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
switch (box->ID)
{
@@ -653,6 +756,9 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
case 0:
{
ANIM_GRAPH_PROFILE_EVENT("Animation");
const float speed = (float)tryGetValue(node->GetBox(5), node->Values[1]);
const bool loop = (bool)tryGetValue(node->GetBox(6), node->Values[2]);
const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
const float length = anim ? anim->GetLength() : 0.0f;
// Calculate new time position
@@ -672,14 +778,20 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
}
// Normalized Time
case 1:
{
const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
value = startTimePos + bucket.TimePosition;
if (anim && anim->IsLoaded())
value.AsFloat /= anim->GetLength();
break;
}
// Time
case 2:
{
const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
value = startTimePos + bucket.TimePosition;
break;
}
// Length
case 3:
value = anim ? anim->GetLength() : 0.0f;
@@ -912,7 +1024,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
{
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
}
RootMotionData::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
value = nodes;
}
@@ -958,7 +1070,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
t.Orientation.Normalize();
Transform::Lerp(tA, t, alpha, nodes->Nodes[i]);
}
RootMotionData::Lerp(nodesA->RootMotion, nodesA->RootMotion + nodesB->RootMotion, alpha, nodes->RootMotion);
Transform::Lerp(nodesA->RootMotion, nodesA->RootMotion + nodesB->RootMotion, alpha, nodes->RootMotion);
value = nodes;
}
}
@@ -1006,7 +1118,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
nodes->Nodes[nodeIndex] = tA;
}
}
RootMotionData::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
value = nodes;
}
@@ -1391,7 +1503,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
value = poseData->RootMotion.Translation;
break;
case 1:
value = poseData->RootMotion.Rotation;
value = poseData->RootMotion.Orientation;
break;
}
}
@@ -1423,7 +1535,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
auto nodes = node->GetNodes(this);
nodes->Nodes = poseData->Nodes;
nodes->RootMotion.Translation = (Vector3)tryGetValue(node->GetBox(2), Value::Zero);
nodes->RootMotion.Rotation = (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
nodes->RootMotion.Orientation = (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
value = nodes;
break;
}
@@ -1441,19 +1553,18 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
auto nodes = node->GetNodes(this);
nodes->Nodes = poseData->Nodes;
nodes->RootMotion.Translation = poseData->RootMotion.Translation + (Vector3)tryGetValue(node->GetBox(2), Value::Zero);
nodes->RootMotion.Rotation = poseData->RootMotion.Rotation * (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
nodes->RootMotion.Orientation = poseData->RootMotion.Orientation * (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
value = nodes;
break;
}
// State Machine
case 18:
{
ANIM_GRAPH_PROFILE_EVENT("State Machine");
const int32 maxTransitionsPerUpdate = node->Values[2].AsInt;
const bool reinitializeOnBecomingRelevant = node->Values[3].AsBool;
const bool skipFirstUpdateTransition = node->Values[4].AsBool;
ANIM_GRAPH_PROFILE_EVENT("State Machine");
// Prepare
auto& bucket = context.Data->State[node->BucketIndex].StateMachine;
auto& data = node->Data.StateMachine;
@@ -1485,6 +1596,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Reset all state buckets pof the graphs and nodes included inside the state machine
ResetBuckets(context, data.Graph);
}
#define END_TRANSITION() \
ResetBuckets(context, bucket.CurrentState->Data.State.Graph); \
bucket.CurrentState = bucket.ActiveTransition->Destination; \
bucket.ActiveTransition = nullptr; \
bucket.TransitionPosition = 0.0f
// Update the active transition
if (bucket.ActiveTransition)
@@ -1494,95 +1610,66 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Check for transition end
if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration)
{
// End transition
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
bucket.CurrentState = bucket.ActiveTransition->Destination;
bucket.ActiveTransition = nullptr;
bucket.TransitionPosition = 0.0f;
END_TRANSITION();
}
// Check for transition interruption
else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking))
{
const bool useDefaultRule = EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
if (bucket.ActiveTransition->RuleGraph && !useDefaultRule)
{
// Execute transition rule
auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode();
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
{
bool cancelTransition = false;
if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
{
cancelTransition = true;
}
else
{
// Blend back to the source state (remove currently applied delta and rewind transition)
bucket.TransitionPosition -= context.DeltaTime;
bucket.TransitionPosition -= context.DeltaTime;
if (bucket.TransitionPosition <= ZeroTolerance)
{
cancelTransition = true;
}
}
if (cancelTransition)
{
// Go back to the source state
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
bucket.ActiveTransition = nullptr;
bucket.TransitionPosition = 0.0f;
}
}
}
}
}
ASSERT(bucket.CurrentState && bucket.CurrentState->GroupID == 9 && bucket.CurrentState->TypeID == 20);
ASSERT(bucket.CurrentState && bucket.CurrentState->Type == GRAPH_NODE_MAKE_TYPE(9, 20));
// Update transitions
// Note: this logic assumes that all transitions are sorted by Order property and Enabled
// Note: this logic assumes that all transitions are sorted by Order property and Enabled (by Editor when saving Anim Graph asset)
while (!bucket.ActiveTransition && transitionsLeft-- > 0)
{
// Check if can change the current state
const auto& stateData = bucket.CurrentState->Data.State;
int32 transitionIndex = 0;
while (stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex
&& transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS)
// State transitions
UpdateStateTransitions(context, data, bucket, bucket.CurrentState->Data.State);
// Any state transitions
// TODO: cache Any state nodes inside State Machine to optimize the loop below
for (const AnimGraphNode& anyStateNode : data.Graph->Nodes)
{
const uint16 idx = stateData.Transitions[transitionIndex];
ASSERT(idx < data.Graph->StateTransitions.Count());
auto& transition = data.Graph->StateTransitions[idx];
const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
if (transition.RuleGraph && !useDefaultRule)
{
// Execute transition rule
auto rootNode = transition.RuleGraph->GetRootNode();
ASSERT(rootNode);
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
{
transitionIndex++;
continue;
}
}
// Evaluate source state transition data (position, length, etc.)
const Value sourceStatePtr = SampleState(bucket.CurrentState);
auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
{
// Use source state as data provider
const auto sourceState = (AnimGraphImpulse*)sourceStatePtr.AsPointer;
auto sourceLength = Math::Max(sourceState->Length, 0.0f);
transitionData.Position = Math::Clamp(sourceState->Position, 0.0f, sourceLength);
transitionData.Length = sourceLength;
}
else
{
// Reset
transitionData.Position = 0;
transitionData.Length = ZeroTolerance;
}
// Check if can trigger the transition
bool canEnter = false;
if (useDefaultRule)
{
// Start transition when the current state animation is about to end (split blend duration evenly into two states)
const auto transitionDurationHalf = transition.BlendDuration * 0.5f + ZeroTolerance;
const auto endPos = transitionData.Length - transitionDurationHalf;
canEnter = transitionData.Position >= endPos;
}
else if (transition.RuleGraph)
canEnter = true;
if (canEnter)
{
// Start transition
bucket.ActiveTransition = &transition;
bucket.TransitionPosition = 0.0f;
break;
}
// Skip after Solo transition
// TODO: don't load transitions after first enabled Solo transition and remove this check here
if (EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::Solo))
break;
transitionIndex++;
if (anyStateNode.Type == GRAPH_NODE_MAKE_TYPE(9, 34))
UpdateStateTransitions(context, data, bucket, anyStateNode.Data.AnyState);
}
// Check for instant transitions
if (bucket.ActiveTransition && bucket.ActiveTransition->BlendDuration <= ZeroTolerance)
{
// End transition
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
bucket.CurrentState = bucket.ActiveTransition->Destination;
bucket.ActiveTransition = nullptr;
bucket.TransitionPosition = 0.0f;
END_TRANSITION();
}
}
@@ -1603,18 +1690,16 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Update bucket
bucket.LastUpdateFrame = context.CurrentFrameIndex;
#undef END_TRANSITION
break;
}
// Entry
case 19:
{
// Not used
CRASH;
break;
}
// State
case 20:
// Any State
case 34:
{
// Not used
CRASH;
@@ -1622,8 +1707,6 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
}
// State Output
case 21:
value = box->HasConnection() ? eatBox(nodeBase, box->FirstConnection()) : Value::Null;
break;
// Rule Output
case 22:
value = box->HasConnection() ? eatBox(nodeBase, box->FirstConnection()) : Value::Null;

View File

@@ -13,12 +13,12 @@
#include "Engine/Renderer/RenderList.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Scripting/MException.h"
#include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Scripting/ManagedCLR/MProperty.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "Engine/Scripting/ManagedCLR/MType.h"
#include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
// This could be Update, LateUpdate or FixedUpdate
#define UPDATE_POINT Update
@@ -260,7 +260,7 @@ void SceneAnimationPlayer::MapTrack(const StringView& from, const Guid& to)
void SceneAnimationPlayer::Restore(SceneAnimation* anim, int32 stateIndexOffset)
{
#if USE_MONO
#if USE_CSHARP
// Restore all tracks
for (int32 j = 0; j < anim->Tracks.Count(); j++)
{
@@ -364,7 +364,7 @@ void SceneAnimationPlayer::Restore(SceneAnimation* anim, int32 stateIndexOffset)
bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexOffset, SceneAnimation* anim, float time, const SceneAnimation::Track& track, TrackInstance& state, void* target)
{
#if USE_MONO
#if USE_CSHARP
switch (track.Type)
{
case SceneAnimation::Track::Types::KeyframesProperty:
@@ -511,7 +511,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
// Return the value
StringView str(keyframesValues[leftKey], keyframesLengths[leftKey]);
*(MonoString**)target = MUtils::ToString(str);
*(MString**)target = MUtils::ToString(str);
break;
}
case SceneAnimation::Track::Types::StructProperty:
@@ -528,10 +528,10 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
// Cache field
if (!childTrackState.Field)
{
MType type = state.Property ? state.Property->GetType() : (state.Field ? state.Field->GetType() : MType());
MType* type = state.Property ? state.Property->GetType() : (state.Field ? state.Field->GetType() : nullptr);
if (!type)
continue;
MClass* mclass = Scripting::FindClass(mono_type_get_class(type.GetNative()));
MClass* mclass = MCore::Type::GetClass(type);
childTrackState.Field = mclass->GetField(childTrackRuntimeData->PropertyName);
if (!childTrackState.Field)
continue;
@@ -556,7 +556,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int32 stateIndexOffset, CallStack& callStack)
{
#if USE_MONO
#if USE_CSHARP
const float fps = anim->FramesPerSecond;
#if !BUILD_RELEASE || USE_EDITOR
callStack.Add(anim);
@@ -873,7 +873,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
// Cache property or field
if (!state.Property && !state.Field)
{
MClass* mclass = Scripting::FindClass(mono_object_get_class(instance));
MClass* mclass = MCore::Object::GetClass(instance);
state.Property = mclass->GetProperty(runtimeData->PropertyName);
if (!state.Property)
{
@@ -886,9 +886,8 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
}
// Get stack memory for data value
MType valueType = state.Property ? state.Property->GetType() : state.Field->GetType();
int32 valueAlignment;
int32 valueSize = mono_type_stack_size(valueType.GetNative(), &valueAlignment);
MType* valueType = state.Property ? state.Property->GetType() : state.Field->GetType();
int32 valueSize = MCore::Type::GetSize(valueType);
_tracksDataStack.AddDefault(valueSize);
void* value = &_tracksDataStack[_tracksDataStack.Count() - valueSize];
@@ -906,10 +905,10 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
MException ex(exception);
ex.Log(LogType::Error, TEXT("Property"));
}
else if (!valueType.IsPointer())
else if (!MCore::Type::IsPointer(valueType))
{
if (boxed)
Platform::MemoryCopy(value, mono_object_unbox(boxed), valueSize);
Platform::MemoryCopy(value, MCore::Object::Unbox(boxed), valueSize);
else
Platform::MemoryClear(value, valueSize);
}
@@ -932,7 +931,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
case SceneAnimation::Track::Types::StringProperty:
{
StringView str;
MUtils::ToString(*(MonoString**)value, str);
MUtils::ToString(*(MString**)value, str);
_restoreData.Add((byte*)str.Get(), str.Length());
_restoreData.Add('\0');
break;
@@ -957,7 +956,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
if (TickPropertyTrack(j, stateIndexOffset, anim, time, track, state, value))
{
// Set the value
if (valueType.IsPointer())
if (MCore::Type::IsPointer(valueType))
value = (void*)*(intptr*)value;
if (state.Property)
{
@@ -1027,7 +1026,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
// Cache method
if (!state.Method)
{
state.Method = mono_class_get_method_from_name(mono_object_get_class(instance), runtimeData->EventName, runtimeData->EventParamsCount);
state.Method = MCore::Object::GetClass(instance)->FindMethod(runtimeData->EventName, runtimeData->EventParamsCount);
if (!state.Method)
break;
}
@@ -1035,7 +1034,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
// Invoke the method
Variant result;
MObject* exception = nullptr;
mono_runtime_invoke((MonoMethod*)state.Method, instance, paramsData, &exception);
state.Method->Invoke(instance, paramsData, &exception);
if (exception)
{
MException ex(exception);

View File

@@ -46,7 +46,7 @@ private:
MObject* ManagedObject = nullptr;
MProperty* Property = nullptr;
MField* Field = nullptr;
void* Method = nullptr;
MMethod* Method = nullptr;
int32 RestoreStateIndex = -1;
bool Warn = true;

View File

@@ -57,6 +57,9 @@ public class Audio : EngineModule
case TargetPlatform.Mac:
useOpenAL = true;
break;
case TargetPlatform.iOS:
useOpenAL = true;
break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
@@ -91,6 +94,11 @@ public class Audio : EngineModule
options.Libraries.Add("AudioUnit.framework");
options.Libraries.Add("AudioToolbox.framework");
break;
case TargetPlatform.iOS:
options.OutputFiles.Add(Path.Combine(depsRoot, "libopenal.a"));
options.Libraries.Add("CoreAudio.framework");
options.Libraries.Add("AudioToolbox.framework");
break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
}

View File

@@ -12,9 +12,7 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Threading/ConcurrentTaskQueue.h"
#if USE_MONO
#include <ThirdParty/mono-2.0/mono/metadata/mono-gc.h>
#endif
#include "Engine/Scripting/ManagedCLR/MCore.h"
AssetReferenceBase::~AssetReferenceBase()
{
@@ -269,9 +267,7 @@ void Asset::OnManagedInstanceDeleted()
// Cleanup
if (_gcHandle)
{
#if USE_MONO
mono_gchandle_free(_gcHandle);
#endif
MCore::GCHandle::Free(_gcHandle);
_gcHandle = 0;
}
@@ -399,6 +395,11 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
return false;
}
// Check if loading failed
Platform::MemoryBarrier();
if (LastLoadFailed())
return true;
// Check if has missing loading task
Platform::MemoryBarrier();
const auto loadingTask = _loadingTask;

View File

@@ -3,7 +3,6 @@
#include "Animation.h"
#include "SkinnedModel.h"
#include "Engine/Core/Log.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Animations/CurveSerialization.h"
#include "Engine/Animations/AnimEvent.h"
@@ -30,9 +29,7 @@ void Animation::OnScriptsReloadStart()
for (auto& e : Events)
{
for (auto& k : e.Second.GetKeyframes())
{
Level::ScriptsReloadRegisterObject((ScriptingObject*&)k.Value.Instance);
}
}
}
@@ -67,72 +64,11 @@ Animation::InfoData Animation::GetInfo() const
}
info.MemoryUsage += Events.Capacity() * sizeof(Pair<String, StepCurve<AnimEventData>>);
info.MemoryUsage += NestedAnims.Capacity() * sizeof(Pair<String, NestedAnimData>);
info.MemoryUsage += MappingCache.Capacity() * (sizeof(void*) + sizeof(NodeToChannel) + 1);
for (auto& e : Events)
info.MemoryUsage += e.Second.GetKeyframes().Capacity() * sizeof(StepCurve<AnimEventData>);
for (auto& e : MappingCache)
info.MemoryUsage += e.Value.Capacity() * sizeof(int32);
return info;
}
void Animation::ClearCache()
{
ScopeLock lock(Locker);
// Unlink events
for (auto i = MappingCache.Begin(); i.IsNotEnd(); ++i)
{
i->Key->OnUnloaded.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
i->Key->OnReloading.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
}
// Free memory
MappingCache.Clear();
MappingCache.SetCapacity(0);
}
const Animation::NodeToChannel* Animation::GetMapping(SkinnedModel* obj)
{
ASSERT(obj && obj->IsLoaded() && IsLoaded());
ScopeLock lock(Locker);
// Try quick lookup
NodeToChannel* result = MappingCache.TryGet(obj);
if (result == nullptr)
{
PROFILE_CPU();
// Add to cache
NodeToChannel tmp;
auto bucket = MappingCache.Add(obj, tmp);
result = &bucket->Value;
obj->OnUnloaded.Bind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
obj->OnReloading.Bind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
// Initialize the mapping
const auto& skeleton = obj->Skeleton;
const int32 nodesCount = skeleton.Nodes.Count();
result->Resize(nodesCount, false);
result->SetAll(-1);
for (int32 i = 0; i < Data.Channels.Count(); i++)
{
auto& nodeAnim = Data.Channels[i];
for (int32 j = 0; j < nodesCount; j++)
{
if (skeleton.Nodes[j].Name == nodeAnim.NodeName)
{
result->At(j) = i;
break;
}
}
}
}
return result;
}
#if USE_EDITOR
void Animation::LoadTimeline(BytesContainer& result) const
@@ -552,23 +488,6 @@ bool Animation::Save(const StringView& path)
#endif
void Animation::OnSkinnedModelUnloaded(Asset* obj)
{
ScopeLock lock(Locker);
const auto key = static_cast<SkinnedModel*>(obj);
auto i = MappingCache.Find(key);
ASSERT(i != MappingCache.End());
// Unlink event
key->OnUnloaded.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
key->OnReloading.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
// Clear cache
i->Value.Resize(0, false);
MappingCache.Remove(i);
}
uint64 Animation::GetMemoryUsage() const
{
Locker.Lock();
@@ -579,9 +498,6 @@ uint64 Animation::GetMemoryUsage() const
for (const auto& e : Events)
result += e.First.Length() * sizeof(Char) + e.Second.GetMemoryUsage();
result += NestedAnims.Capacity() * sizeof(Pair<String, NestedAnimData>);
result += MappingCache.Capacity() * sizeof(Pair<String, StepCurve<AnimEventData>>);
for (const auto& e : MappingCache)
result += e.Value.Capacity() * sizeof(int32);
Locker.Unlock();
return result;
}
@@ -738,7 +654,6 @@ void Animation::unload(bool isReloading)
Level::ScriptsReloadStart.Unbind<Animation, &Animation::OnScriptsReloadStart>(this);
}
#endif
ClearCache();
Data.Dispose();
for (const auto& e : Events)
{

View File

@@ -98,17 +98,6 @@ public:
/// </summary>
Array<Pair<String, NestedAnimData>> NestedAnims;
/// <summary>
/// Contains the mapping for every skeleton node to the animation data channels.
/// Can be used for a simple lookup or to check if a given node is animated (unused nodes are using -1 index).
/// </summary>
typedef Array<int32> NodeToChannel;
/// <summary>
/// The skeleton nodes to animation channel indices mapping cache. Use it as read-only. It's being maintained internally by the asset.
/// </summary>
Dictionary<SkinnedModel*, NodeToChannel> MappingCache;
public:
/// <summary>
/// Gets the length of the animation (in seconds).
@@ -139,18 +128,6 @@ public:
/// </summary>
API_PROPERTY() InfoData GetInfo() const;
/// <summary>
/// Clears the skeleton mapping cache.
/// </summary>
void ClearCache();
/// <summary>
/// Clears the skeleton mapping cache.
/// </summary>
/// <param name="obj">The target skinned model to get mapping to its skeleton.</param>
/// <returns>The cached node-to-channel mapping for the fast animation sampling for the skinned model skeleton nodes.</returns>
const NodeToChannel* GetMapping(SkinnedModel* obj);
#if USE_EDITOR
/// <summary>
/// Gets the animation as serialized timeline data. Used to show it in Editor.
@@ -174,9 +151,6 @@ public:
bool Save(const StringView& path = StringView::Empty);
#endif
private:
void OnSkinnedModelUnloaded(Asset* obj);
public:
// [BinaryAsset]
uint64 GetMemoryUsage() const override;

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "SkinnedModel.h"
#include "Animation.h"
#include "Engine/Core/Log.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Serialization/MemoryReadStream.h"
@@ -11,10 +12,12 @@
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
#include "Engine/Graphics/Models/Config.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/WeakAssetReference.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h"
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/DrawCall.h"
#define CHECK_INVALID_BUFFER(model, buffer) \
@@ -116,6 +119,7 @@ SkinnedModel::SkinnedModel(const SpawnParams& params, const AssetInfo* info)
SkinnedModel::~SkinnedModel()
{
ASSERT(_streamingTask == nullptr);
ASSERT(_skeletonMappingCache.Count() == 0);
}
bool SkinnedModel::HasAnyLODInitialized() const
@@ -152,23 +156,165 @@ void SkinnedModel::GetLODData(int32 lodIndex, BytesContainer& data) const
GetChunkData(chunkIndex, data);
}
SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source)
{
SkeletonMapping mapping;
mapping.TargetSkeleton = this;
if (WaitForLoaded() || !source || source->WaitForLoaded())
return mapping;
ScopeLock lock(Locker);
SkeletonMappingData mappingData;
if (!_skeletonMappingCache.TryGet(source, mappingData))
{
PROFILE_CPU();
// Initialize the mapping
const int32 nodesCount = Skeleton.Nodes.Count();
mappingData.NodesMapping = Span<int32>((int32*)Allocator::Allocate(nodesCount * sizeof(int32)), nodesCount);
for (int32 i = 0; i < nodesCount; i++)
mappingData.NodesMapping[i] = -1;
SkeletonRetarget* retarget = nullptr;
const Guid sourceId = source->GetID();
for (auto& e : _skeletonRetargets)
{
if (e.SourceAsset == sourceId)
{
retarget = &e;
break;
}
}
if (const auto* sourceAnim = Cast<Animation>(source))
{
const auto& channels = sourceAnim->Data.Channels;
if (retarget && retarget->SkeletonAsset)
{
// Map retarget skeleton nodes from animation channels
if (auto* skeleton = Content::Load<SkinnedModel>(retarget->SkeletonAsset))
{
const SkeletonMapping skeletonMapping = GetSkeletonMapping(skeleton);
mappingData.SourceSkeleton = skeleton;
if (skeletonMapping.NodesMapping.Length() == nodesCount)
{
const auto& nodes = skeleton->Skeleton.Nodes;
for (int32 j = 0; j < nodesCount; j++)
{
if (skeletonMapping.NodesMapping[j] != -1)
{
const Char* nodeName = nodes[skeletonMapping.NodesMapping[j]].Name.GetText();
for (int32 i = 0; i < channels.Count(); i++)
{
if (StringUtils::CompareIgnoreCase(nodeName, channels[i].NodeName.GetText()) == 0)
{
mappingData.NodesMapping[j] = i;
break;
}
}
}
}
}
}
else
{
#if !BUILD_RELEASE
LOG(Error, "Missing asset {0} to use for skeleton mapping of {1}", retarget->SkeletonAsset, ToString());
#endif
return mapping;
}
}
else
{
// Map animation channels to the skeleton nodes (by name)
for (int32 i = 0; i < channels.Count(); i++)
{
const NodeAnimationData& nodeAnim = channels[i];
for (int32 j = 0; j < nodesCount; j++)
{
if (StringUtils::CompareIgnoreCase(Skeleton.Nodes[j].Name.GetText(), nodeAnim.NodeName.GetText()) == 0)
{
mappingData.NodesMapping[j] = i;
break;
}
}
}
}
}
else if (const auto* sourceModel = Cast<SkinnedModel>(source))
{
if (retarget)
{
// Use nodes retargeting
for (const auto& e : retarget->NodesMapping)
{
const int32 dstIndex = Skeleton.FindNode(e.Key);
const int32 srcIndex = sourceModel->Skeleton.FindNode(e.Value);
if (dstIndex != -1 && srcIndex != -1)
{
mappingData.NodesMapping[dstIndex] = srcIndex;
}
}
}
else
{
// Map source skeleton nodes to the target skeleton nodes (by name)
const auto& nodes = sourceModel->Skeleton.Nodes;
for (int32 i = 0; i < nodes.Count(); i++)
{
const SkeletonNode& node = nodes[i];
for (int32 j = 0; j < nodesCount; j++)
{
if (StringUtils::CompareIgnoreCase(Skeleton.Nodes[j].Name.GetText(), node.Name.GetText()) == 0)
{
mappingData.NodesMapping[j] = i;
break;
}
}
}
}
}
else
{
#if !BUILD_RELEASE
LOG(Error, "Invalid asset type {0} to use for skeleton mapping of {1}", source->GetTypeName(), ToString());
#endif
}
// Add to cache
_skeletonMappingCache.Add(source, mappingData);
source->OnUnloaded.Bind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
#if USE_EDITOR
source->OnReloading.Bind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
#endif
}
mapping.SourceSkeleton = mappingData.SourceSkeleton;
mapping.NodesMapping = mappingData.NodesMapping;
return mapping;
}
bool SkinnedModel::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh, int32 lodIndex)
{
if (LODs.Count() == 0)
return false;
return LODs[lodIndex].Intersects(ray, world, distance, normal, mesh);
}
bool SkinnedModel::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh, int32 lodIndex)
{
if (LODs.Count() == 0)
return false;
return LODs[lodIndex].Intersects(ray, transform, distance, normal, mesh);
}
BoundingBox SkinnedModel::GetBox(const Matrix& world, int32 lodIndex) const
{
if (LODs.Count() == 0)
return BoundingBox::Zero;
return LODs[lodIndex].GetBox(world);
}
BoundingBox SkinnedModel::GetBox(int32 lodIndex) const
{
if (LODs.Count() == 0)
return BoundingBox::Zero;
return LODs[lodIndex].GetBox();
}
@@ -305,10 +451,8 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes)
ScopeLock lock(model->Locker);
// Setup nodes
// Setup
model->Skeleton.Nodes = nodes;
// Setup bones
model->Skeleton.Bones.Resize(nodes.Count());
for (int32 i = 0; i < nodes.Count(); i++)
{
@@ -317,6 +461,7 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes)
model->Skeleton.Bones[i].LocalTransform = node.LocalTransform;
model->Skeleton.Bones[i].NodeIndex = i;
}
ClearSkeletonMapping();
// Calculate offset matrix (inverse bind pose transform) for every bone manually
for (int32 i = 0; i < model->Skeleton.Bones.Count(); i++)
@@ -351,11 +496,10 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
ScopeLock lock(model->Locker);
// Setup nodes
// Setup
model->Skeleton.Nodes = nodes;
// Setup bones
model->Skeleton.Bones = bones;
ClearSkeletonMapping();
// Calculate offset matrix (inverse bind pose transform) for every bone manually
if (autoCalculateOffsetMatrix)
@@ -412,6 +556,9 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
MemoryWriteStream headerStream(1024);
MemoryWriteStream* stream = &headerStream;
{
// Header Version
stream->WriteByte(1);
// Min Screen Size
stream->WriteFloat(MinScreenSize);
@@ -499,6 +646,17 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
stream->Write(bone.OffsetMatrix);
}
}
// Retargeting
{
stream->WriteInt32(_skeletonRetargets.Count());
for (const auto& retarget : _skeletonRetargets)
{
stream->Write(retarget.SourceAsset);
stream->Write(retarget.SkeletonAsset);
stream->Write(retarget.NodesMapping);
}
}
}
// Use a temporary chunks for data storage for virtual assets
@@ -552,8 +710,6 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
task->Start();
tasks.Add(task);
}
// Wait for all
if (Task::WaitAll(tasks))
return true;
tasks.Clear();
@@ -562,12 +718,10 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
{
int32 dataSize = meshesCount * (2 * sizeof(uint32) + sizeof(bool));
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
dataSize += meshesData[meshIndex].DataSize();
}
MemoryWriteStream meshesStream(dataSize);
MemoryWriteStream meshesStream(Math::RoundUpToPowerOf2(dataSize));
meshesStream.WriteByte(1);
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
const auto& mesh = lod.Meshes[meshIndex];
@@ -597,10 +751,10 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
return true;
}
// #MODEL_DATA_FORMAT_USAGE
meshesStream.WriteUint32(vertices);
meshesStream.WriteUint32(triangles);
meshesStream.WriteUint16(mesh.BlendShapes.Count());
for (const auto& blendShape : mesh.BlendShapes)
{
meshesStream.WriteBool(blendShape.UseNormals);
@@ -609,9 +763,7 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
meshesStream.WriteUint32(blendShape.Vertices.Count());
meshesStream.WriteBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
}
meshesStream.WriteBytes(meshData.VB0.Get(), vb0Size);
if (shouldUse16BitIndexBuffer == use16BitIndexBuffer)
{
meshesStream.WriteBytes(meshData.IB.Get(), ibSize);
@@ -690,11 +842,10 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
// Setup
MaterialSlots.Resize(1);
MinScreenSize = 0.0f;
// Setup LODs
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
LODs[lodIndex].Dispose();
LODs.Resize(meshesCountPerLod.Length());
_initialized = true;
// Setup meshes
for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++)
@@ -720,6 +871,49 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
return false;
}
void SkinnedModel::ClearSkeletonMapping()
{
for (auto& e : _skeletonMappingCache)
{
e.Key->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
#if USE_EDITOR
e.Key->OnReloading.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
#endif
Allocator::Free(e.Value.NodesMapping.Get());
}
_skeletonMappingCache.Clear();
}
void SkinnedModel::OnSkeletonMappingSourceAssetUnloaded(Asset* obj)
{
ScopeLock lock(Locker);
auto i = _skeletonMappingCache.Find(obj);
ASSERT(i != _skeletonMappingCache.End());
// Unlink event
obj->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
#if USE_EDITOR
obj->OnReloading.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
#endif
// Clear cache
Allocator::Free(i->Value.NodesMapping.Get());
_skeletonMappingCache.Remove(i);
}
uint64 SkinnedModel::GetMemoryUsage() const
{
Locker.Lock();
uint64 result = BinaryAsset::GetMemoryUsage();
result += sizeof(SkinnedModel) - sizeof(BinaryAsset);
result += Skeleton.GetMemoryUsage();
result += _skeletonMappingCache.Capacity() * sizeof(Dictionary<Asset*, Span<int32>>::Bucket);
for (const auto& e : _skeletonMappingCache)
result += e.Value.NodesMapping.Length();
Locker.Unlock();
return result;
}
void SkinnedModel::SetupMaterialSlots(int32 slotsCount)
{
ModelBase::SetupMaterialSlots(slotsCount);
@@ -754,6 +948,7 @@ void SkinnedModel::InitAsVirtual()
// Init with one mesh and single bone
int32 meshesCount = 1;
Init(ToSpan(&meshesCount, 1));
ClearSkeletonMapping();
Skeleton.Dispose();
//
Skeleton.Nodes.Resize(1);
@@ -874,13 +1069,16 @@ Asset::LoadResult SkinnedModel::load()
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
ReadStream* stream = &headerStream;
// Header Version
byte version = stream->ReadByte();
// Min Screen Size
stream->ReadFloat(&MinScreenSize);
// Amount of material slots
int32 materialSlotsCount;
stream->ReadInt32(&materialSlotsCount);
if (materialSlotsCount <= 0 || materialSlotsCount > 4096)
if (materialSlotsCount < 0 || materialSlotsCount > 4096)
return LoadResult::InvalidData;
MaterialSlots.Resize(materialSlotsCount, false);
@@ -904,9 +1102,10 @@ Asset::LoadResult SkinnedModel::load()
// Amount of LODs
byte lods;
stream->ReadByte(&lods);
if (lods == 0 || lods > MODEL_MAX_LODS)
if (lods > MODEL_MAX_LODS)
return LoadResult::InvalidData;
LODs.Resize(lods);
_initialized = true;
// For each LOD
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
@@ -972,12 +1171,9 @@ Asset::LoadResult SkinnedModel::load()
if (nodesCount <= 0)
return LoadResult::InvalidData;
Skeleton.Nodes.Resize(nodesCount, false);
// For each node
for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++)
{
auto& node = Skeleton.Nodes[nodeIndex];
auto& node = Skeleton.Nodes.Get()[nodeIndex];
stream->Read(node.ParentIndex);
stream->ReadTransform(&node.LocalTransform);
stream->ReadString(&node.Name, 71);
@@ -988,12 +1184,9 @@ Asset::LoadResult SkinnedModel::load()
if (bonesCount <= 0)
return LoadResult::InvalidData;
Skeleton.Bones.Resize(bonesCount, false);
// For each bone
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
{
auto& bone = Skeleton.Bones[boneIndex];
auto& bone = Skeleton.Bones.Get()[boneIndex];
stream->Read(bone.ParentIndex);
stream->Read(bone.NodeIndex);
stream->ReadTransform(&bone.LocalTransform);
@@ -1001,6 +1194,20 @@ Asset::LoadResult SkinnedModel::load()
}
}
// Retargeting
{
int32 entriesCount;
stream->ReadInt32(&entriesCount);
_skeletonRetargets.Resize(entriesCount);
for (int32 entryIndex = 0; entryIndex < entriesCount; entryIndex++)
{
auto& retarget = _skeletonRetargets[entryIndex];
stream->Read(retarget.SourceAsset);
stream->Read(retarget.SkeletonAsset);
stream->Read(retarget.NodesMapping);
}
}
// Request resource streaming
StartStreaming(true);
@@ -1023,7 +1230,10 @@ void SkinnedModel::unload(bool isReloading)
LODs[i].Dispose();
LODs.Clear();
Skeleton.Dispose();
_initialized = false;
_loadedLODs = 0;
_skeletonRetargets.Clear();
ClearSkeletonMapping();
}
bool SkinnedModel::init(AssetInitData& initData)

View File

@@ -14,12 +14,32 @@ class StreamSkinnedModelLODTask;
/// </summary>
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase
{
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 4);
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 5);
friend SkinnedMesh;
friend StreamSkinnedModelLODTask;
public:
// Skeleton mapping descriptor.
struct FLAXENGINE_API SkeletonMapping
{
// Target skeleton.
AssetReference<SkinnedModel> TargetSkeleton;
// Source skeleton.
AssetReference<SkinnedModel> SourceSkeleton;
// The node-to-node mapping for the fast animation sampling for the skinned model skeleton nodes. Each item is index of the source skeleton node into target skeleton node.
Span<int32> NodesMapping;
};
private:
struct SkeletonMappingData
{
AssetReference<SkinnedModel> SourceSkeleton;
Span<int32> NodesMapping;
};
bool _initialized = false;
int32 _loadedLODs = 0;
StreamSkinnedModelLODTask* _streamingTask = nullptr;
Dictionary<Asset*, SkeletonMappingData> _skeletonMappingCache;
public:
/// <summary>
@@ -44,7 +64,7 @@ public:
/// </summary>
FORCE_INLINE bool IsInitialized() const
{
return LODs.HasItems();
return _initialized;
}
/// <summary>
@@ -76,13 +96,11 @@ public:
/// <summary>
/// Determines whether any LOD has been initialized.
/// </summary>
/// <returns>True if any LOD has been initialized, otherwise false.</returns>
bool HasAnyLODInitialized() const;
/// <summary>
/// Determines whether this model can be rendered.
/// </summary>
/// <returns>True if can render that model, otherwise false.</returns>
FORCE_INLINE bool CanBeRendered() const
{
return _loadedLODs > 0;
@@ -109,7 +127,7 @@ public:
/// </summary>
/// <param name="name">The name of the node.</param>
/// <returns>The index of the node or -1 if not found.</returns>
API_FUNCTION() FORCE_INLINE int32 FindNode(const StringView& name)
API_FUNCTION() FORCE_INLINE int32 FindNode(const StringView& name) const
{
return Skeleton.FindNode(name);
}
@@ -119,7 +137,7 @@ public:
/// </summary>
/// <param name="name">The name of the node used by the bone.</param>
/// <returns>The index of the bone or -1 if not found.</returns>
API_FUNCTION() FORCE_INLINE int32 FindBone(const StringView& name)
API_FUNCTION() FORCE_INLINE int32 FindBone(const StringView& name) const
{
return FindBone(FindNode(name));
}
@@ -129,7 +147,7 @@ public:
/// </summary>
/// <param name="nodeIndex">The index of the node.</param>
/// <returns>The index of the bone or -1 if not found.</returns>
API_FUNCTION() FORCE_INLINE int32 FindBone(int32 nodeIndex)
API_FUNCTION() FORCE_INLINE int32 FindBone(int32 nodeIndex) const
{
return Skeleton.FindBone(nodeIndex);
}
@@ -154,7 +172,13 @@ public:
/// <param name="data">The data (may be missing if failed to get it).</param>
void GetLODData(int32 lodIndex, BytesContainer& data) const;
public:
/// <summary>
/// Gets the skeleton mapping for a given asset (animation or other skinned model). Uses identity mapping or manually created retargeting setup.
/// </summary>
/// <param name="source">The source asset (animation or other skinned model) to get mapping to its skeleton.</param>
/// <returns>The skeleton mapping for the source asset into this skeleton.</returns>
SkeletonMapping GetSkeletonMapping(Asset* source);
/// <summary>
/// Determines if there is an intersection between the SkinnedModel and a Ray in given world using given instance.
/// </summary>
@@ -194,7 +218,6 @@ public:
/// <returns>The bounding box.</returns>
API_FUNCTION() BoundingBox GetBox(int32 lodIndex = 0) const;
public:
/// <summary>
/// Draws the meshes. Binds vertex and index buffers and invokes the draw calls.
/// </summary>
@@ -219,7 +242,6 @@ public:
/// <param name="info">The packed drawing info data.</param>
void Draw(const RenderContextBatch& renderContextBatch, const SkinnedMesh::DrawInfo& info);
public:
/// <summary>
/// Setups the model LODs collection including meshes creation.
/// </summary>
@@ -244,7 +266,6 @@ public:
API_FUNCTION() bool SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<SkeletonBone>& bones, bool autoCalculateOffsetMatrix);
#if USE_EDITOR
/// <summary>
/// Saves this asset to the file. Supported only in Editor.
/// </summary>
@@ -253,7 +274,6 @@ public:
/// <param name="path">The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset.</param>
/// <returns>True if cannot save data, otherwise false.</returns>
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
#endif
private:
@@ -264,8 +284,38 @@ private:
/// <returns>True if failed, otherwise false.</returns>
bool Init(const Span<int32>& meshesCountPerLod);
void ClearSkeletonMapping();
void OnSkeletonMappingSourceAssetUnloaded(Asset* obj);
#if USE_EDITOR
public:
// Skeleton retargeting setup (internal use only - accessed by Editor)
API_STRUCT(NoDefault) struct SkeletonRetarget
{
DECLARE_SCRIPTING_TYPE_MINIMAL(SkeletonRetarget);
// Source asset id.
API_FIELD() Guid SourceAsset;
// Skeleton asset id to use for remapping.
API_FIELD() Guid SkeletonAsset;
// Skeleton nodes remapping table (maps this skeleton node name to other skeleton node).
API_FIELD() Dictionary<String, String, HeapAllocation> NodesMapping;
};
// Gets or sets the skeleton retarget entries (accessed in Editor only).
API_PROPERTY() const Array<SkeletonRetarget>& GetSkeletonRetargets() const { return _skeletonRetargets; }
API_PROPERTY() void SetSkeletonRetargets(const Array<SkeletonRetarget>& value) { Locker.Lock(); _skeletonRetargets = value; ClearSkeletonMapping(); Locker.Unlock(); }
private:
#else
struct SkeletonRetarget
{
Guid SourceAsset, SkeletonAsset;
Dictionary<String, String, HeapAllocation> NodesMapping;
};
#endif
Array<SkeletonRetarget> _skeletonRetargets;
public:
// [ModelBase]
uint64 GetMemoryUsage() const override;
void SetupMaterialSlots(int32 slotsCount) override;
int32 GetLODsCount() const override;
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;

View File

@@ -7,7 +7,7 @@
#include "Engine/Platform/FileSystem.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Scripting/MainThreadManagedInvokeAction.h"
#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
REGISTER_BINARY_ASSET_WITH_UPGRADER(Texture, "FlaxEngine.Texture", TextureAssetUpgrader, true);

View File

@@ -5,14 +5,13 @@
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Scripting/MException.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/Events.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "Engine/Scripting/ManagedCLR/MType.h"
#include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Serialization/Serialization.h"
@@ -310,15 +309,15 @@ void VisualScriptExecutor::ProcessGroupTools(Box* box, Node* node, Value& value)
const StringAsANSI<100> typeNameAnsi(typeName.Get(), typeName.Length());
if (StringUtils::Compare(typeNameAnsi.Get(), obj.Type.GetTypeName()) != 0)
{
#if USE_MONO
MonoClass* klass = Scripting::FindClassNative(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
MonoClass* objKlass = MUtils::GetClass(obj);
if (!klass || !objKlass || mono_class_is_subclass_of(objKlass, klass, false) == 0)
#if USE_CSHARP
MClass* klass = Scripting::FindClass(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
MClass* objKlass = MUtils::GetClass(obj);
if (!klass || !objKlass || !objKlass->IsSubClassOf(klass))
obj = Value::Null;
#else
const ScriptingTypeHandle type = Scripting::FindScriptingType(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
const ScriptingTypeHandle objType = Scripting::FindScriptingType(obj.Type.GetTypeName());
if (!type || !objType || objType.IsSubclassOf(type))
if (!type || !objType || !objType.IsSubclassOf(type))
obj = Value::Null;
#endif
}

View File

@@ -760,9 +760,14 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
// Change asset ID
{
auto storage = ContentStorageManager::GetStorage(tmpPath);
if (!storage)
{
LOG(Warning, "Cannot change asset ID.");
return true;
}
FlaxStorage::Entry e;
storage->GetEntry(0, e);
if (!storage || storage->ChangeAssetID(e, dstId))
if (storage->ChangeAssetID(e, dstId))
{
LOG(Warning, "Cannot change asset ID.");
return true;

View File

@@ -416,9 +416,13 @@ void JsonAsset::DeleteInstance()
ScopeLock lock(Locker);
// C# instance
if (MObject* object = GetManagedInstance())
MObject* object = GetManagedInstance();
MClass* klass = GetClass();
if (object && klass)
{
GetClass()->GetField("_instance")->SetValue(object, nullptr);
const MField* field = klass->GetField("_instance");
if (field)
field->SetValue(object, nullptr);
}
// C++ instance

View File

@@ -40,7 +40,7 @@ namespace FlaxEngine
Debug.LogError(string.Format("Missing typename of data in Json asset '{0}'.", Path), this);
return null;
}
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var assemblies = Utils.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
var assembly = assemblies[i];

View File

@@ -211,6 +211,12 @@ FlaxStorage::~FlaxStorage()
CHECK(_chunksLock == 0);
CHECK(_refCount == 0);
ASSERT(_chunks.IsEmpty());
#if USE_EDITOR
// Ensure to close any outstanding file handles to prevent file locking in case it failed to load
_file.DeleteAll();
#endif
}
FlaxStorage::LockData FlaxStorage::LockSafe()

View File

@@ -26,10 +26,10 @@ public:
{
static const Upgrader upgraders[] =
{
{ 24, 25, &Upgrade_With_Repack },
{ 23, 24, &Upgrade_22OrNewer_To_Newest },
{ 22, 24, &Upgrade_22OrNewer_To_Newest },
{ 1, 24, &Upgrade_Old_To_Newest },
{ 24, 25, &Upgrade_With_Repack }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
{ 23, 24, &Upgrade_22OrNewer_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
{ 22, 24, &Upgrade_22OrNewer_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
{ 1, 24, &Upgrade_Old_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
};
setup(upgraders, ARRAY_COUNT(upgraders));
}

View File

@@ -28,9 +28,10 @@ public:
{
static const Upgrader upgraders[] =
{
{ 1, 2, &Upgrade_1_To_2 },
{ 2, 3, &Upgrade_2_To_3 },
{ 3, 4, &Upgrade_3_To_4 },
{ 1, 2, &Upgrade_1_To_2 }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
{ 2, 3, &Upgrade_2_To_3 }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
{ 3, 4, &Upgrade_3_To_4 }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
{ 4, 5, &Upgrade_4_To_5 }, // [Deprecated on 28.04.2023, expires on 28.04.2026]
};
setup(upgraders, ARRAY_COUNT(upgraders));
}
@@ -57,7 +58,6 @@ private:
MemoryWriteStream output(srcData->Size());
do
{
// #MODEL_DATA_FORMAT_USAGE
uint32 vertices;
stream.ReadUint32(&vertices);
uint32 triangles;
@@ -422,6 +422,241 @@ private:
return false;
}
static bool Upgrade_4_To_5(AssetMigrationContext& context)
{
ASSERT(context.Input.SerializedVersion == 4 && context.Output.SerializedVersion == 5);
// Changes:
// - added version number to header (for easier changes in future)
// - added version number to mesh data (for easier changes in future)
// - added skeleton retarget setups to header
// Rewrite header chunk (added header version and retarget entries)
byte lodCount;
Array<uint16> meshesCounts;
{
const auto srcData = context.Input.Header.Chunks[0];
if (srcData == nullptr || srcData->IsMissing())
{
LOG(Warning, "Missing model header chunk");
return true;
}
MemoryReadStream stream(srcData->Get(), srcData->Size());
MemoryWriteStream output(srcData->Size());
// Header Version
output.WriteByte(1);
// Min Screen Size
float minScreenSize;
stream.ReadFloat(&minScreenSize);
output.WriteFloat(minScreenSize);
// Amount of material slots
int32 materialSlotsCount;
stream.ReadInt32(&materialSlotsCount);
output.WriteInt32(materialSlotsCount);
// For each material slot
for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++)
{
// Material
Guid materialId;
stream.Read(materialId);
output.Write(materialId);
// Shadows Mode
output.WriteByte(stream.ReadByte());
// Name
String name;
stream.ReadString(&name, 11);
output.WriteString(name, 11);
}
// Amount of LODs
stream.ReadByte(&lodCount);
output.WriteByte(lodCount);
meshesCounts.Resize(lodCount);
// For each LOD
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
// Screen Size
float screenSize;
stream.ReadFloat(&screenSize);
output.WriteFloat(screenSize);
// Amount of meshes
uint16 meshesCount;
stream.ReadUint16(&meshesCount);
output.WriteUint16(meshesCount);
meshesCounts[lodIndex] = meshesCount;
// For each mesh
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
// Material Slot index
int32 materialSlotIndex;
stream.ReadInt32(&materialSlotIndex);
output.WriteInt32(materialSlotIndex);
// Box
BoundingBox box;
stream.Read(box);
output.Write(box);
// Sphere
BoundingSphere sphere;
stream.Read(sphere);
output.Write(sphere);
// Blend Shapes
uint16 blendShapes;
stream.ReadUint16(&blendShapes);
output.WriteUint16(blendShapes);
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++)
{
String blendShapeName;
stream.ReadString(&blendShapeName, 13);
output.WriteString(blendShapeName, 13);
float blendShapeWeight;
stream.ReadFloat(&blendShapeWeight);
output.WriteFloat(blendShapeWeight);
}
}
}
// Skeleton
{
int32 nodesCount;
stream.ReadInt32(&nodesCount);
output.WriteInt32(nodesCount);
// For each node
for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++)
{
int32 parentIndex;
stream.ReadInt32(&parentIndex);
output.WriteInt32(parentIndex);
Transform localTransform;
stream.Read(localTransform);
output.Write(localTransform);
String name;
stream.ReadString(&name, 71);
output.WriteString(name, 71);
}
int32 bonesCount;
stream.ReadInt32(&bonesCount);
output.WriteInt32(bonesCount);
// For each bone
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
{
int32 parentIndex;
stream.ReadInt32(&parentIndex);
output.WriteInt32(parentIndex);
int32 nodeIndex;
stream.ReadInt32(&nodeIndex);
output.WriteInt32(nodeIndex);
Transform localTransform;
stream.Read(localTransform);
output.Write(localTransform);
Matrix offsetMatrix;
stream.ReadBytes(&offsetMatrix, sizeof(Matrix));
output.WriteBytes(&offsetMatrix, sizeof(Matrix));
}
}
// Retargeting
{
output.WriteInt32(0);
}
// Save new data
if (stream.GetPosition() != stream.GetLength())
{
LOG(Error, "Invalid position after upgrading skinned model header data.");
return true;
}
if (context.AllocateChunk(0))
return true;
context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition());
}
// Rewrite meshes data chunks
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
const int32 chunkIndex = lodIndex + 1;
const auto srcData = context.Input.Header.Chunks[chunkIndex];
if (srcData == nullptr || srcData->IsMissing())
{
LOG(Warning, "Missing skinned model LOD meshes data chunk");
return true;
}
MemoryReadStream stream(srcData->Get(), srcData->Size());
MemoryWriteStream output(srcData->Size());
// Mesh Data Version
output.WriteByte(1);
for (int32 meshIndex = 0; meshIndex < meshesCounts[lodIndex]; meshIndex++)
{
uint32 vertices;
stream.ReadUint32(&vertices);
output.WriteUint32(vertices);
uint32 triangles;
stream.ReadUint32(&triangles);
output.WriteUint32(triangles);
uint16 blendShapesCount;
stream.ReadUint16(&blendShapesCount);
output.WriteUint16(blendShapesCount);
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
{
output.WriteBool(stream.ReadBool());
uint32 minVertexIndex, maxVertexIndex;
stream.ReadUint32(&minVertexIndex);
output.WriteUint32(minVertexIndex);
stream.ReadUint32(&maxVertexIndex);
output.WriteUint32(maxVertexIndex);
uint32 blendShapeVertices;
stream.ReadUint32(&blendShapeVertices);
output.WriteUint32(blendShapeVertices);
const uint32 blendShapeDataSize = blendShapeVertices * sizeof(BlendShapeVertex);
const auto blendShapeData = stream.Move<byte>(blendShapeDataSize);
output.WriteBytes(blendShapeData, blendShapeDataSize);
}
const uint32 indicesCount = triangles * 3;
const bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
if (vertices == 0 || triangles == 0)
return true;
const auto vb0 = stream.Move<VB0SkinnedElementType>(vertices);
output.WriteBytes(vb0, vertices * sizeof(VB0SkinnedElementType));
const auto ib = stream.Move<byte>(indicesCount * ibStride);
output.WriteBytes(ib, indicesCount * ibStride);
}
// Save new data
if (stream.GetPosition() != stream.GetLength())
{
LOG(Error, "Invalid position after upgrading skinned model LOD meshes data.");
return true;
}
if (context.AllocateChunk(chunkIndex))
return true;
context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition());
}
return false;
}
};
#endif

View File

@@ -151,6 +151,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
// Extract all meshes
const auto& lod = asset->LODs[lodIndex];
int32 vertexStart = 1; // OBJ counts vertices from 1 not from 0
byte version = stream.ReadByte();
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
{
auto& mesh = lod.Meshes[meshIndex];
@@ -160,6 +161,18 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
stream.ReadUint32(&vertices);
uint32 triangles;
stream.ReadUint32(&triangles);
uint16 blendShapesCount;
stream.ReadUint16(&blendShapesCount);
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
{
stream.ReadBool();
uint32 tmp;
stream.ReadUint32(&tmp);
stream.ReadUint32(&tmp);
uint32 blendShapeVertices;
stream.ReadUint32(&blendShapeVertices);
stream.Move(blendShapeVertices * sizeof(BlendShapeVertex));
}
uint32 indicesCount = triangles * 3;
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);

View File

@@ -30,7 +30,7 @@ bool CreateJson::Create(const StringView& path, rapidjson_flax::StringBuffer& da
return Create(path, data1, data2);
}
bool CreateJson::Create(const StringView& path, StringAnsiView& data, StringAnsiView& dataTypename)
bool CreateJson::Create(const StringView& path, const StringAnsiView& data, const StringAnsiView& dataTypename)
{
Guid id = Guid::New();

View File

@@ -16,7 +16,7 @@ class CreateJson
public:
static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const String& dataTypename);
static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const char* dataTypename);
static bool Create(const StringView& path, StringAnsiView& data, StringAnsiView& dataTypename);
static bool Create(const StringView& path, const StringAnsiView& data, const StringAnsiView& dataTypename);
static CreateAssetResult ImportPo(CreateAssetContext& context);
};

View File

@@ -45,12 +45,11 @@ void ImportAudio::Options::Deserialize(DeserializeStream& stream, ISerializeModi
DESERIALIZE(BitDepth);
}
bool ImportAudio::TryGetImportOptions(const String& path, Options& options)
bool ImportAudio::TryGetImportOptions(const StringView& path, Options& options)
{
#if IMPORT_AUDIO_CACHE_OPTIONS
if (FileSystem::FileExists(path))
{
// Try to load asset file and asset info
auto tmpFile = ContentStorageManager::GetStorage(path);
AssetInitData data;
if (tmpFile
@@ -64,13 +63,11 @@ bool ImportAudio::TryGetImportOptions(const String& path, Options& options)
metadata.Parse(data.Metadata.Get<const char>(), data.Metadata.Length());
if (!metadata.HasParseError())
{
// Success
options.Deserialize(metadata, nullptr);
return true;
}
}
}
#endif
return false;
}

View File

@@ -45,7 +45,7 @@ public:
/// <param name="path">The asset path.</param>
/// <param name="options">The options.</param>
/// <returns>True if success, otherwise false.</returns>
static bool TryGetImportOptions(const String& path, Options& options);
static bool TryGetImportOptions(const StringView& path, Options& options);
/// <summary>
/// Imports the audio data (with given audio decoder).

View File

@@ -28,7 +28,7 @@ public:
/// <param name="path">The asset path.</param>
/// <param name="options">The options.</param>
/// <returns>True if success, otherwise false.</returns>
static bool TryGetImportOptions(String path, Options& options);
static bool TryGetImportOptions(const StringView& path, Options& options);
/// <summary>
/// Imports the model file.

View File

@@ -16,11 +16,9 @@
#include "Engine/Platform/FileSystem.h"
#include "AssetsImportingManager.h"
bool ImportModelFile::TryGetImportOptions(String path, Options& options)
bool ImportModelFile::TryGetImportOptions(const StringView& path, Options& options)
{
#if IMPORT_MODEL_CACHE_OPTIONS
// Check if target asset file exists
if (FileSystem::FileExists(path))
{
// Try to load asset file and asset info
@@ -41,15 +39,12 @@ bool ImportModelFile::TryGetImportOptions(String path, Options& options)
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
if (metadata.HasParseError() == false)
{
// Success
options.Deserialize(metadata, nullptr);
return true;
}
}
}
#endif
return false;
}
@@ -269,6 +264,9 @@ CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& contex
{
stream.SetPosition(0);
// Mesh Data Version
stream.WriteByte(1);
// Pack meshes
auto& meshes = modelData.LODs[lodIndex].Meshes;
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)

View File

@@ -1,9 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "ImportTexture.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/Log.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/JsonWriters.h"
@@ -28,11 +26,8 @@ bool IsSpriteAtlasOrTexture(const String& typeName)
bool ImportTexture::TryGetImportOptions(const StringView& path, Options& options)
{
#if IMPORT_TEXTURE_CACHE_OPTIONS
// Check if target asset texture exists
if (FileSystem::FileExists(path))
{
// Try to load asset file and asset info (also check for Sprite Atlas or Texture assets)
auto tmpFile = ContentStorageManager::GetStorage(path);
AssetInitData data;
if (tmpFile
@@ -48,12 +43,11 @@ bool ImportTexture::TryGetImportOptions(const StringView& path, Options& options
if (chunk15 != nullptr && !tmpFile->LoadAssetChunk(chunk15) && chunk15->Data.IsValid())
{
MemoryReadStream stream(chunk15->Data.Get(), chunk15->Data.Length());
// Load tiles data
int32 tilesVersion, tilesCount;
stream.ReadInt32(&tilesVersion);
if (tilesVersion == 1)
{
options.Sprites.Clear();
stream.ReadInt32(&tilesCount);
for (int32 i = 0; i < tilesCount; i++)
{
@@ -72,15 +66,12 @@ bool ImportTexture::TryGetImportOptions(const StringView& path, Options& options
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
if (metadata.HasParseError() == false)
{
// Success
options.Deserialize(metadata, nullptr);
return true;
}
}
}
#endif
return false;
}

View File

@@ -13,37 +13,37 @@
/// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API BuildSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(BuildSettings);
public:
DECLARE_SCRIPTING_TYPE_MINIMAL(BuildSettings);
public:
/// <summary>
/// The maximum amount of assets to include into a single assets package. Asset packages will split into several packages if need to.
/// </summary>
API_FIELD(Attributes="EditorOrder(10), DefaultValue(4096), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max assets per package\")")
API_FIELD(Attributes="EditorOrder(10), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max assets per package\")")
int32 MaxAssetsPerPackage = 4096;
/// <summary>
/// The maximum size of the single assets package (in megabytes). Asset packages will split into several packages if need to.
/// </summary>
API_FIELD(Attributes="EditorOrder(20), DefaultValue(1024), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max package size (in MB)\")")
API_FIELD(Attributes="EditorOrder(20), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max package size (in MB)\")")
int32 MaxPackageSizeMB = 1024;
/// <summary>
/// The game content cooking keycode. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), DefaultValue(0), EditorDisplay(\"General\")")
API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"General\")")
int32 ContentKey = 0;
/// <summary>
/// If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett).
/// </summary>
API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"General\")")
API_FIELD(Attributes="EditorOrder(40), EditorDisplay(\"General\")")
bool ForDistribution = false;
/// <summary>
/// If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC.
/// </summary>
API_FIELD(Attributes="EditorOrder(50), DefaultValue(false), EditorDisplay(\"General\")")
API_FIELD(Attributes="EditorOrder(50), EditorDisplay(\"General\")")
bool SkipPackaging = false;
/// <summary>
@@ -51,7 +51,7 @@ public:
/// </summary>
API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Additional Data\")")
Array<AssetReference<Asset>> AdditionalAssets;
/// <summary>
/// The list of additional scenes to include into build (into root assets set).
/// </summary>
@@ -67,17 +67,28 @@ public:
/// <summary>
/// Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time.
/// </summary>
API_FIELD(Attributes="EditorOrder(2000), DefaultValue(false), EditorDisplay(\"Content\", \"Shaders No Optimize\")")
API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Content\", \"Shaders No Optimize\")")
bool ShadersNoOptimize = false;
/// <summary>
/// Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend).
/// </summary>
API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Content\")")
API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Content\")")
bool ShadersGenerateDebugData = false;
public:
/// <summary>
/// If checked, .NET Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS.
/// </summary>
API_FIELD(Attributes="EditorOrder(3000), EditorDisplay(\"Scripting\", \"Skip .NET Runtime Packaging\")")
bool SkipDotnetPackaging = false;
/// <summary>
/// If checked, .NET Runtime packaging will skip unused libraries from packaging resulting in smaller game builds.
/// </summary>
API_FIELD(Attributes="EditorOrder(3010), EditorDisplay(\"Scripting\", \"Skip Unused .NET Runtime Libs Packaging\")")
bool SkipUnusedDotnetLibsPackaging = true;
public:
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
@@ -95,5 +106,7 @@ public:
DESERIALIZE(AdditionalAssetFolders);
DESERIALIZE(ShadersNoOptimize);
DESERIALIZE(ShadersGenerateDebugData);
DESERIALIZE(SkipDotnetPackaging);
DESERIALIZE(SkipUnusedDotnetLibsPackaging);
}
};

View File

@@ -71,6 +71,8 @@ IMPLEMENT_ENGINE_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform);
IMPLEMENT_ENGINE_SETTINGS_GETTER(SwitchPlatformSettings, SwitchPlatform);
#elif PLATFORM_MAC
IMPLEMENT_ENGINE_SETTINGS_GETTER(MacPlatformSettings, MacPlatform);
#elif PLATFORM_IOS
IMPLEMENT_ENGINE_SETTINGS_GETTER(iOSPlatformSettings, iOSPlatform);
#else
#error Unknown platform
#endif
@@ -254,6 +256,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
DESERIALIZE(SwitchPlatform);
DESERIALIZE(PS5Platform);
DESERIALIZE(MacPlatform);
DESERIALIZE(iOSPlatform);
}
void LayersAndTagsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using FlaxEngine;
namespace FlaxEditor.Content.Settings
@@ -201,6 +202,14 @@ namespace FlaxEditor.Content.Settings
public JsonAsset MacPlatform;
#endif
#if FLAX_EDITOR || PLATFORM_IOS
/// <summary>
/// Reference to <see cref="iOSPlatformSettings"/> asset. Used to apply configuration on iOS platform.
/// </summary>
[EditorOrder(2100), EditorDisplay("Platform Settings", "iOS"), AssetReference(typeof(iOSPlatformSettings), true), Tooltip("Reference to iOS Platform Settings asset")]
public JsonAsset iOSPlatform;
#endif
/// <summary>
/// Gets the absolute path to the game settings asset file.
/// </summary>
@@ -332,6 +341,10 @@ namespace FlaxEditor.Content.Settings
if (type == typeof(MacPlatformSettings))
return Load<MacPlatformSettings>(gameSettings.MacPlatform) as T;
#endif
#if FLAX_EDITOR || PLATFORM_IOS
if (type == typeof(iOSPlatformSettings))
return Load<iOSPlatformSettings>(gameSettings.iOSPlatform) as T;
#endif
if (gameSettings.CustomSettings != null)
{
@@ -426,6 +439,10 @@ namespace FlaxEditor.Content.Settings
if (type == typeof(MacPlatformSettings))
return gameSettings.MacPlatform;
#endif
#if FLAX_EDITOR || PLATFORM_IOS
if (type == typeof(iOSPlatformSettings))
return gameSettings.iOSPlatform;
#endif
if (gameSettings.CustomSettings != null)
{
@@ -538,6 +555,8 @@ namespace FlaxEditor.Content.Settings
return SaveAsset(gameSettings, ref gameSettings.Audio, obj);
if (type == typeof(MacPlatformSettings))
return SaveAsset(gameSettings, ref gameSettings.MacPlatform, obj);
if (type == typeof(iOSPlatformSettings))
return SaveAsset(gameSettings, ref gameSettings.iOSPlatform, obj);
return true;
}
@@ -572,8 +591,8 @@ namespace FlaxEditor.Content.Settings
/// <summary>
/// Loads the current game settings asset and applies it to the engine runtime configuration.
/// </summary>
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void Apply();
[LibraryImport("FlaxEngine", EntryPoint = "GameSettingsInternal_Apply")]
public static partial void Apply();
#endif
}
}

View File

@@ -84,6 +84,7 @@ public:
Guid SwitchPlatform;
Guid PS5Platform;
Guid MacPlatform;
Guid iOSPlatform;
public:

View File

@@ -1,7 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using FlaxEngine;
namespace FlaxEditor.Content.Settings
@@ -24,7 +25,13 @@ namespace FlaxEditor.Content.Settings
/// Gets the current layer names (max 32 items but trims last empty items).
/// </summary>
/// <returns>The layers.</returns>
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern string[] GetCurrentLayers();
public static string[] GetCurrentLayers()
{
return GetCurrentLayers(out int _);
}
[LibraryImport("FlaxEngine", EntryPoint = "LayersAndTagsSettingsInternal_GetCurrentLayers", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))]
[return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "layerCount")]
internal static partial string[] GetCurrentLayers(out int layerCount);
}
}

View File

@@ -35,3 +35,6 @@
#if PLATFORM_MAC
#include "Engine/Platform/Mac/MacPlatformSettings.h"
#endif
#if PLATFORM_IOS
#include "Engine/Platform/iOS/iOSPlatformSettings.h"
#endif

View File

@@ -18,8 +18,7 @@ namespace fmt_flax
FORCE_INLINE static void format(fmt::basic_memory_buffer<T, fmt::inline_buffer_size, std_flax::allocator<T>>& buffer, const T* format, const Args& ... args)
{
typedef fmt::buffer_context<T> context;
fmt::format_arg_store<context, Args...> as{ args... };
fmt::internal::vformat_to(buffer, fmt::to_string_view(format), fmt::basic_format_args<context>(as));
fmt::detail::vformat_to(buffer, fmt::basic_string_view<T>(format), fmt::make_format_args<context>(args...), {});
}
}
@@ -37,7 +36,7 @@ namespace fmt_flax
template<typename FormatContext> \
auto format(const type& v, FormatContext& ctx) -> decltype(ctx.out()) \
{ \
return format_to(ctx.out(), TEXT(formatText), ##__VA_ARGS__); \
return fmt::format_to(ctx.out(), basic_string_view<Char>(TEXT(formatText)), ##__VA_ARGS__); \
} \
}; \
}
@@ -57,7 +56,7 @@ namespace fmt_flax
auto format(const type& v, FormatContext& ctx) -> decltype(ctx.out()) \
{ \
const String str = v.ToString(); \
return fmt::internal::copy(str.Get(), str.Get() + str.Length(), ctx.out()); \
return fmt::detail::copy_str<Char>(str.Get(), str.Get() + str.Length(), ctx.out()); \
} \
}; \
}

View File

@@ -13,7 +13,7 @@
/// <summary>
/// Sends a formatted message to the log file (message type - describes level of the log (see LogType enum))
/// </summary>
#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, TEXT(format), ##__VA_ARGS__)
#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, ::String::Format(TEXT(format), ##__VA_ARGS__))
/// <summary>
/// Sends a string message to the log file (message type - describes level of the log (see LogType enum))

View File

@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -9,7 +8,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
[TypeConverter(typeof(TypeConverters.ColorConverter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.ColorConverter))]
#endif
partial struct Color
{
/// <summary>

View File

@@ -55,7 +55,6 @@ using Real = System.Single;
* THE SOFTWARE.
*/
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -63,7 +62,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
[TypeConverter(typeof(TypeConverters.Double2Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double2Converter))]
#endif
partial struct Double2 : IEquatable<Double2>, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";

View File

@@ -56,7 +56,6 @@ using Real = System.Single;
*/
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -64,7 +63,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
[TypeConverter(typeof(TypeConverters.Double3Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double3Converter))]
#endif
partial struct Double3 : IEquatable<Double3>, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";

View File

@@ -56,7 +56,6 @@ using Real = System.Single;
*/
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -64,7 +63,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
[TypeConverter(typeof(TypeConverters.Double4Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double4Converter))]
#endif
partial struct Double4 : IEquatable<Double4>, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";

View File

@@ -50,7 +50,6 @@
*/
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
[TypeConverter(typeof(TypeConverters.Float2Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float2Converter))]
#endif
partial struct Float2 : IEquatable<Float2>, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";

View File

@@ -50,7 +50,6 @@
*/
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
[TypeConverter(typeof(TypeConverters.Float3Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float3Converter))]
#endif
partial struct Float3 : IEquatable<Float3>, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";

View File

@@ -50,7 +50,6 @@
*/
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
[TypeConverter(typeof(TypeConverters.Float4Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float4Converter))]
#endif
partial struct Float4 : IEquatable<Float4>, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";

View File

@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -12,7 +11,9 @@ namespace FlaxEngine
/// Represents a two dimensional mathematical vector (signed integers).
/// </summary>
[Serializable]
[TypeConverter(typeof(TypeConverters.Int2Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int2Converter))]
#endif
partial struct Int2 : IEquatable<Int2>, IFormattable
{
private static readonly string _formatString = "X:{0} Y:{1}";

View File

@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -12,7 +11,9 @@ namespace FlaxEngine
/// Represents a three dimensional mathematical vector (signed integers).
/// </summary>
[Serializable]
[TypeConverter(typeof(TypeConverters.Int3Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int3Converter))]
#endif
partial struct Int3 : IEquatable<Int3>, IFormattable
{
private static readonly string _formatString = "X:{0} Y:{1} Z:{2}";

View File

@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -12,7 +11,9 @@ namespace FlaxEngine
/// Represents a four dimensional mathematical vector (signed integers).
/// </summary>
[Serializable]
[TypeConverter(typeof(TypeConverters.Int4Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int4Converter))]
#endif
partial struct Int4 : IEquatable<Int4>, IFormattable
{
private static readonly string _formatString = "X:{0} Y:{1} Z:{2} W:{3}";

View File

@@ -50,7 +50,6 @@
*/
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
[TypeConverter(typeof(TypeConverters.QuaternionConverter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.QuaternionConverter))]
#endif
partial struct Quaternion : IEquatable<Quaternion>, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,6 +19,16 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
@@ -25,9 +36,9 @@ namespace FlaxEngine.TypeConverters
{
string[] v = str.Split(',');
if (v.Length == 4)
return new Color(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
return new Color(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
if (v.Length == 3)
return new Color(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), 1.0f);
return new Color(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), 1.0f);
throw new FormatException("Invalid Color value format.");
}
return base.ConvertFrom(context, culture, value);
@@ -39,9 +50,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Color)value;
return v.R + "," + v.G + "," + v.B + "," + v.A;
return v.R.ToString(culture) + "," + v.G.ToString(culture) + "," + v.B.ToString(culture) + "," + v.A.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Double2(double.Parse(v[0]), double.Parse(v[1]));
return new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Double2)value;
return v.X + "," + v.Y;
return v.X.ToString(culture) + "," + v.Y.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Double3(double.Parse(v[0]), double.Parse(v[1]), double.Parse(v[2]));
return new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Double3)value;
return v.X + "," + v.Y + "," + v.Z;
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Double4(double.Parse(v[0]), double.Parse(v[1]), double.Parse(v[2]), double.Parse(v[3]));
return new Double4(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture), double.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Double4)value;
return v.X + "," + v.Y + "," + v.Z + "," + v.W;
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Float2(float.Parse(v[0]), float.Parse(v[1]));
return new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Float2)value;
return v.X + "," + v.Y;
return v.X.ToString(culture) + "," + v.Y.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Float3(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]));
return new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Float3)value;
return v.X + "," + v.Y + "," + v.Z;
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Float4(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
return new Float4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Float4)value;
return v.X + "," + v.Y + "," + v.Z + "," + v.W;
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Int2(int.Parse(v[0]), int.Parse(v[1]));
return new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Int2)value;
return v.X + "," + v.Y;
return v.X.ToString(culture) + "," + v.Y.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Int3(int.Parse(v[0]), int.Parse(v[1]), int.Parse(v[2]));
return new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Int3)value;
return v.X + "," + v.Y + "," + v.Z;
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Int4(int.Parse(v[0]), int.Parse(v[1]), int.Parse(v[2]), int.Parse(v[3]));
return new Int4(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture), int.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Int4)value;
return v.X + "," + v.Y + "," + v.Z + "," + v.W;
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Quaternion(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
return new Quaternion(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Quaternion)value;
return v.X + "," + v.Y + "," + v.Z + "," + v.W;
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Vector2(float.Parse(v[0]), float.Parse(v[1]));
return new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Vector2)value;
return v.X + "," + v.Y;
return v.X.ToString(culture) + "," + v.Y.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Vector3(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]));
return new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Vector3)value;
return v.X + "," + v.Y + "," + v.Z;
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
return new Vector4(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
return new Vector4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Vector4)value;
return v.X + "," + v.Y + "," + v.Z + "," + v.W;
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
#endif

View File

@@ -57,7 +57,6 @@ using Mathr = FlaxEngine.Mathf;
* THE SOFTWARE.
*/
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -70,7 +69,9 @@ namespace FlaxEngine
[Unmanaged]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
[TypeConverter(typeof(TypeConverters.Vector2Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector2Converter))]
#endif
public unsafe partial struct Vector2 : IEquatable<Vector2>, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";

View File

@@ -57,7 +57,6 @@ using Mathr = FlaxEngine.Mathf;
* THE SOFTWARE.
*/
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -70,7 +69,9 @@ namespace FlaxEngine
[Unmanaged]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
[TypeConverter(typeof(TypeConverters.Vector3Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector3Converter))]
#endif
public unsafe partial struct Vector3 : IEquatable<Vector3>, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";

View File

@@ -57,7 +57,6 @@ using Mathr = FlaxEngine.Mathf;
* THE SOFTWARE.
*/
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -70,7 +69,9 @@ namespace FlaxEngine
[Unmanaged]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
[TypeConverter(typeof(TypeConverters.Vector4Converter))]
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector4Converter))]
#endif
public partial struct Vector4 : IEquatable<Vector4>, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";

View File

@@ -309,7 +309,7 @@ namespace fmt
{
int32 year, month, day;
v.GetDate(year, month, day);
return format_to(ctx.out(), TEXT("{0}-{1:0>2}-{2:0>2} {3:0>2}:{4:0>2}:{5:0>2}"), year, month, day, v.GetHour(), v.GetMinute(), v.GetSecond());
return fmt::format_to(ctx.out(), basic_string_view<Char>(TEXT("{0}-{1:0>2}-{2:0>2} {3:0>2}:{4:0>2}:{5:0>2}")), year, month, day, v.GetHour(), v.GetMinute(), v.GetSecond());
}
};
}

View File

@@ -17,6 +17,8 @@ protected:
int32 _length = 0;
public:
typedef T CharType;
/// <summary>
/// Finalizes an instance of the <see cref="StringBase"/> class.
/// </summary>
@@ -1237,7 +1239,7 @@ namespace fmt
template<typename FormatContext>
auto format(const String& v, FormatContext& ctx) -> decltype(ctx.out())
{
return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
return fmt::detail::copy_str<Char>(v.Get(), v.Get() + v.Length(), ctx.out());
}
};
}
@@ -1808,7 +1810,7 @@ namespace fmt
template<typename FormatContext>
auto format(const StringAnsi& v, FormatContext& ctx) -> decltype(ctx.out())
{
return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
return fmt::detail::copy_str<char>(v.Get(), v.Get() + v.Length(), ctx.out());
}
};
}

View File

@@ -284,7 +284,7 @@ namespace fmt
template<typename FormatContext>
auto format(const String& v, FormatContext& ctx) -> decltype(ctx.out())
{
return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
return fmt::detail::copy_str<Char>(v.Get(), v.Get() + v.Length(), ctx.out());
}
};
}

View File

@@ -29,6 +29,8 @@ protected:
}
public:
typedef T CharType;
/// <summary>
/// Gets the specific const character from this string.
/// </summary>
@@ -381,7 +383,7 @@ namespace fmt
template<typename FormatContext>
auto format(const StringView& v, FormatContext& ctx) -> decltype(ctx.out())
{
return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
return fmt::detail::copy_str<Char>(v.Get(), v.Get() + v.Length(), ctx.out());
}
};
}
@@ -551,7 +553,7 @@ namespace fmt
template<typename FormatContext>
auto format(const StringAnsiView& v, FormatContext& ctx) -> decltype(ctx.out())
{
return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
return fmt::detail::copy_str<char>(v.Get(), v.Get() + v.Length(), ctx.out());
}
};
}

View File

@@ -23,12 +23,10 @@
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "Engine/Utilities/Crc.h"
#include "Engine/Utilities/StringConverter.h"
#if USE_MONO
#include <ThirdParty/mono-2.0/mono/metadata/object.h>
#endif
namespace
{
@@ -108,15 +106,14 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName)
}
}
VariantType::VariantType(Types type, _MonoClass* klass)
VariantType::VariantType(Types type, MClass* klass)
{
Type = type;
TypeName = nullptr;
#if USE_MONO
#if USE_CSHARP
if (klass)
{
MString typeName;
MUtils::GetClassFullname(klass, typeName);
const StringAnsi& typeName = klass->GetFullName();
const int32 length = typeName.Length();
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
Platform::MemoryCopy(TypeName, typeName.Get(), length);
@@ -166,7 +163,7 @@ VariantType::VariantType(const StringAnsiView& typeName)
}
// Try using managed class
#if USE_MONO
#if USE_CSHARP
if (const auto mclass = Scripting::FindClass(typeName))
{
new(this) VariantType(ManagedObject, typeName);
@@ -615,17 +612,21 @@ Variant::Variant(Asset* v)
}
}
#if USE_MONO
#if USE_CSHARP
Variant::Variant(_MonoObject* v)
: Type(VariantType::ManagedObject, v ? mono_object_get_class(v) : nullptr)
Variant::Variant(MObject* v)
: Type(VariantType::ManagedObject, v ? MCore::Object::GetClass(v) : nullptr)
{
AsUint = v ? mono_gchandle_new(v, true) : 0;
#if USE_NETCORE
AsUint64 = v ? MCore::GCHandle::New(v) : 0;
#else
AsUint = v ? MCore::GCHandle::New(v) : 0;
#endif
}
#else
Variant::Variant(_MonoObject* v)
Variant::Variant(MObject* v)
: Type(VariantType::ManagedObject, nullptr)
{
AsUint = 0;
@@ -958,11 +959,14 @@ Variant::~Variant()
Delete(AsDictionary);
break;
case VariantType::ManagedObject:
#if USE_MONO
#if USE_NETCORE
if (AsUint64)
MCore::GCHandle::Free(AsUint64);
#elif USE_MONO
if (AsUint)
mono_gchandle_free(AsUint);
break;
MCore::GCHandle::Free(AsUint);
#endif
break;
default: ;
}
}
@@ -1089,8 +1093,10 @@ Variant& Variant::operator=(const Variant& other)
AsDictionary = New<Dictionary<Variant, Variant>>(*other.AsDictionary);
break;
case VariantType::ManagedObject:
#if USE_MONO
AsUint = other.AsUint ? mono_gchandle_new(mono_gchandle_get_target(other.AsUint), true) : 0;
#if USE_NETCORE
AsUint64 = other.AsUint64 ? MCore::GCHandle::New(MCore::GCHandle::GetTarget(other.AsUint64)) : 0;
#elif USE_MONO
AsUint = other.AsUint ? MCore::GCHandle::New(MCore::GCHandle::GetTarget(other.AsUint)) : 0;
#endif
break;
case VariantType::Null:
@@ -1216,9 +1222,13 @@ bool Variant::operator==(const Variant& other) const
return false;
return AsBlob.Length == other.AsBlob.Length && StringUtils::Compare(static_cast<const char*>(AsBlob.Data), static_cast<const char*>(other.AsBlob.Data), AsBlob.Length - 1) == 0;
case VariantType::ManagedObject:
#if USE_MONO
#if USE_NETCORE
// TODO: invoke C# Equality logic?
return AsUint == other.AsUint || mono_gchandle_get_target(AsUint) == mono_gchandle_get_target(other.AsUint);
return AsUint64 == other.AsUint64 || MCore::GCHandle::GetTarget(AsUint64) == MCore::GCHandle::GetTarget(other.AsUint64);
#elif USE_MONO
return AsUint == other.AsUint || MCore::GCHandle::GetTarget(AsUint) == MCore::GCHandle::GetTarget(other.AsUint);
#else
return false;
#endif
default:
return false;
@@ -1309,8 +1319,12 @@ Variant::operator bool() const
case VariantType::Asset:
return AsAsset != nullptr;
case VariantType::ManagedObject:
#if USE_MONO
return AsUint != 0 && mono_gchandle_get_target(AsUint) != nullptr;
#if USE_NETCORE
return AsUint64 != 0 && MCore::GCHandle::GetTarget(AsUint64) != nullptr;
#elif USE_MONO
return AsUint != 0 && MCore::GCHandle::GetTarget(AsUint) != nullptr;
#else
return false;
#endif
default:
return false;
@@ -1579,8 +1593,12 @@ Variant::operator void*() const
case VariantType::Blob:
return AsBlob.Data;
case VariantType::ManagedObject:
#if USE_MONO
return AsUint ? mono_gchandle_get_target(AsUint) : nullptr;
#if USE_NETCORE
return AsUint64 ? MCore::GCHandle::GetTarget(AsUint64) : nullptr;
#elif USE_MONO
return AsUint ? MCore::GCHandle::GetTarget(AsUint) : nullptr;
#else
return nullptr;
#endif
default:
return nullptr;
@@ -1622,10 +1640,12 @@ Variant::operator ScriptingObject*() const
}
}
Variant::operator _MonoObject*() const
Variant::operator MObject*() const
{
#if USE_MONO
return Type.Type == VariantType::ManagedObject && AsUint ? mono_gchandle_get_target(AsUint) : nullptr;
#if USE_NETCORE
return Type.Type == VariantType::ManagedObject && AsUint64 ? MCore::GCHandle::GetTarget(AsUint64) : nullptr;
#elif USE_MONO
return Type.Type == VariantType::ManagedObject && AsUint ? MCore::GCHandle::GetTarget(AsUint) : nullptr;
#else
return nullptr;
#endif
@@ -2338,9 +2358,12 @@ void Variant::SetType(const VariantType& type)
Delete(AsDictionary);
break;
case VariantType::ManagedObject:
#if USE_MONO
#if USE_NETCORE
if (AsUint64)
MCore::GCHandle::Free(AsUint64);
#elif USE_MONO
if (AsUint)
mono_gchandle_free(AsUint);
MCore::GCHandle::Free(AsUint);
#endif
break;
default: ;
@@ -2448,9 +2471,12 @@ void Variant::SetType(VariantType&& type)
Delete(AsDictionary);
break;
case VariantType::ManagedObject:
#if USE_MONO
#if USE_NETCORE
if (AsUint64)
MCore::GCHandle::Free(AsUint64);
#elif USE_MONO
if (AsUint)
mono_gchandle_free(AsUint);
MCore::GCHandle::Free(AsUint);
#endif
break;
default: ;
@@ -2626,14 +2652,18 @@ void Variant::SetObject(ScriptingObject* object)
object->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(this);
}
void Variant::SetManagedObject(_MonoObject* object)
void Variant::SetManagedObject(MObject* object)
{
#if USE_MONO
#if USE_CSHARP
if (object)
{
if (Type.Type != VariantType::ManagedObject)
SetType(VariantType(VariantType::ManagedObject, mono_object_get_class(object)));
AsUint = mono_gchandle_new(object, true);
SetType(VariantType(VariantType::ManagedObject, MCore::Object::GetClass(object)));
#if USE_NETCORE
AsUint64 = MCore::GCHandle::New(object);
#else
AsUint = MCore::GCHandle::New(object);
#endif
}
else
{
@@ -2752,8 +2782,12 @@ String Variant::ToString() const
case VariantType::Typename:
return String((const char*)AsBlob.Data, AsBlob.Length ? AsBlob.Length - 1 : 0);
case VariantType::ManagedObject:
#if USE_MONO
return AsUint ? String(MUtils::ToString(mono_object_to_string(mono_gchandle_get_target(AsUint), nullptr))) : TEXT("null");
#if USE_NETCORE
return AsUint64 ? String(MUtils::ToString(MCore::Object::ToString(MCore::GCHandle::GetTarget(AsUint64)))) : TEXT("null");
#elif USE_MONO
return AsUint ? String(MUtils::ToString(MCore::Object::ToString(MCore::GCHandle::GetTarget(AsUint)))) : TEXT("null");
#else
return String::Empty;
#endif
default:
return String::Empty;
@@ -3656,23 +3690,28 @@ void Variant::AllocStructure()
AsBlob.Data = Allocator::Allocate(AsBlob.Length);
*((int16*)AsBlob.Data) = 0;
}
#if USE_MONO
#if USE_CSHARP
else if (const auto mclass = Scripting::FindClass(typeName))
{
// Fallback to C#-only types
MCore::AttachThread();
auto instance = mclass->CreateInstance();
MCore::Thread::Attach();
MObject* instance = mclass->CreateInstance();
if (instance)
{
#if 0
void* data = mono_object_unbox(instance);
int32 instanceSize = mono_class_instance_size(mclass->GetNative());
void* data = MCore::Object::Unbox(instance);
int32 instanceSize = mclass->GetInstanceSize();
AsBlob.Length = instanceSize - (int32)((uintptr)data - (uintptr)instance);
AsBlob.Data = Allocator::Allocate(AsBlob.Length);
Platform::MemoryCopy(AsBlob.Data, data, AsBlob.Length);
#else
Type.Type = VariantType::ManagedObject;
AsUint = mono_gchandle_new(instance, true);
#if USE_NETCORE
AsUint64 = MCore::GCHandle::New(instance);
#else
AsUint = MCore::GCHandle::New(instance);
#endif
#endif
}
else
@@ -3764,8 +3803,12 @@ uint32 GetHash(const Variant& key)
case VariantType::Typename:
return GetHash((const char*)key.AsBlob.Data);
case VariantType::ManagedObject:
#if USE_MONO
return key.AsUint ? (uint32)mono_object_hash(mono_gchandle_get_target(key.AsUint)) : 0;
#if USE_NETCORE
return key.AsUint64 ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.AsUint64)) : 0;
#elif USE_MONO
return key.AsUint ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.AsUint)) : 0;
#else
return 0;
#endif
default:
return 0;

View File

@@ -3,10 +3,9 @@
#pragma once
#include "Engine/Core/Types/String.h"
#include "Engine/Scripting/Types.h"
class Asset;
class ScriptingObject;
struct ScriptingType;
struct Transform;
struct CommonValue;
template<typename T>
@@ -105,7 +104,7 @@ public:
explicit VariantType(Types type, const StringView& typeName);
explicit VariantType(Types type, const StringAnsiView& typeName);
explicit VariantType(Types type, struct _MonoClass* klass);
explicit VariantType(Types type, MClass* klass);
explicit VariantType(const StringAnsiView& typeName);
VariantType(const VariantType& other);
VariantType(VariantType&& other) noexcept;
@@ -215,7 +214,7 @@ public:
Variant(void* v);
Variant(ScriptingObject* v);
Variant(Asset* v);
Variant(struct _MonoObject* v);
Variant(MObject* v);
Variant(const StringView& v);
Variant(const StringAnsiView& v);
Variant(const Char* v);
@@ -296,7 +295,7 @@ public:
explicit operator StringView() const; // Returned StringView, if not empty, is guaranteed to point to a null terminated buffer.
explicit operator StringAnsiView() const; // Returned StringView, if not empty, is guaranteed to point to a null terminated buffer.
explicit operator ScriptingObject*() const;
explicit operator struct _MonoObject*() const;
explicit operator MObject*() const;
explicit operator Asset*() const;
explicit operator Float2() const;
explicit operator Float3() const;
@@ -356,7 +355,7 @@ public:
void SetBlob(int32 length);
void SetBlob(const void* data, int32 length);
void SetObject(ScriptingObject* object);
void SetManagedObject(struct _MonoObject* object);
void SetManagedObject(MObject* object);
void SetAsset(Asset* asset);
String ToString() const;

View File

@@ -454,7 +454,12 @@ inline void DrawText3D(const DebugText3D& t, const RenderContext& renderContext,
{
Matrix w, fw, m;
if (t.FaceCamera)
Matrix::CreateWorld(t.Transform.Translation, renderContext.View.Direction, viewUp, w);
{
Matrix s, ss;
Matrix::Scaling(t.Transform.Scale.X, s);
Matrix::CreateWorld(t.Transform.Translation, renderContext.View.Direction, viewUp, ss);
Matrix::Multiply(s, ss, w);
}
else
t.Transform.GetWorld(w);
Matrix::Multiply(f, w, fw);
@@ -1995,7 +2000,7 @@ void DebugDraw::DrawText(const StringView& text, const Float2& position, const C
t.TimeLeft = duration;
}
void DebugDraw::DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size, float duration)
void DebugDraw::DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size, float duration, float scale)
{
if (text.Length() == 0 || size < 4)
return;
@@ -2005,6 +2010,7 @@ void DebugDraw::DrawText(const StringView& text, const Vector3& position, const
Platform::MemoryCopy(t.Text.Get(), text.Get(), text.Length() * sizeof(Char));
t.Text[text.Length()] = 0;
t.Transform = position - Context->Origin;
t.Transform.Scale.X = scale;
t.FaceCamera = true;
t.Size = size;
t.Color = color;

View File

@@ -590,7 +590,8 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// <param name="color">The color.</param>
/// <param name="size">The font size.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size = 32, float duration = 0.0f);
/// <param name="scale">The text scale.</param>
API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size = 32, float duration = 0.0f, float scale = 1.0f);
/// <summary>
/// Draws the text (3D).

View File

@@ -3,17 +3,15 @@
#include "DebugLog.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/MainThreadManagedInvokeAction.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MDomain.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
#include "Engine/Threading/Threading.h"
#include "FlaxEngine.Gen.h"
#if USE_MONO
#include <ThirdParty/mono-2.0/mono/metadata/exception.h>
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
#if USE_CSHARP
namespace Impl
{
@@ -68,19 +66,19 @@ bool CacheMethods()
void DebugLog::Log(LogType type, const StringView& message)
{
#if USE_MONO
#if USE_CSHARP
if (CacheMethods())
return;
auto scriptsDomain = Scripting::GetScriptsDomain();
MainThreadManagedInvokeAction::ParamsBuilder params;
params.AddParam(type);
params.AddParam(message, scriptsDomain->GetNative());
params.AddParam(message, scriptsDomain);
#if BUILD_RELEASE
params.AddParam(StringView::Empty, scriptsDomain->GetNative());
params.AddParam(StringView::Empty, scriptsDomain);
#else
const String stackTrace = Platform::GetStackTrace(1);
params.AddParam(stackTrace, scriptsDomain->GetNative());
params.AddParam(stackTrace, scriptsDomain);
#endif
MainThreadManagedInvokeAction::Invoke(Internal_SendLog, params);
#endif
@@ -88,7 +86,7 @@ void DebugLog::Log(LogType type, const StringView& message)
void DebugLog::LogException(MObject* exceptionObject)
{
#if USE_MONO
#if USE_CSHARP
if (exceptionObject == nullptr || CacheMethods())
return;
@@ -101,11 +99,11 @@ void DebugLog::LogException(MObject* exceptionObject)
String DebugLog::GetStackTrace()
{
String result;
#if USE_MONO
#if USE_CSHARP
if (!CacheMethods())
{
auto stackTraceObj = Internal_GetStackTrace->Invoke(nullptr, nullptr, nullptr);
MUtils::ToString((MonoString*)stackTraceObj, result);
MUtils::ToString((MString*)stackTraceObj, result);
}
#endif
return result;
@@ -113,10 +111,9 @@ String DebugLog::GetStackTrace()
void DebugLog::ThrowException(const char* msg)
{
#if USE_MONO
// Throw exception to the C# world
auto ex = mono_exception_from_name_msg(mono_get_corlib(), "System", "Exception", msg);
mono_raise_exception(ex);
#if USE_CSHARP
auto ex = MCore::Exception::Get(msg);
MCore::Exception::Throw(ex);
#endif
}
@@ -125,45 +122,40 @@ void DebugLog::ThrowNullReference()
//LOG(Warning, "Invalid null reference.");
//LOG_STR(Warning, DebugLog::GetStackTrace());
#if USE_MONO
// Throw exception to the C# world
auto ex = mono_get_exception_null_reference();
mono_raise_exception(ex);
#if USE_CSHARP
auto ex = MCore::Exception::GetNullReference();
MCore::Exception::Throw(ex);
#endif
}
void DebugLog::ThrowArgument(const char* arg, const char* msg)
{
#if USE_MONO
// Throw exception to the C# world
auto ex = mono_get_exception_argument(arg, msg);
mono_raise_exception(ex);
#if USE_CSHARP
auto ex = MCore::Exception::GetArgument(arg, msg);
MCore::Exception::Throw(ex);
#endif
}
void DebugLog::ThrowArgumentNull(const char* arg)
{
#if USE_MONO
// Throw exception to the C# world
auto ex = mono_get_exception_argument_null(arg);
mono_raise_exception(ex);
#if USE_CSHARP
auto ex = MCore::Exception::GetArgumentNull(arg);
MCore::Exception::Throw(ex);
#endif
}
void DebugLog::ThrowArgumentOutOfRange(const char* arg)
{
#if USE_MONO
// Throw exception to the C# world
auto ex = mono_get_exception_argument_out_of_range(arg);
mono_raise_exception(ex);
#if USE_CSHARP
auto ex = MCore::Exception::GetArgumentOutOfRange(arg);
MCore::Exception::Throw(ex);
#endif
}
void DebugLog::ThrowNotSupported(const char* msg)
{
#if USE_MONO
// Throw exception to the C# world
auto ex = mono_get_exception_not_supported(msg);
mono_raise_exception(ex);
#if USE_CSHARP
auto ex = MCore::Exception::GetNotSupported(msg);
MCore::Exception::Throw(ex);
#endif
}

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
ArgumentException(const String& additionalInfo)
: Exception(TEXT("One or more of provided arguments are not valid."), additionalInfo)
{
@@ -33,7 +33,8 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="argumentName">Argument name</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
ArgumentException(const String& argumentName, const String& additionalInfo)
: Exception(String::Format(TEXT("Provided argument {0} is not valid."), argumentName), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
ArgumentNullException(const String& additionalInfo)
: Exception(TEXT("One or more of provided arguments is null"), additionalInfo)
{
@@ -33,7 +33,8 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="argumentName">Argument name</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
ArgumentNullException(const String& argumentName, const String& additionalInfo)
: Exception(String::Format(TEXT("Provided argument {0} is null."), argumentName), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
ArgumentOutOfRangeException(const String& additionalInfo)
: Exception(TEXT("One or more of provided arguments has index out of range"), additionalInfo)
{
@@ -33,7 +33,8 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="argumentName">Argument name</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
ArgumentOutOfRangeException(const String& argumentName, const String& additionalInfo)
: Exception(String::Format(TEXT("Provided argument {0} is out of range."), argumentName), additionalInfo)
{

View File

@@ -3,6 +3,7 @@
#pragma once
#include "Engine/Debug/Exception.h"
#include "Engine/Scripting/Types.h"
namespace Log
{
@@ -24,15 +25,15 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
CLRInnerException(const String& additionalInfo)
: Exception(String::Format(TEXT("Current {0} CLR method has thrown an inner exception"),
#if USE_MONO
TEXT("Mono")
#elif USE_CORECLR
TEXT(".NET Core")
TEXT("Mono")
#elif USE_NETCORE
TEXT(".NET Core")
#else
TEXT("Unknown engine")
TEXT("Unknown engine")
#endif
), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
DivideByZeroException(const String& additionalInfo)
: Exception(TEXT("Tried to divide value by zero"), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
FileNotFoundException(const String& additionalInfo)
: Exception(TEXT("File not found"), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
IOException(const String& additionalInfo)
: Exception(TEXT("I/O error occurred."), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
IndexOutOfRangeException(const String& additionalInfo)
: Exception(TEXT("Index is out of range for items in array"), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
InvalidOperationException(const String& additionalInfo)
: Exception(TEXT("Current object didn't exists or its state was invalid."), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
NotImplementedException(const String& additionalInfo)
: Exception(TEXT("Current method or operation is not implemented."), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
NotSupportedException(const String& additionalInfo)
: Exception(TEXT("Current method or operation is not supported in current context"), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
OverflowException(const String& additionalInfo)
: Exception(TEXT("Arithmetic, casting, or conversion operation results in an overflow."), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
PathTooLongException(const String& additionalInfo)
: Exception(TEXT("Path too long."), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
PlatformNotSupportedException(const String& additionalInfo)
: Exception(TEXT("Method or operation in not supported on current platform."), additionalInfo)
{

View File

@@ -24,7 +24,7 @@ namespace Log
/// <summary>
/// Creates default exception with additional data
/// </summary>
/// <param name="message">Additional information that help describe error</param>
/// <param name="additionalInfo">Additional information that help describe error</param>
TimeoutException(const String& additionalInfo)
: Exception(TEXT("Current operation has timed out."), additionalInfo)
{

View File

@@ -4,10 +4,12 @@ using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Security;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
namespace FlaxEngine
{
internal sealed class DebugLogHandler : ILogHandler
internal partial class DebugLogHandler : ILogHandler
{
/// <summary>
/// Occurs on sending a log message.
@@ -64,14 +66,14 @@ namespace FlaxEngine
Debug.Logger.LogException(exception);
}
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_LogWrite(LogType level, string msg);
[LibraryImport("FlaxEngine", EntryPoint = "DebugLogHandlerInternal_LogWrite", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial void Internal_LogWrite(LogType level, string msg);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_Log(LogType level, string msg, IntPtr obj, string stackTrace);
[LibraryImport("FlaxEngine", EntryPoint = "DebugLogHandlerInternal_Log", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial void Internal_Log(LogType level, string msg, IntPtr obj, string stackTrace);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_LogException(Exception exception, IntPtr obj);
[LibraryImport("FlaxEngine", EntryPoint = "DebugLogHandlerInternal_LogException", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial void Internal_LogException([MarshalUsing(typeof(Interop.ExceptionMarshaller))] Exception exception, IntPtr obj);
[SecuritySafeCritical]
public static string Internal_GetStackTrace()

View File

@@ -20,6 +20,7 @@
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Threading/ThreadRegistry.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/JsonAsset.h"
@@ -39,7 +40,7 @@
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/MException.h"
#include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Core/Config/PlatformSettings.h"
#endif
@@ -65,6 +66,7 @@ Action Engine::FixedUpdate;
Action Engine::Update;
TaskGraph* Engine::UpdateGraph = nullptr;
Action Engine::LateUpdate;
Action Engine::LateFixedUpdate;
Action Engine::Draw;
Action Engine::Pause;
Action Engine::Unpause;
@@ -198,6 +200,7 @@ int32 Engine::Main(const Char* cmdLine)
if (Time::OnBeginPhysics())
{
OnFixedUpdate();
OnLateFixedUpdate();
Time::OnEndPhysics();
}
@@ -273,6 +276,17 @@ void Engine::OnFixedUpdate()
}
}
void Engine::OnLateFixedUpdate()
{
PROFILE_CPU_NAMED("Late Fixed Update");
// Call event
LateFixedUpdate();
// Update services
EngineService::OnLateFixedUpdate();
}
void Engine::OnUpdate()
{
PROFILE_CPU_NAMED("Update");
@@ -309,6 +323,14 @@ void Engine::OnUpdate()
// Update services
EngineService::OnUpdate();
#ifdef USE_NETCORE
// Force GC to run in background periodically to avoid large blocking collections causing hitches
if (Time::Update.TicksCount % 60 == 0)
{
MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false);
}
#endif
}
void Engine::OnLateUpdate()
@@ -547,6 +569,7 @@ void EngineImpl::InitPaths()
#endif
#if USE_EDITOR
Globals::EngineContentFolder = Globals::StartupFolder / TEXT("Content");
#if USE_MONO
#if PLATFORM_WINDOWS
Globals::MonoPath = Globals::StartupFolder / TEXT("Source/Platforms/Editor/Windows/Mono");
#elif PLATFORM_LINUX
@@ -556,8 +579,11 @@ void EngineImpl::InitPaths()
#else
#error "Please specify the Mono data location for Editor on this platform."
#endif
#endif
#else
#if USE_MONO
Globals::MonoPath = Globals::StartupFolder / TEXT("Mono");
#endif
#endif
Globals::ProjectContentFolder = Globals::ProjectFolder / TEXT("Content");
#if USE_EDITOR

View File

@@ -54,6 +54,11 @@ public:
/// </summary>
static Action LateUpdate;
/// <summary>
/// Event called after engine update.
/// </summary>
static Action LateFixedUpdate;
/// <summary>
/// Event called during frame rendering and can be used to invoke custom rendering with GPUDevice.
/// </summary>
@@ -107,6 +112,11 @@ public:
/// </summary>
static void OnLateUpdate();
/// <summary>
/// Late fixed update callback.
/// </summary>
static void OnLateFixedUpdate();
/// <summary>
/// Draw callback.
/// </summary>

View File

@@ -33,6 +33,7 @@ static bool CompareEngineServices(EngineService* const& a, EngineService* const&
DEFINE_ENGINE_SERVICE_EVENT(FixedUpdate);
DEFINE_ENGINE_SERVICE_EVENT(Update);
DEFINE_ENGINE_SERVICE_EVENT(LateUpdate);
DEFINE_ENGINE_SERVICE_EVENT(LateFixedUpdate);
DEFINE_ENGINE_SERVICE_EVENT(Draw);
DEFINE_ENGINE_SERVICE_EVENT_INVERTED(BeforeExit);

View File

@@ -44,6 +44,7 @@ public:
DECLARE_ENGINE_SERVICE_EVENT(void, FixedUpdate);
DECLARE_ENGINE_SERVICE_EVENT(void, Update);
DECLARE_ENGINE_SERVICE_EVENT(void, LateUpdate);
DECLARE_ENGINE_SERVICE_EVENT(void, LateFixedUpdate);
DECLARE_ENGINE_SERVICE_EVENT(void, Draw);
DECLARE_ENGINE_SERVICE_EVENT(void, BeforeExit);
DECLARE_ENGINE_SERVICE_EVENT(void, Dispose);

View File

@@ -24,6 +24,8 @@
#include "Platforms/Switch/Engine/Engine/SwitchGame.h"
#elif PLATFORM_MAC
#include "Mac/MacGame.h"
#elif PLATFORM_IOS
#include "iOS/iOSGame.h"
#else
#error Missing Game implementation!
#endif

View File

@@ -15,7 +15,9 @@ String Globals::EngineContentFolder;
String Globals::ProjectSourceFolder;
#endif
String Globals::ProjectContentFolder;
#if USE_MONO
String Globals::MonoPath;
#endif
bool Globals::FatalErrorOccurred;
bool Globals::IsRequestingExit;
int32 Globals::ExitCode;

View File

@@ -49,8 +49,10 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals);
// Project content directory path
API_FIELD(ReadOnly) static String ProjectContentFolder;
#if USE_MONO
// Mono library folder path
API_FIELD(ReadOnly) static String MonoPath;
#endif
// State

Some files were not shown because too many files have changed in this diff Show More