Merge branch 'master' of https://gitlab.flaxengine.com/flax/flaxengine
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
||||
"Version": {
|
||||
"Major": 1,
|
||||
"Minor": 1,
|
||||
"Build": 6219
|
||||
"Build": 6220
|
||||
},
|
||||
"Company": "Flax",
|
||||
"Copyright": "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.",
|
||||
|
||||
+2
-18
@@ -334,7 +334,7 @@ namespace FlaxEditor
|
||||
}
|
||||
|
||||
// Load scene
|
||||
|
||||
|
||||
// scene cmd line argument
|
||||
var scene = ContentDatabase.Find(_startupSceneCmdLine);
|
||||
if (scene is SceneItem)
|
||||
@@ -1306,22 +1306,6 @@ namespace FlaxEditor
|
||||
VisualScriptingDebugFlow?.Invoke(debugFlow);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AnimGraphDebugFlowInfo
|
||||
{
|
||||
public Asset Asset;
|
||||
public FlaxEngine.Object Object;
|
||||
public uint NodeId;
|
||||
public int BoxId;
|
||||
}
|
||||
|
||||
internal static event Action<AnimGraphDebugFlowInfo> AnimGraphDebugFlow;
|
||||
|
||||
internal static void Internal_OnAnimGraphDebugFlow(ref AnimGraphDebugFlowInfo debugFlow)
|
||||
{
|
||||
AnimGraphDebugFlow?.Invoke(debugFlow);
|
||||
}
|
||||
|
||||
private static void RequestStartPlayOnEditMode()
|
||||
{
|
||||
if (Instance.StateMachine.IsEditMode)
|
||||
@@ -1334,7 +1318,7 @@ namespace FlaxEditor
|
||||
{
|
||||
Instance.StateMachine.StateChanged += RequestStartPlayOnEditMode;
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
internal static extern int Internal_ReadOutputLogs(string[] outMessages, byte[] outLogTypes, long[] outLogTimes);
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ MMethod* Internal_GetGameWinPtr = nullptr;
|
||||
MMethod* Internal_GetGameWindowSize = nullptr;
|
||||
MMethod* Internal_OnAppExit = nullptr;
|
||||
MMethod* Internal_OnVisualScriptingDebugFlow = nullptr;
|
||||
MMethod* Internal_OnAnimGraphDebugFlow = nullptr;
|
||||
MMethod* Internal_RequestStartPlayOnEditMode = nullptr;
|
||||
|
||||
void OnLightmapsBake(ShadowsOfMordor::BuildProgressStep step, float stepProgress, float totalProgress, bool isProgressEvent)
|
||||
@@ -138,38 +137,6 @@ void OnVisualScriptingDebugFlow()
|
||||
}
|
||||
}
|
||||
|
||||
struct AnimGraphDebugFlowInfo
|
||||
{
|
||||
MonoObject* Asset;
|
||||
MonoObject* Object;
|
||||
uint32 NodeId;
|
||||
int32 BoxId;
|
||||
};
|
||||
|
||||
void OnAnimGraphDebugFlow(Asset* asset, ScriptingObject* object, uint32 nodeId, uint32 boxId)
|
||||
{
|
||||
if (Internal_OnAnimGraphDebugFlow == nullptr)
|
||||
{
|
||||
Internal_OnAnimGraphDebugFlow = ManagedEditor::GetStaticClass()->GetMethod("Internal_OnAnimGraphDebugFlow", 1);
|
||||
ASSERT(Internal_OnAnimGraphDebugFlow);
|
||||
}
|
||||
|
||||
AnimGraphDebugFlowInfo flowInfo;
|
||||
flowInfo.Asset = asset ? asset->GetOrCreateManagedInstance() : nullptr;
|
||||
flowInfo.Object = object ? object->GetOrCreateManagedInstance() : nullptr;
|
||||
flowInfo.NodeId = nodeId;
|
||||
flowInfo.BoxId = boxId;
|
||||
MonoObject* exception = nullptr;
|
||||
void* params[1];
|
||||
params[0] = &flowInfo;
|
||||
Internal_OnAnimGraphDebugFlow->Invoke(nullptr, params, &exception);
|
||||
if (exception)
|
||||
{
|
||||
MException ex(exception);
|
||||
ex.Log(LogType::Error, TEXT("OnAnimGraphDebugFlow"));
|
||||
}
|
||||
}
|
||||
|
||||
void OnLogMessage(LogType type, const StringView& msg);
|
||||
|
||||
ManagedEditor::ManagedEditor()
|
||||
@@ -187,7 +154,6 @@ ManagedEditor::ManagedEditor()
|
||||
CSG::Builder::OnBrushModified.Bind<OnBrushModified>();
|
||||
Log::Logger::OnMessage.Bind<OnLogMessage>();
|
||||
VisualScripting::DebugFlow.Bind<OnVisualScriptingDebugFlow>();
|
||||
AnimGraphExecutor::DebugFlow.Bind<OnAnimGraphDebugFlow>();
|
||||
}
|
||||
|
||||
ManagedEditor::~ManagedEditor()
|
||||
@@ -204,7 +170,6 @@ ManagedEditor::~ManagedEditor()
|
||||
CSG::Builder::OnBrushModified.Unbind<OnBrushModified>();
|
||||
Log::Logger::OnMessage.Unbind<OnLogMessage>();
|
||||
VisualScripting::DebugFlow.Unbind<OnVisualScriptingDebugFlow>();
|
||||
AnimGraphExecutor::DebugFlow.Unbind<OnAnimGraphDebugFlow>();
|
||||
}
|
||||
|
||||
void ManagedEditor::Init()
|
||||
@@ -530,7 +495,6 @@ void ManagedEditor::DestroyManaged()
|
||||
Internal_GetGameWinPtr = nullptr;
|
||||
Internal_OnAppExit = nullptr;
|
||||
Internal_OnVisualScriptingDebugFlow = nullptr;
|
||||
Internal_OnAnimGraphDebugFlow = nullptr;
|
||||
|
||||
// Base
|
||||
PersistentScriptingObject::DestroyManaged();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.CustomEditors;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
@@ -13,6 +14,7 @@ using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEditor.Viewport.Previews;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
// ReSharper disable UnusedMember.Local
|
||||
// ReSharper disable UnusedMember.Global
|
||||
@@ -206,11 +208,18 @@ namespace FlaxEditor.Windows.Assets
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct AnimGraphDebugFlowInfo
|
||||
{
|
||||
public uint NodeId;
|
||||
public int BoxId;
|
||||
}
|
||||
|
||||
private FlaxObjectRefPickerControl _debugPicker;
|
||||
private NavigationBar _navigationBar;
|
||||
private PropertiesProxy _properties;
|
||||
private Tab _previewTab;
|
||||
private readonly List<Editor.AnimGraphDebugFlowInfo> _debugFlows = new List<Editor.AnimGraphDebugFlowInfo>();
|
||||
private readonly List<AnimGraphDebugFlowInfo> _debugFlows = new List<AnimGraphDebugFlowInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the animated model actor used for the animation preview.
|
||||
@@ -285,7 +294,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
Parent = this
|
||||
};
|
||||
|
||||
Editor.AnimGraphDebugFlow += OnDebugFlow;
|
||||
Animations.DebugFlow += OnDebugFlow;
|
||||
}
|
||||
|
||||
private void OnSurfaceContextChanged(VisjectSurfaceContext context)
|
||||
@@ -293,26 +302,27 @@ namespace FlaxEditor.Windows.Assets
|
||||
_surface.UpdateNavigationBar(_navigationBar, _toolstrip);
|
||||
}
|
||||
|
||||
private bool OnCheckValid(FlaxEngine.Object obj, ScriptType type)
|
||||
private bool OnCheckValid(Object obj, ScriptType type)
|
||||
{
|
||||
return obj is AnimatedModel player && player.AnimationGraph == OriginalAsset;
|
||||
}
|
||||
|
||||
private void OnDebugFlow(Editor.AnimGraphDebugFlowInfo flowInfo)
|
||||
private void OnDebugFlow(Asset asset, Object obj, uint nodeId, uint boxId)
|
||||
{
|
||||
// Filter the flow
|
||||
if (_debugPicker.Value != null)
|
||||
{
|
||||
if (flowInfo.Asset != OriginalAsset || _debugPicker.Value != flowInfo.Object)
|
||||
if (asset != OriginalAsset || _debugPicker.Value != obj)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (flowInfo.Asset != Asset || _preview.PreviewActor != flowInfo.Object)
|
||||
if (asset != Asset || _preview.PreviewActor != obj)
|
||||
return;
|
||||
}
|
||||
|
||||
// Register flow to show it in UI on a surface
|
||||
var flowInfo = new AnimGraphDebugFlowInfo { NodeId = nodeId, BoxId = (int)boxId };
|
||||
lock (_debugFlows)
|
||||
{
|
||||
_debugFlows.Add(flowInfo);
|
||||
@@ -457,7 +467,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Editor.AnimGraphDebugFlow -= OnDebugFlow;
|
||||
Animations.DebugFlow -= OnDebugFlow;
|
||||
|
||||
_properties = null;
|
||||
_navigationBar = null;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Animations.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Level/Actors/AnimatedModel.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
|
||||
Array<AnimatedModel*> UpdateList;
|
||||
Array<Matrix> UpdateBones;
|
||||
#include "Engine/Threading/TaskGraph.h"
|
||||
|
||||
class AnimationsService : public EngineService
|
||||
{
|
||||
@@ -18,70 +17,110 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void Update() override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
AnimationsService AnimationManagerInstance;
|
||||
|
||||
void AnimationsService::Update()
|
||||
class AnimationsSystem : public TaskGraphSystem
|
||||
{
|
||||
PROFILE_CPU_NAMED("Animations");
|
||||
public:
|
||||
float DeltaTime, UnscaledDeltaTime, Time, UnscaledTime;
|
||||
void Job(int32 index);
|
||||
void Execute(TaskGraph* graph) override;
|
||||
void PostExecute(TaskGraph* graph) override;
|
||||
};
|
||||
|
||||
// TODO: implement the thread jobs pipeline to run set of tasks at once (use it for multi-threaded rendering and animations evaluation)
|
||||
AnimationsService AnimationManagerInstance;
|
||||
Array<AnimatedModel*> UpdateList;
|
||||
TaskGraphSystem* Animations::System = nullptr;
|
||||
Delegate<Asset*, ScriptingObject*, uint32, uint32> Animations::DebugFlow;
|
||||
|
||||
const auto& tickData = Time::Update;
|
||||
const float deltaTime = tickData.DeltaTime.GetTotalSeconds();
|
||||
const float unscaledDeltaTime = tickData.UnscaledDeltaTime.GetTotalSeconds();
|
||||
const float time = tickData.Time.GetTotalSeconds();
|
||||
const float unscaledTime = tickData.UnscaledTime.GetTotalSeconds();
|
||||
|
||||
for (int32 i = 0; i < UpdateList.Count(); i++)
|
||||
{
|
||||
auto animatedModel = UpdateList[i];
|
||||
if (animatedModel->SkinnedModel == nullptr || !animatedModel->SkinnedModel->IsLoaded())
|
||||
continue;
|
||||
|
||||
// Prepare skinning data
|
||||
animatedModel->SetupSkinningData();
|
||||
|
||||
// Update the animation graph and the skinning
|
||||
auto graph = animatedModel->AnimationGraph.Get();
|
||||
if (graph && graph->IsLoaded() && graph->Graph.CanUseWithSkeleton(animatedModel->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 USE_EDITOR
|
||||
// Lock in editor only (more reloads during asset live editing)
|
||||
ScopeLock lock(animatedModel->AnimationGraph->Locker);
|
||||
#endif
|
||||
|
||||
// Animation delta time can be based on a time since last update or the current delta
|
||||
float dt = animatedModel->UseTimeScale ? deltaTime : unscaledDeltaTime;
|
||||
float t = animatedModel->UseTimeScale ? time : unscaledTime;
|
||||
const float lastUpdateTime = animatedModel->GraphInstance.LastUpdateTime;
|
||||
if (lastUpdateTime > 0 && t > lastUpdateTime)
|
||||
{
|
||||
dt = t - lastUpdateTime;
|
||||
}
|
||||
animatedModel->GraphInstance.LastUpdateTime = t;
|
||||
|
||||
// Evaluate animated nodes pose
|
||||
graph->GraphExecutor.Update(animatedModel->GraphInstance, dt);
|
||||
|
||||
// Update gameplay
|
||||
animatedModel->OnAnimationUpdated();
|
||||
}
|
||||
}
|
||||
UpdateList.Clear();
|
||||
bool AnimationsService::Init()
|
||||
{
|
||||
Animations::System = New<AnimationsSystem>();
|
||||
Engine::UpdateGraph->AddSystem(Animations::System);
|
||||
return false;
|
||||
}
|
||||
|
||||
void AnimationsService::Dispose()
|
||||
{
|
||||
UpdateList.Resize(0);
|
||||
UpdateBones.Resize(0);
|
||||
SAFE_DELETE(Animations::System);
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
{
|
||||
// Prepare skinning data
|
||||
animatedModel->SetupSkinningData();
|
||||
|
||||
// Animation delta time can be based on a time since last update or the current delta
|
||||
float dt = animatedModel->UseTimeScale ? DeltaTime : UnscaledDeltaTime;
|
||||
float t = animatedModel->UseTimeScale ? Time : UnscaledTime;
|
||||
const float lastUpdateTime = animatedModel->GraphInstance.LastUpdateTime;
|
||||
if (lastUpdateTime > 0 && t > lastUpdateTime)
|
||||
{
|
||||
dt = t - lastUpdateTime;
|
||||
}
|
||||
animatedModel->GraphInstance.LastUpdateTime = t;
|
||||
|
||||
// Evaluate animated nodes pose
|
||||
graph->GraphExecutor.Update(animatedModel->GraphInstance, dt);
|
||||
|
||||
// Update gameplay
|
||||
animatedModel->OnAnimationUpdated_Async();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationsSystem::Execute(TaskGraph* graph)
|
||||
{
|
||||
if (UpdateList.Count() == 0)
|
||||
return;
|
||||
|
||||
// Setup data for async update
|
||||
const auto& tickData = Time::Update;
|
||||
DeltaTime = tickData.DeltaTime.GetTotalSeconds();
|
||||
UnscaledDeltaTime = tickData.UnscaledDeltaTime.GetTotalSeconds();
|
||||
Time = tickData.Time.GetTotalSeconds();
|
||||
UnscaledTime = tickData.UnscaledTime.GetTotalSeconds();
|
||||
|
||||
// Schedule work to update all animated models in async
|
||||
Function<void(int32)> job;
|
||||
job.Bind<AnimationsSystem, &AnimationsSystem::Job>(this);
|
||||
graph->DispatchJob(job, UpdateList.Count());
|
||||
}
|
||||
|
||||
void AnimationsSystem::PostExecute(TaskGraph* graph)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Animations.PostExecute");
|
||||
|
||||
// Update gameplay
|
||||
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
|
||||
)
|
||||
{
|
||||
animatedModel->OnAnimationUpdated_Sync();
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
UpdateList.Clear();
|
||||
}
|
||||
|
||||
void Animations::AddToUpdate(AnimatedModel* obj)
|
||||
|
||||
@@ -2,14 +2,29 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
#include "Engine/Core/Delegate.h"
|
||||
|
||||
class TaskGraphSystem;
|
||||
class AnimatedModel;
|
||||
class Asset;
|
||||
|
||||
/// <summary>
|
||||
/// The animations service.
|
||||
/// The animations playback service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API Animations
|
||||
API_CLASS(Static) class FLAXENGINE_API Animations
|
||||
{
|
||||
public:
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Content);
|
||||
|
||||
/// <summary>
|
||||
/// The system for Animations update.
|
||||
/// </summary>
|
||||
API_FIELD(ReadOnly) static TaskGraphSystem* System;
|
||||
|
||||
#if USE_EDITOR
|
||||
// Custom event that is called every time the Anim Graph signal flows over the graph (including the data connections). Can be used to read and visualize the animation blending logic. Args are: anim graph asset, animated object, node id, box id
|
||||
API_EVENT() static Delegate<Asset*, ScriptingObject*, uint32, uint32> DebugFlow;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Adds an animated model to update.
|
||||
|
||||
@@ -9,25 +9,6 @@
|
||||
#include "Engine/Utilities/Delaunay2D.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
|
||||
void AnimGraphBase::ClearCache()
|
||||
{
|
||||
// Clear sub-graphs
|
||||
for (int32 i = 0; i < SubGraphs.Count(); i++)
|
||||
{
|
||||
SubGraphs[i]->ClearCache();
|
||||
}
|
||||
|
||||
// Clear cache
|
||||
for (int32 i = 0; i < Nodes.Count(); i++)
|
||||
{
|
||||
auto& node = Nodes[i];
|
||||
for (int32 j = 0; j < node.Boxes.Count(); j++)
|
||||
{
|
||||
node.Boxes[j].InvalidateCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimSubGraph* AnimGraphBase::LoadSubGraph(const void* data, int32 dataLength, const Char* name)
|
||||
{
|
||||
if (data == nullptr || dataLength == 0)
|
||||
|
||||
@@ -80,10 +80,6 @@ namespace AnimGraphInternal
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
Delegate<Asset*, ScriptingObject*, uint32, uint32> AnimGraphExecutor::DebugFlow;
|
||||
#endif
|
||||
|
||||
void AnimGraphExecutor::initRuntime()
|
||||
{
|
||||
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_HasConnection", &AnimGraphInternal::HasConnection);
|
||||
@@ -93,13 +89,10 @@ void AnimGraphExecutor::initRuntime()
|
||||
|
||||
void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value)
|
||||
{
|
||||
auto box = (AnimGraphBox*)boxBase;
|
||||
if (box->IsCacheValid())
|
||||
{
|
||||
// Return cache
|
||||
value = box->Cache;
|
||||
auto& context = Context.Get();
|
||||
if (context.ValueCache.TryGet(boxBase, value))
|
||||
return;
|
||||
}
|
||||
auto box = (AnimGraphBox*)boxBase;
|
||||
auto node = (AnimGraphNode*)nodeBase;
|
||||
auto& data = node->Data.Custom;
|
||||
value = Value::Null;
|
||||
@@ -109,16 +102,16 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value&
|
||||
return;
|
||||
|
||||
// Prepare node context
|
||||
InternalContext context;
|
||||
context.Graph = &_graph;
|
||||
context.GraphExecutor = this;
|
||||
context.Node = node;
|
||||
context.NodeId = node->ID;
|
||||
context.BoxId = box->ID;
|
||||
context.DeltaTime = _deltaTime;
|
||||
context.CurrentFrameIndex = _currentFrameIndex;;
|
||||
context.BaseModel = _graph.BaseModel->GetOrCreateManagedInstance();
|
||||
context.Instance = _data->Object ? _data->Object->GetOrCreateManagedInstance() : nullptr;
|
||||
InternalContext internalContext;
|
||||
internalContext.Graph = &_graph;
|
||||
internalContext.GraphExecutor = this;
|
||||
internalContext.Node = node;
|
||||
internalContext.NodeId = node->ID;
|
||||
internalContext.BoxId = box->ID;
|
||||
internalContext.DeltaTime = context.DeltaTime;
|
||||
internalContext.CurrentFrameIndex = context.CurrentFrameIndex;
|
||||
internalContext.BaseModel = _graph.BaseModel->GetOrCreateManagedInstance();
|
||||
internalContext.Instance = context.Data->Object ? context.Data->Object->GetOrCreateManagedInstance() : nullptr;
|
||||
|
||||
// Peek managed object
|
||||
const auto obj = mono_gchandle_get_target(data.Handle);
|
||||
@@ -130,7 +123,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value&
|
||||
|
||||
// Evaluate node
|
||||
void* params[1];
|
||||
params[0] = &context;
|
||||
params[0] = &internalContext;
|
||||
MonoObject* exception = nullptr;
|
||||
MonoObject* result = data.Evaluate->Invoke(obj, params, &exception);
|
||||
if (exception)
|
||||
@@ -142,7 +135,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value&
|
||||
|
||||
// Extract result
|
||||
value = MUtils::UnboxVariant(result);
|
||||
box->Cache = value;
|
||||
context.ValueCache.Add(boxBase, value);
|
||||
}
|
||||
|
||||
bool AnimGraph::IsReady() const
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "AnimGraph.h"
|
||||
#include "Engine/Animations/Animations.h"
|
||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||
#include "Engine/Graphics/Models/SkeletonData.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
|
||||
ThreadLocal<AnimGraphContext, 64> AnimGraphExecutor::Context;
|
||||
|
||||
RootMotionData RootMotionData::Identity = { Vector3(0.0f), Quaternion(0.0f, 0.0f, 0.0f, 1.0f) };
|
||||
|
||||
RootMotionData& RootMotionData::operator+=(const RootMotionData& b)
|
||||
@@ -78,16 +81,44 @@ void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32
|
||||
parentTransform.WorldToLocal(value, Nodes[nodeIndex]);
|
||||
}
|
||||
|
||||
void AnimGraphInstanceData::Clear()
|
||||
{
|
||||
Version = 0;
|
||||
LastUpdateTime = -1;
|
||||
CurrentFrame = 0;
|
||||
RootTransform = Transform::Identity;
|
||||
RootMotion = RootMotionData::Identity;
|
||||
Parameters.Resize(0);
|
||||
State.Resize(0);
|
||||
NodesPose.Resize(0);
|
||||
}
|
||||
|
||||
void AnimGraphInstanceData::ClearState()
|
||||
{
|
||||
Version = 0;
|
||||
LastUpdateTime = -1;
|
||||
CurrentFrame = 0;
|
||||
RootTransform = Transform::Identity;
|
||||
RootMotion = RootMotionData::Identity;
|
||||
State.Resize(0);
|
||||
NodesPose.Resize(0);
|
||||
}
|
||||
|
||||
void AnimGraphInstanceData::Invalidate()
|
||||
{
|
||||
LastUpdateTime = -1;
|
||||
CurrentFrame = 0;
|
||||
}
|
||||
|
||||
AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor)
|
||||
{
|
||||
// Ensure to have memory
|
||||
auto& context = AnimGraphExecutor::Context.Get();
|
||||
const int32 count = executor->_skeletonNodesCount;
|
||||
if (Nodes.Nodes.Count() != count)
|
||||
{
|
||||
Nodes.Nodes.Resize(count, false);
|
||||
}
|
||||
|
||||
return &Nodes;
|
||||
if (context.PoseCacheSize == context.PoseCache.Count())
|
||||
context.PoseCache.AddOne();
|
||||
auto& nodes = context.PoseCache[context.PoseCacheSize++];
|
||||
nodes.Nodes.Resize(count, false);
|
||||
return &nodes;
|
||||
}
|
||||
|
||||
bool AnimGraph::Load(ReadStream* stream, bool loadMeta)
|
||||
@@ -181,20 +212,24 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
|
||||
// Initialize
|
||||
auto& skeleton = _graph.BaseModel->Skeleton;
|
||||
auto& context = Context.Get();
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Init");
|
||||
|
||||
// Prepare graph data for the evaluation
|
||||
// Init data from base model
|
||||
_skeletonNodesCount = skeleton.Nodes.Count();
|
||||
_graphStack.Clear();
|
||||
_graphStack.Push((Graph*)&_graph);
|
||||
_data = &data;
|
||||
_deltaTime = dt;
|
||||
_rootMotionMode = (RootMotionMode)(int32)_graph._rootNode->Values[0];
|
||||
_currentFrameIndex = ++data.CurrentFrame;
|
||||
_callStack.Clear();
|
||||
_functions.Clear();
|
||||
_graph.ClearCache();
|
||||
|
||||
// Prepare context data for the evaluation
|
||||
context.GraphStack.Clear();
|
||||
context.GraphStack.Push((Graph*)&_graph);
|
||||
context.Data = &data;
|
||||
context.DeltaTime = dt;
|
||||
context.CurrentFrameIndex = ++data.CurrentFrame;
|
||||
context.CallStack.Clear();
|
||||
context.Functions.Clear();
|
||||
context.PoseCacheSize = 0;
|
||||
context.ValueCache.Clear();
|
||||
|
||||
// Prepare instance data
|
||||
if (data.Version != _graph.Version)
|
||||
@@ -208,18 +243,18 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
data.State.Resize(_graph.BucketsCountTotal, false);
|
||||
|
||||
// Initialize buckets
|
||||
ResetBuckets(&_graph);
|
||||
ResetBuckets(context, &_graph);
|
||||
}
|
||||
|
||||
// Init empty nodes data
|
||||
_emptyNodes.RootMotion = RootMotionData::Identity;
|
||||
_emptyNodes.Position = 0.0f;
|
||||
_emptyNodes.Length = 0.0f;
|
||||
_emptyNodes.Nodes.Resize(_skeletonNodesCount, false);
|
||||
context.EmptyNodes.RootMotion = RootMotionData::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];
|
||||
_emptyNodes.Nodes[i] = node.LocalTransform;
|
||||
context.EmptyNodes.Nodes[i] = node.LocalTransform;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +279,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Global Pose");
|
||||
|
||||
_data->NodesPose.Resize(_skeletonNodesCount, false);
|
||||
data.NodesPose.Resize(_skeletonNodesCount, false);
|
||||
|
||||
// Note: this assumes that nodes are sorted (parents first)
|
||||
for (int32 nodeIndex = 0; nodeIndex < _skeletonNodesCount; nodeIndex++)
|
||||
@@ -254,18 +289,16 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
{
|
||||
nodesTransformations[nodeIndex] = nodesTransformations[parentIndex].LocalToWorld(nodesTransformations[nodeIndex]);
|
||||
}
|
||||
nodesTransformations[nodeIndex].GetWorld(_data->NodesPose[nodeIndex]);
|
||||
nodesTransformations[nodeIndex].GetWorld(data.NodesPose[nodeIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
// Process the root node transformation and the motion
|
||||
{
|
||||
_data->RootTransform = nodesTransformations[0];
|
||||
_data->RootMotion = animResult->RootMotion;
|
||||
// Process the root node transformation and the motion
|
||||
data.RootTransform = nodesTransformations[0];
|
||||
data.RootMotion = animResult->RootMotion;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
_data = nullptr;
|
||||
context.Data = nullptr;
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::GetInputValue(Box* box, Value& result)
|
||||
@@ -273,29 +306,39 @@ void AnimGraphExecutor::GetInputValue(Box* box, Value& result)
|
||||
result = eatBox(box->GetParent<Node>(), box->FirstConnection());
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::ResetBucket(int32 bucketIndex)
|
||||
AnimGraphImpulse* AnimGraphExecutor::GetEmptyNodes()
|
||||
{
|
||||
auto& stateBucket = _data->State[bucketIndex];
|
||||
_graph._bucketInitializerList[bucketIndex](stateBucket);
|
||||
return &Context.Get().EmptyNodes;
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::ResetBuckets(AnimGraphBase* graph)
|
||||
void AnimGraphExecutor::InitNodes(AnimGraphImpulse* nodes) const
|
||||
{
|
||||
const auto& emptyNodes = Context.Get().EmptyNodes;
|
||||
Platform::MemoryCopy(nodes->Nodes.Get(), emptyNodes.Nodes.Get(), sizeof(Transform) * _skeletonNodesCount);
|
||||
nodes->RootMotion = emptyNodes.RootMotion;
|
||||
nodes->Position = emptyNodes.Position;
|
||||
nodes->Length = emptyNodes.Length;
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph)
|
||||
{
|
||||
if (graph == nullptr)
|
||||
return;
|
||||
|
||||
ASSERT(_data);
|
||||
auto& state = context.Data->State;
|
||||
for (int32 i = 0; i < graph->BucketsCountTotal; i++)
|
||||
{
|
||||
const int32 bucketIndex = graph->BucketsStart + i;
|
||||
_graph._bucketInitializerList[bucketIndex](_data->State[bucketIndex]);
|
||||
_graph._bucketInitializerList[bucketIndex](state[bucketIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box)
|
||||
{
|
||||
auto& context = Context.Get();
|
||||
|
||||
// Check if graph is looped or is too deep
|
||||
if (_callStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK)
|
||||
if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK)
|
||||
{
|
||||
OnError(caller, box, TEXT("Graph is looped or too deep!"));
|
||||
return Value::Zero;
|
||||
@@ -309,10 +352,10 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box)
|
||||
#endif
|
||||
|
||||
// Add to the calling stack
|
||||
_callStack.Add(caller);
|
||||
context.CallStack.Add(caller);
|
||||
|
||||
#if USE_EDITOR
|
||||
DebugFlow(_graph._owner, _data->Object, box->GetParent<Node>()->ID, box->ID);
|
||||
Animations::DebugFlow(_graph._owner, context.Data->Object, box->GetParent<Node>()->ID, box->ID);
|
||||
#endif
|
||||
|
||||
// Call per group custom processing event
|
||||
@@ -322,12 +365,13 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box)
|
||||
(this->*func)(box, parentNode, value);
|
||||
|
||||
// Remove from the calling stack
|
||||
_callStack.RemoveLast();
|
||||
context.CallStack.RemoveLast();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
VisjectExecutor::Graph* AnimGraphExecutor::GetCurrentGraph() const
|
||||
{
|
||||
return _graphStack.Peek();
|
||||
auto& context = Context.Get();
|
||||
return context.GraphStack.Peek();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "Engine/Visject/VisjectGraph.h"
|
||||
#include "Engine/Content/Assets/Animation.h"
|
||||
#include "Engine/Core/Collections/ChunkedArray.h"
|
||||
#include "Engine/Animations/AlphaBlend.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "../Config.h"
|
||||
@@ -362,40 +363,17 @@ public:
|
||||
/// <summary>
|
||||
/// Clears this container data.
|
||||
/// </summary>
|
||||
void Clear()
|
||||
{
|
||||
Version = 0;
|
||||
LastUpdateTime = -1;
|
||||
CurrentFrame = 0;
|
||||
RootTransform = Transform::Identity;
|
||||
RootMotion = RootMotionData::Identity;
|
||||
Parameters.Resize(0);
|
||||
State.Resize(0);
|
||||
NodesPose.Resize(0);
|
||||
}
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Clears this container state data.
|
||||
/// </summary>
|
||||
void ClearState()
|
||||
{
|
||||
Version = 0;
|
||||
LastUpdateTime = -1;
|
||||
CurrentFrame = 0;
|
||||
RootTransform = Transform::Identity;
|
||||
RootMotion = RootMotionData::Identity;
|
||||
State.Resize(0);
|
||||
NodesPose.Resize(0);
|
||||
}
|
||||
void ClearState();
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates the update timer.
|
||||
/// </summary>
|
||||
void Invalidate()
|
||||
{
|
||||
LastUpdateTime = -1;
|
||||
CurrentFrame = 0;
|
||||
}
|
||||
void Invalidate();
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -424,18 +402,6 @@ public:
|
||||
: VisjectGraphBox(parent, id, type)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool IsCacheValid() const
|
||||
{
|
||||
return Cache.Type.Type != VariantType::Pointer || Cache.AsPointer != nullptr;
|
||||
}
|
||||
|
||||
void InvalidateCache()
|
||||
{
|
||||
Cache = Variant::Null;
|
||||
}
|
||||
};
|
||||
|
||||
class AnimGraphNode : public VisjectGraphNode<AnimGraphBox>
|
||||
@@ -575,13 +541,6 @@ public:
|
||||
/// </summary>
|
||||
int32 BucketIndex = -1;
|
||||
|
||||
// TODO: use shared allocator per AnimGraph to reduce dynamic memory allocation (also bones data would be closer in memory -> less cache misses)
|
||||
|
||||
/// <summary>
|
||||
/// The node transformations (layout matches the linked to graph skinned model skeleton).
|
||||
/// </summary>
|
||||
AnimGraphImpulse Nodes;
|
||||
|
||||
/// <summary>
|
||||
/// The custom data (depends on node type). Used to cache data for faster usage at runtime.
|
||||
/// </summary>
|
||||
@@ -661,17 +620,11 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the root node of the graph (cache don load).
|
||||
/// </summary>
|
||||
/// <returns>The root node.</returns>
|
||||
FORCE_INLINE Node* GetRootNode() const
|
||||
{
|
||||
return _rootNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all cached values in the graph nodes and the sub-graphs data.
|
||||
/// </summary>
|
||||
void ClearCache();
|
||||
|
||||
/// <summary>
|
||||
/// Loads the sub-graph.
|
||||
/// </summary>
|
||||
@@ -751,9 +704,9 @@ public:
|
||||
AnimGraph(Asset* owner, bool isFunction = false)
|
||||
: AnimGraphBase(this)
|
||||
, _isFunction(isFunction)
|
||||
, _isRegisteredForScriptingEvents(false)
|
||||
, _bucketInitializerList(64)
|
||||
, _owner(owner)
|
||||
, _isRegisteredForScriptingEvents(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -806,6 +759,24 @@ public:
|
||||
bool onParamCreated(Parameter* p) override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The Animation Graph evaluation context.
|
||||
/// </summary>
|
||||
struct AnimGraphContext
|
||||
{
|
||||
float DeltaTime;
|
||||
uint64 CurrentFrameIndex;
|
||||
AnimGraphInstanceData* Data;
|
||||
AnimGraphImpulse EmptyNodes;
|
||||
AnimGraphTransitionData TransitionData;
|
||||
Array<VisjectExecutor::Node*, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> CallStack;
|
||||
Array<VisjectExecutor::Graph*, FixedAllocation<32>> GraphStack;
|
||||
Dictionary<VisjectExecutor::Node*, VisjectExecutor::Graph*> Functions;
|
||||
ChunkedArray<AnimGraphImpulse, 256> PoseCache;
|
||||
int32 PoseCacheSize;
|
||||
Dictionary<VisjectExecutor::Box*, Variant> ValueCache;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The Animation Graph executor runtime for animation pose evaluation.
|
||||
/// </summary>
|
||||
@@ -815,24 +786,14 @@ class AnimGraphExecutor : public VisjectExecutor
|
||||
private:
|
||||
|
||||
AnimGraph& _graph;
|
||||
float _deltaTime = 0.0f;
|
||||
uint64 _currentFrameIndex = 0;
|
||||
int32 _skeletonNodesCount = 0;
|
||||
RootMotionMode _rootMotionMode = RootMotionMode::NoExtraction;
|
||||
AnimGraphInstanceData* _data = nullptr;
|
||||
AnimGraphImpulse _emptyNodes;
|
||||
AnimGraphTransitionData _transitionData;
|
||||
Array<Node*, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> _callStack;
|
||||
Array<Graph*, FixedAllocation<32>> _graphStack;
|
||||
Dictionary<Node*, Graph*> _functions;
|
||||
int32 _skeletonNodesCount = 0;
|
||||
|
||||
// Per-thread context to allow async execution
|
||||
static ThreadLocal<AnimGraphContext, 64> Context;
|
||||
|
||||
public:
|
||||
|
||||
#if USE_EDITOR
|
||||
// Custom event that is called every time the Anim Graph signal flows over the graph (including the data connections). Can be used to read and visualize the animation blending logic.
|
||||
static Delegate<Asset*, ScriptingObject*, uint32, uint32> DebugFlow;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the managed runtime calls.
|
||||
/// </summary>
|
||||
@@ -858,34 +819,10 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the skeleton nodes transformations structure containing identity matrices.
|
||||
/// </summary>
|
||||
FORCE_INLINE const AnimGraphImpulse* GetEmptyNodes() const
|
||||
{
|
||||
return &_emptyNodes;
|
||||
}
|
||||
AnimGraphImpulse* GetEmptyNodes();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the skeleton nodes transformations structure containing identity matrices.
|
||||
/// </summary>
|
||||
/// <returns>The data.</returns>
|
||||
FORCE_INLINE AnimGraphImpulse* GetEmptyNodes()
|
||||
{
|
||||
return &_emptyNodes;
|
||||
}
|
||||
|
||||
FORCE_INLINE void InitNodes(AnimGraphImpulse* nodes) const
|
||||
{
|
||||
// Initialize with cached node transformations
|
||||
Platform::MemoryCopy(nodes->Nodes.Get(), _emptyNodes.Nodes.Get(), sizeof(Transform) * _skeletonNodesCount);
|
||||
nodes->RootMotion = _emptyNodes.RootMotion;
|
||||
nodes->Position = _emptyNodes.Position;
|
||||
nodes->Length = _emptyNodes.Length;
|
||||
}
|
||||
|
||||
FORCE_INLINE void InitNode(AnimGraphImpulse* nodes, int32 index) const
|
||||
{
|
||||
// Initialize with cached node transformation
|
||||
nodes->Nodes[index] = GetEmptyNodes()->Nodes[index];
|
||||
}
|
||||
// Initialize impulse with cached node transformations
|
||||
void InitNodes(AnimGraphImpulse* nodes) const;
|
||||
|
||||
FORCE_INLINE void CopyNodes(AnimGraphImpulse* dstNodes, AnimGraphImpulse* srcNodes) const
|
||||
{
|
||||
@@ -903,16 +840,10 @@ public:
|
||||
CopyNodes(dstNodes, static_cast<AnimGraphImpulse*>(value.AsPointer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the state bucket.
|
||||
/// </summary>
|
||||
/// <param name="bucketIndex">The zero-based index of the bucket.</param>
|
||||
void ResetBucket(int32 bucketIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Resets all the state bucket used by the given graph including sub-graphs (total). Can eb used to reset the animation state of the nested graph (including children).
|
||||
/// </summary>
|
||||
void ResetBuckets(AnimGraphBase* graph);
|
||||
void ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph);
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim)
|
||||
if (anim->Data.RootNodeName.HasChars())
|
||||
{
|
||||
auto& skeleton = _graph.BaseModel->Skeleton;
|
||||
for (int32 i = 0; i < _skeletonNodesCount; i++)
|
||||
for (int32 i = 0; i < skeleton.Nodes.Count(); i++)
|
||||
{
|
||||
if (skeleton.Nodes[i].Name == anim->Data.RootNodeName)
|
||||
{
|
||||
@@ -119,7 +119,7 @@ float GetAnimSamplePos(float length, Animation* anim, float pos, float speed)
|
||||
// Also, scale the animation to fit the total animation node length without cut in a middle
|
||||
const auto animLength = anim->GetLength();
|
||||
const int32 cyclesCount = Math::FloorToInt(length / animLength);
|
||||
const float cycleLength = animLength * cyclesCount;
|
||||
const float cycleLength = animLength * (float)cyclesCount;
|
||||
const float adjustRateScale = length / cycleLength;
|
||||
auto animPos = pos * speed * adjustRateScale;
|
||||
while (animPos > animLength)
|
||||
@@ -152,10 +152,11 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float
|
||||
nodes->Position = pos;
|
||||
nodes->Length = length;
|
||||
const auto mapping = anim->GetMapping(_graph.BaseModel);
|
||||
for (int32 i = 0; i < _skeletonNodesCount; i++)
|
||||
const auto emptyNodes = GetEmptyNodes();
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
const int32 nodeToChannel = mapping->At(i);
|
||||
InitNode(nodes, i);
|
||||
nodes->Nodes[i] = emptyNodes->Nodes[i];
|
||||
if (nodeToChannel != -1)
|
||||
{
|
||||
// Calculate the animated node transformation
|
||||
@@ -197,7 +198,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
|
||||
nodes->Length = length;
|
||||
const auto mappingA = animA->GetMapping(_graph.BaseModel);
|
||||
const auto mappingB = animB->GetMapping(_graph.BaseModel);
|
||||
for (int32 i = 0; i < _skeletonNodesCount; i++)
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
const int32 nodeToChannelA = mappingA->At(i);
|
||||
const int32 nodeToChannelB = mappingB->At(i);
|
||||
@@ -286,12 +287,13 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
|
||||
const auto mappingB = animB->GetMapping(_graph.BaseModel);
|
||||
const auto mappingC = animC->GetMapping(_graph.BaseModel);
|
||||
Transform tmp, t;
|
||||
for (int32 i = 0; i < _skeletonNodesCount; i++)
|
||||
const auto emptyNodes = GetEmptyNodes();
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
const int32 nodeToChannelA = mappingA->At(i);
|
||||
const int32 nodeToChannelB = mappingB->At(i);
|
||||
const int32 nodeToChannelC = mappingC->At(i);
|
||||
tmp = t = GetEmptyNodes()->Nodes[i];
|
||||
tmp = t = emptyNodes->Nodes[i];
|
||||
|
||||
// Calculate the animated node transformations
|
||||
if (nodeToChannelA != -1)
|
||||
@@ -384,7 +386,7 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
|
||||
if (!ANIM_GRAPH_IS_VALID_PTR(poseB))
|
||||
nodesB = GetEmptyNodes();
|
||||
|
||||
for (int32 i = 0; i < _skeletonNodesCount; i++)
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
|
||||
}
|
||||
@@ -443,6 +445,7 @@ void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
|
||||
|
||||
void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value)
|
||||
{
|
||||
auto& context = Context.Get();
|
||||
switch (node->TypeID)
|
||||
{
|
||||
// Get
|
||||
@@ -453,7 +456,7 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu
|
||||
const auto param = _graph.GetParameter((Guid)node->Values[0], paramIndex);
|
||||
if (param)
|
||||
{
|
||||
value = _data->Parameters[paramIndex].Value;
|
||||
value = context.Data->Parameters[paramIndex].Value;
|
||||
switch (param->Type.Type)
|
||||
{
|
||||
case VariantType::Vector2:
|
||||
@@ -523,19 +526,20 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu
|
||||
|
||||
void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value)
|
||||
{
|
||||
auto& context = Context.Get();
|
||||
auto node = (AnimGraphNode*)nodeBase;
|
||||
switch (node->TypeID)
|
||||
{
|
||||
// Time
|
||||
case 5:
|
||||
{
|
||||
auto& bucket = _data->State[node->BucketIndex].Animation;
|
||||
if (bucket.LastUpdateFrame != _currentFrameIndex)
|
||||
auto& bucket = context.Data->State[node->BucketIndex].Animation;
|
||||
if (bucket.LastUpdateFrame != context.CurrentFrameIndex)
|
||||
{
|
||||
bucket.TimePosition += _deltaTime;
|
||||
bucket.LastUpdateFrame = _currentFrameIndex;
|
||||
bucket.TimePosition += context.DeltaTime;
|
||||
bucket.LastUpdateFrame = context.CurrentFrameIndex;
|
||||
}
|
||||
value = box->ID == 0 ? bucket.TimePosition : _deltaTime;
|
||||
value = box->ID == 0 ? bucket.TimePosition : context.DeltaTime;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -546,13 +550,10 @@ void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value
|
||||
|
||||
void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Value& value)
|
||||
{
|
||||
auto box = (AnimGraphBox*)boxBase;
|
||||
if (box->IsCacheValid())
|
||||
{
|
||||
// Return cache
|
||||
value = box->Cache;
|
||||
auto& context = Context.Get();
|
||||
if (context.ValueCache.TryGet(boxBase, value))
|
||||
return;
|
||||
}
|
||||
auto box = (AnimGraphBox*)boxBase;
|
||||
auto node = (AnimGraphNode*)nodeBase;
|
||||
switch (node->TypeID)
|
||||
{
|
||||
@@ -569,7 +570,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
case 2:
|
||||
{
|
||||
const auto anim = node->Assets[0].As<Animation>();
|
||||
auto& bucket = _data->State[node->BucketIndex].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]);
|
||||
@@ -584,17 +585,17 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
const float length = anim ? anim->GetLength() : 0.0f;
|
||||
|
||||
// Calculate new time position
|
||||
if (speed < 0.0f && bucket.LastUpdateFrame < _currentFrameIndex - 1)
|
||||
if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1)
|
||||
{
|
||||
// If speed is negative and it's the first node update then start playing from end
|
||||
bucket.TimePosition = length;
|
||||
}
|
||||
float newTimePos = bucket.TimePosition + _deltaTime * speed;
|
||||
float newTimePos = bucket.TimePosition + context.DeltaTime * speed;
|
||||
|
||||
value = SampleAnimation(node, loop, length, startTimePos, bucket.TimePosition, newTimePos, anim, 1.0f);
|
||||
|
||||
bucket.TimePosition = newTimePos;
|
||||
bucket.LastUpdateFrame = _currentFrameIndex;
|
||||
bucket.LastUpdateFrame = context.CurrentFrameIndex;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -615,7 +616,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
// Is Playing
|
||||
case 4:
|
||||
// If anim was updated during this or a previous frame
|
||||
value = bucket.LastUpdateFrame >= _currentFrameIndex - 1;
|
||||
value = bucket.LastUpdateFrame >= context.CurrentFrameIndex - 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -643,7 +644,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
value = Value::Null;
|
||||
if (inputBox->HasConnection())
|
||||
value = eatBox(nodeBase, inputBox->FirstConnection());
|
||||
box->Cache = value;
|
||||
context.ValueCache.Add(boxBase, value);
|
||||
return;
|
||||
}
|
||||
const auto nodeIndex = _graph.BaseModel->Skeleton.Bones[boneIndex].NodeIndex;
|
||||
@@ -690,7 +691,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
|
||||
// Transform every node
|
||||
const auto& skeleton = BaseModel->Skeleton;
|
||||
for (int32 i = 0; i < _skeletonNodesCount; i++)
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
const int32 parentIndex = skeleton.Nodes[i].ParentIndex;
|
||||
if (parentIndex != -1)
|
||||
@@ -729,7 +730,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
|
||||
// Inv transform every node
|
||||
const auto& skeleton = BaseModel->Skeleton;
|
||||
for (int32 i = _skeletonNodesCount - 1; i >= 0; i--)
|
||||
for (int32 i = nodes->Nodes.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
const int32 parentIndex = skeleton.Nodes[i].ParentIndex;
|
||||
if (parentIndex != -1)
|
||||
@@ -775,7 +776,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
{
|
||||
// Pass through the input
|
||||
value = input;
|
||||
box->Cache = value;
|
||||
context.ValueCache.Add(boxBase, value);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -836,7 +837,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
if (!ANIM_GRAPH_IS_VALID_PTR(valueB))
|
||||
nodesB = GetEmptyNodes();
|
||||
|
||||
for (int32 i = 0; i < _skeletonNodesCount; i++)
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
|
||||
}
|
||||
@@ -876,7 +877,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
const auto nodesA = static_cast<AnimGraphImpulse*>(valueA.AsPointer);
|
||||
const auto nodesB = static_cast<AnimGraphImpulse*>(valueB.AsPointer);
|
||||
Transform t, tA, tB;
|
||||
for (int32 i = 0; i < _skeletonNodesCount; i++)
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
tA = nodesA->Nodes[i];
|
||||
tB = nodesB->Nodes[i];
|
||||
@@ -921,7 +922,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
// Blend all nodes masked by the user
|
||||
Transform tA, tB;
|
||||
auto& nodesMask = mask->GetNodesMask();
|
||||
for (int32 nodeIndex = 0; nodeIndex < _skeletonNodesCount; nodeIndex++)
|
||||
for (int32 nodeIndex = 0; nodeIndex < nodes->Nodes.Count(); nodeIndex++)
|
||||
{
|
||||
tA = nodesA->Nodes[nodeIndex];
|
||||
if (nodesMask[nodeIndex])
|
||||
@@ -956,7 +957,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
// [1]: Guid Animation
|
||||
|
||||
// Prepare
|
||||
auto& bucket = _data->State[node->BucketIndex].MultiBlend;
|
||||
auto& bucket = context.Data->State[node->BucketIndex].MultiBlend;
|
||||
const auto range = node->Values[0].AsVector4();
|
||||
const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]);
|
||||
const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]);
|
||||
@@ -988,12 +989,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
}
|
||||
|
||||
// Calculate new time position
|
||||
if (speed < 0.0f && bucket.LastUpdateFrame < _currentFrameIndex - 1)
|
||||
if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1)
|
||||
{
|
||||
// If speed is negative and it's the first node update then start playing from end
|
||||
bucket.TimePosition = data.Length;
|
||||
}
|
||||
float newTimePos = bucket.TimePosition + _deltaTime * speed;
|
||||
float newTimePos = bucket.TimePosition + context.DeltaTime * speed;
|
||||
|
||||
ANIM_GRAPH_PROFILE_EVENT("Multi Blend 1D");
|
||||
|
||||
@@ -1035,7 +1036,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
}
|
||||
|
||||
bucket.TimePosition = newTimePos;
|
||||
bucket.LastUpdateFrame = _currentFrameIndex;
|
||||
bucket.LastUpdateFrame = context.CurrentFrameIndex;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -1054,7 +1055,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
// [1]: Guid Animation
|
||||
|
||||
// Prepare
|
||||
auto& bucket = _data->State[node->BucketIndex].MultiBlend;
|
||||
auto& bucket = context.Data->State[node->BucketIndex].MultiBlend;
|
||||
const auto range = node->Values[0].AsVector4();
|
||||
const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]);
|
||||
const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]);
|
||||
@@ -1090,12 +1091,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
}
|
||||
|
||||
// Calculate new time position
|
||||
if (speed < 0.0f && bucket.LastUpdateFrame < _currentFrameIndex - 1)
|
||||
if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1)
|
||||
{
|
||||
// If speed is negative and it's the first node update then start playing from end
|
||||
bucket.TimePosition = data.Length;
|
||||
}
|
||||
float newTimePos = bucket.TimePosition + _deltaTime * speed;
|
||||
float newTimePos = bucket.TimePosition + context.DeltaTime * speed;
|
||||
|
||||
ANIM_GRAPH_PROFILE_EVENT("Multi Blend 2D");
|
||||
|
||||
@@ -1227,7 +1228,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
}
|
||||
|
||||
bucket.TimePosition = newTimePos;
|
||||
bucket.LastUpdateFrame = _currentFrameIndex;
|
||||
bucket.LastUpdateFrame = context.CurrentFrameIndex;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -1246,7 +1247,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
// [3]: AlphaBlendMode Mode
|
||||
|
||||
// Prepare
|
||||
auto& bucket = _data->State[node->BucketIndex].BlendPose;
|
||||
auto& bucket = context.Data->State[node->BucketIndex].BlendPose;
|
||||
const int32 poseIndex = (int32)tryGetValue(node->GetBox(1), node->Values[0]);
|
||||
const float blendDuration = (float)tryGetValue(node->GetBox(2), node->Values[1]);
|
||||
const int32 poseCount = Math::Clamp(node->Values[2].AsInt, 0, MaxBlendPoses);
|
||||
@@ -1259,7 +1260,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
}
|
||||
|
||||
// Check if transition is not active (first update, pose not changing or transition ended)
|
||||
bucket.TransitionPosition += _deltaTime;
|
||||
bucket.TransitionPosition += context.DeltaTime;
|
||||
if (bucket.PreviousBlendPoseIndex == -1 || bucket.PreviousBlendPoseIndex == poseIndex || bucket.TransitionPosition >= blendDuration || blendDuration <= ANIM_GRAPH_BLEND_THRESHOLD)
|
||||
{
|
||||
bucket.TransitionPosition = 0.0f;
|
||||
@@ -1356,11 +1357,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
ANIM_GRAPH_PROFILE_EVENT("State Machine");
|
||||
|
||||
// Prepare
|
||||
auto& bucket = _data->State[node->BucketIndex].StateMachine;
|
||||
auto& bucket = context.Data->State[node->BucketIndex].StateMachine;
|
||||
auto& data = node->Data.StateMachine;
|
||||
int32 transitionsLeft = maxTransitionsPerUpdate == 0 ? MAX_uint16 : maxTransitionsPerUpdate;
|
||||
bool isFirstUpdate = bucket.LastUpdateFrame == 0 || bucket.CurrentState == nullptr;
|
||||
if (bucket.LastUpdateFrame != _currentFrameIndex - 1 && reinitializeOnBecomingRelevant)
|
||||
if (bucket.LastUpdateFrame != context.CurrentFrameIndex - 1 && reinitializeOnBecomingRelevant)
|
||||
{
|
||||
// Reset on becoming relevant
|
||||
isFirstUpdate = true;
|
||||
@@ -1384,19 +1385,19 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
bucket.TransitionPosition = 0.0f;
|
||||
|
||||
// Reset all state buckets pof the graphs and nodes included inside the state machine
|
||||
ResetBuckets(data.Graph);
|
||||
ResetBuckets(context, data.Graph);
|
||||
}
|
||||
|
||||
// Update the active transition
|
||||
if (bucket.ActiveTransition)
|
||||
{
|
||||
bucket.TransitionPosition += _deltaTime;
|
||||
bucket.TransitionPosition += context.DeltaTime;
|
||||
|
||||
// Check ofr transition end
|
||||
// Check for transition end
|
||||
if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration)
|
||||
{
|
||||
// End transition
|
||||
ResetBuckets(bucket.CurrentState->Data.State.Graph);
|
||||
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
|
||||
bucket.CurrentState = bucket.ActiveTransition->Destination;
|
||||
bucket.ActiveTransition = nullptr;
|
||||
bucket.TransitionPosition = 0.0f;
|
||||
@@ -1422,7 +1423,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
|
||||
// Evaluate source state transition data (position, length, etc.)
|
||||
const Value sourceStatePtr = SampleState(bucket.CurrentState);
|
||||
auto& transitionData = _transitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
|
||||
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
|
||||
@@ -1475,7 +1476,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
if (bucket.ActiveTransition && bucket.ActiveTransition->BlendDuration <= ZeroTolerance)
|
||||
{
|
||||
// End transition
|
||||
ResetBuckets(bucket.CurrentState->Data.State.Graph);
|
||||
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
|
||||
bucket.CurrentState = bucket.ActiveTransition->Destination;
|
||||
bucket.ActiveTransition = nullptr;
|
||||
bucket.TransitionPosition = 0.0f;
|
||||
@@ -1498,7 +1499,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
}
|
||||
|
||||
// Update bucket
|
||||
bucket.LastUpdateFrame = _currentFrameIndex;
|
||||
bucket.LastUpdateFrame = context.CurrentFrameIndex;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -1537,7 +1538,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
// Transition Source State Anim
|
||||
case 23:
|
||||
{
|
||||
const AnimGraphTransitionData& transitionsData = _transitionData;
|
||||
const AnimGraphTransitionData& transitionsData = context.TransitionData;
|
||||
switch (box->ID)
|
||||
{
|
||||
// Length
|
||||
@@ -1587,7 +1588,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
if (callFunc == function)
|
||||
{
|
||||
value = Value::Zero;
|
||||
box->Cache = value;
|
||||
context.ValueCache.Add(boxBase, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1606,12 +1607,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
Box* functionOutputBox = functionOutputNode->TryGetBox(0);
|
||||
|
||||
// Cache relation between current node in the call stack to the actual function graph
|
||||
_functions[nodeBase] = (Graph*)data.Graph;
|
||||
context.Functions[nodeBase] = (Graph*)data.Graph;
|
||||
|
||||
// Evaluate the function output
|
||||
_graphStack.Push((Graph*)data.Graph);
|
||||
context.GraphStack.Push((Graph*)data.Graph);
|
||||
value = functionOutputBox && functionOutputBox->HasConnection() ? eatBox(nodeBase, functionOutputBox->FirstConnection()) : Value::Zero;
|
||||
_graphStack.Pop();
|
||||
context.GraphStack.Pop();
|
||||
break;
|
||||
}
|
||||
// Transform Bone (local/model space)
|
||||
@@ -1635,7 +1636,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
value = Value::Null;
|
||||
if (inputBox->HasConnection())
|
||||
value = eatBox(nodeBase, inputBox->FirstConnection());
|
||||
box->Cache = value;
|
||||
context.ValueCache.Add(boxBase, value);
|
||||
return;
|
||||
}
|
||||
const auto nodes = node->GetNodes(this);
|
||||
@@ -1704,7 +1705,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
{
|
||||
// Pass through the input
|
||||
value = input;
|
||||
box->Cache = value;
|
||||
context.ValueCache.Add(boxBase, value);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1859,18 +1860,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
default:
|
||||
break;
|
||||
}
|
||||
box->Cache = value;
|
||||
context.ValueCache.Add(boxBase, value);
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& value)
|
||||
{
|
||||
auto box = (AnimGraphBox*)boxBase;
|
||||
if (box->IsCacheValid())
|
||||
{
|
||||
// Return cache
|
||||
value = box->Cache;
|
||||
auto& context = Context.Get();
|
||||
if (context.ValueCache.TryGet(boxBase, value))
|
||||
return;
|
||||
}
|
||||
switch (node->TypeID)
|
||||
{
|
||||
// Function Input
|
||||
@@ -1878,13 +1875,13 @@ void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& va
|
||||
{
|
||||
// Find the function call
|
||||
AnimGraphNode* functionCallNode = nullptr;
|
||||
ASSERT(_graphStack.Count() >= 2);
|
||||
ASSERT(context.GraphStack.Count() >= 2);
|
||||
Graph* graph;
|
||||
for (int32 i = _callStack.Count() - 1; i >= 0; i--)
|
||||
for (int32 i = context.CallStack.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(9, 24) && _functions.TryGet(_callStack[i], graph) && _graphStack[_graphStack.Count() - 1] == (Graph*)graph)
|
||||
if (context.CallStack[i]->Type == GRAPH_NODE_MAKE_TYPE(9, 24) && context.Functions.TryGet(context.CallStack[i], graph) && context.GraphStack.Last() == (Graph*)graph)
|
||||
{
|
||||
functionCallNode = (AnimGraphNode*)_callStack[i];
|
||||
functionCallNode = (AnimGraphNode*)context.CallStack[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1926,19 +1923,19 @@ void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& va
|
||||
if (functionCallBox && functionCallBox->HasConnection())
|
||||
{
|
||||
// Use provided input value from the function call
|
||||
_graphStack.Pop();
|
||||
context.GraphStack.Pop();
|
||||
value = eatBox(node, functionCallBox->FirstConnection());
|
||||
_graphStack.Push(graph);
|
||||
context.GraphStack.Push(graph);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the default value from the function graph
|
||||
value = tryGetValue(node->TryGetBox(1), Value::Zero);
|
||||
}
|
||||
context.ValueCache.Add(boxBase, value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
box->Cache = value;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "AudioBackend.h"
|
||||
#include "Audio.h"
|
||||
|
||||
@@ -340,6 +341,8 @@ bool AudioSource::IntersectsItself(const Ray& ray, float& distance, Vector3& nor
|
||||
|
||||
void AudioSource::Update()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
// Update the velocity
|
||||
const Vector3 pos = GetPosition();
|
||||
const float dt = Math::Max(Time::Update.UnscaledDeltaTime.GetTotalSeconds(), ZeroTolerance);
|
||||
|
||||
@@ -493,17 +493,8 @@ public:
|
||||
/// Adds the other collection to the collection.
|
||||
/// </summary>
|
||||
/// <param name="other">The other collection to add.</param>
|
||||
FORCE_INLINE void Add(const Array& other)
|
||||
{
|
||||
Add(other.Get(), other.Count());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the other collection to the collection.
|
||||
/// </summary>
|
||||
/// <param name="other">The other collection to add.</param>
|
||||
template<typename U>
|
||||
FORCE_INLINE void Add(const Array<U>& other)
|
||||
template<typename OtherT, typename OtherAllocationType = HeapAllocation>
|
||||
FORCE_INLINE void Add(const Array<OtherT, OtherAllocationType>& other)
|
||||
{
|
||||
Add(other.Get(), other.Count());
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the amount of the elements in the collection.
|
||||
/// </summary>
|
||||
/// <returns>The amount of the elements in the collection.</returns>
|
||||
FORCE_INLINE int32 Count() const
|
||||
{
|
||||
return _count;
|
||||
@@ -55,7 +54,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the amount of the elements that can be hold by collection without resizing.
|
||||
/// </summary>
|
||||
/// <returns>The current capacity of the collection.</returns>
|
||||
FORCE_INLINE int32 Capacity() const
|
||||
{
|
||||
return _chunks.Count() * ChunkSize;
|
||||
@@ -64,7 +62,6 @@ public:
|
||||
/// <summary>
|
||||
/// Returns true if array isn't empty.
|
||||
/// </summary>
|
||||
/// <returns>True if array has any elements added, otherwise it is empty.</returns>
|
||||
FORCE_INLINE bool HasItems() const
|
||||
{
|
||||
return _count != 0;
|
||||
@@ -73,7 +70,6 @@ public:
|
||||
/// <summary>
|
||||
/// Returns true if collection is empty.
|
||||
/// </summary>
|
||||
/// <returns>True if array is empty, otherwise it has any elements added.</returns>
|
||||
FORCE_INLINE bool IsEmpty() const
|
||||
{
|
||||
return _count == 0;
|
||||
@@ -154,20 +150,12 @@ public:
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Checks if iterator is in the end of the collection.
|
||||
/// </summary>
|
||||
/// <returns>True if is in the end, otherwise false.</returns>
|
||||
bool IsEnd() const
|
||||
{
|
||||
ASSERT(_collection);
|
||||
return Index() == _collection->Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if iterator is not in the end of the collection.
|
||||
/// </summary>
|
||||
/// <returns>True if is not in the end, otherwise false.</returns>
|
||||
bool IsNotEnd() const
|
||||
{
|
||||
ASSERT(_collection);
|
||||
@@ -331,6 +319,36 @@ public:
|
||||
return &chunk->At(chunk->Count() - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the one item to the collection and returns the reference to it.
|
||||
/// </summary>
|
||||
/// <returns>The reference to the added item.</returns>
|
||||
T& AddOne()
|
||||
{
|
||||
// Find first chunk with some space
|
||||
Chunk* chunk = nullptr;
|
||||
for (int32 i = 0; i < _chunks.Count(); i++)
|
||||
{
|
||||
if (_chunks[i]->Count() < ChunkSize)
|
||||
{
|
||||
chunk = _chunks[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate chunk if missing
|
||||
if (chunk == nullptr)
|
||||
{
|
||||
chunk = New<Chunk>();
|
||||
chunk->SetCapacity(ChunkSize);
|
||||
_chunks.Add(chunk);
|
||||
}
|
||||
|
||||
// Add item
|
||||
_count++;
|
||||
return chunk->AddOne();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the element at specified iterator position.
|
||||
/// </summary>
|
||||
@@ -408,7 +426,6 @@ public:
|
||||
/// <param name="newSize">The new size.</param>
|
||||
void Resize(int32 newSize)
|
||||
{
|
||||
// Check if shrink
|
||||
if (newSize < Count())
|
||||
{
|
||||
MISSING_CODE("shrinking ChunkedArray on Resize");
|
||||
@@ -439,7 +456,6 @@ public:
|
||||
chunkIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(newSize == Count());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Platform/Platform.h"
|
||||
#include "Engine/Core/Memory/Memory.h"
|
||||
#include "Engine/Core/Memory/Allocation.h"
|
||||
|
||||
/// <summary>
|
||||
/// Template for ring buffer with variable capacity.
|
||||
/// </summary>
|
||||
template<typename T, typename AllocationType = HeapAllocation>
|
||||
class RingBuffer
|
||||
{
|
||||
public:
|
||||
|
||||
typedef T ItemType;
|
||||
typedef typename AllocationType::template Data<T> AllocationData;
|
||||
|
||||
private:
|
||||
|
||||
int32 _front = 0, _back = 0, _count = 0, _capacity = 0;
|
||||
AllocationData _allocation;
|
||||
|
||||
public:
|
||||
|
||||
~RingBuffer()
|
||||
{
|
||||
Memory::DestructItems(Get() + Math::Min(_front, _back), _count);
|
||||
}
|
||||
|
||||
FORCE_INLINE T* Get()
|
||||
{
|
||||
return _allocation.Get();
|
||||
}
|
||||
|
||||
FORCE_INLINE int32 Count() const
|
||||
{
|
||||
return _count;
|
||||
}
|
||||
|
||||
FORCE_INLINE int32 Capacity() const
|
||||
{
|
||||
return _capacity;
|
||||
}
|
||||
|
||||
void PushBack(const T& data)
|
||||
{
|
||||
if (_capacity == 0 || _capacity == _count)
|
||||
{
|
||||
const int32 capacity = _allocation.CalculateCapacityGrow(_capacity, _count + 1);
|
||||
AllocationData alloc;
|
||||
alloc.Allocate(capacity);
|
||||
const int32 frontCount = Math::Min(_capacity - _front, _count);
|
||||
Memory::MoveItems(alloc.Get(), _allocation.Get() + _front, frontCount);
|
||||
Memory::DestructItems(_allocation.Get() + _front, frontCount);
|
||||
const int32 backCount = _count - frontCount;
|
||||
Memory::MoveItems(alloc.Get() + frontCount, _allocation.Get(), backCount);
|
||||
Memory::DestructItems(_allocation.Get(), backCount);
|
||||
_allocation.Swap(alloc);
|
||||
_front = 0;
|
||||
_back = _count;
|
||||
_capacity = capacity;
|
||||
}
|
||||
Memory::ConstructItems(_allocation.Get() + _back, &data, 1);
|
||||
_back = (_back + 1) % _capacity;
|
||||
_count++;
|
||||
}
|
||||
|
||||
FORCE_INLINE T& PeekFront()
|
||||
{
|
||||
ASSERT(_front != _back);
|
||||
return _allocation.Get()[_front];
|
||||
}
|
||||
|
||||
FORCE_INLINE const T& PeekFront() const
|
||||
{
|
||||
ASSERT(_front != _back);
|
||||
return _allocation.Get()[_front];
|
||||
}
|
||||
|
||||
FORCE_INLINE T& operator[](int32 index)
|
||||
{
|
||||
ASSERT(index >= 0 && index < _count);
|
||||
return _allocation.Get()[(_front + index) % _capacity];
|
||||
}
|
||||
|
||||
FORCE_INLINE const T& operator[](int32 index) const
|
||||
{
|
||||
ASSERT(index >= 0 && index < _count);
|
||||
return _allocation.Get()[(_front + index) % _capacity];
|
||||
}
|
||||
|
||||
void PopFront()
|
||||
{
|
||||
ASSERT(_front != _back);
|
||||
Memory::DestructItems(_allocation.Get() + _front, 1);
|
||||
_front = (_front + 1) % _capacity;
|
||||
_count--;
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
Memory::DestructItems(Get() + Math::Min(_front, _back), _count);
|
||||
_front = _back = _count = 0;
|
||||
}
|
||||
};
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Profiler/Profiler.h"
|
||||
#include "Engine/Threading/TaskGraph.h"
|
||||
#if USE_EDITOR
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/ProjectInfo.h"
|
||||
@@ -62,6 +63,7 @@ bool Engine::HasFocus = false;
|
||||
uint64 Engine::FrameCount = 0;
|
||||
Action Engine::FixedUpdate;
|
||||
Action Engine::Update;
|
||||
TaskGraph* Engine::UpdateGraph = nullptr;
|
||||
Action Engine::LateUpdate;
|
||||
Action Engine::Draw;
|
||||
Action Engine::Pause;
|
||||
@@ -122,6 +124,7 @@ int32 Engine::Main(const Char* cmdLine)
|
||||
#endif
|
||||
|
||||
// Initialize engine
|
||||
UpdateGraph = New<TaskGraph>();
|
||||
EngineService::OnInit();
|
||||
if (Application::Init())
|
||||
return -10;
|
||||
@@ -289,6 +292,7 @@ void Engine::OnUpdate()
|
||||
|
||||
// Call event
|
||||
Update();
|
||||
UpdateGraph->Execute();
|
||||
|
||||
// Update services
|
||||
EngineService::OnUpdate();
|
||||
@@ -436,6 +440,7 @@ void Engine::OnExit()
|
||||
|
||||
// Unload Engine services
|
||||
EngineService::OnDispose();
|
||||
Delete(UpdateGraph);
|
||||
|
||||
LOG_FLUSH();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
class TaskGraph;
|
||||
class JsonAsset;
|
||||
|
||||
/// <summary>
|
||||
@@ -43,6 +44,11 @@ public:
|
||||
/// </summary>
|
||||
static Action Update;
|
||||
|
||||
/// <summary>
|
||||
/// Task graph for engine update.
|
||||
/// </summary>
|
||||
API_FIELD(ReadOnly) static TaskGraph* UpdateGraph;
|
||||
|
||||
/// <summary>
|
||||
/// Event called after engine update.
|
||||
/// </summary>
|
||||
|
||||
@@ -41,10 +41,27 @@ void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory)
|
||||
{
|
||||
if (!bones)
|
||||
return;
|
||||
ASSERT(BonesCount > 0);
|
||||
|
||||
ANIM_GRAPH_PROFILE_EVENT("SetSkinnedMeshData");
|
||||
|
||||
// Copy bones to the buffer
|
||||
const int32 count = BonesCount;
|
||||
const int32 preFetchStride = 2;
|
||||
const Matrix* input = bones;
|
||||
const auto output = (Matrix3x4*)Data.Get();
|
||||
ASSERT(Data.Count() == count * sizeof(Matrix3x4));
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
Matrix3x4* bone = output + i;
|
||||
Platform::Prefetch(bone + preFetchStride);
|
||||
Platform::Prefetch((byte*)(bone + preFetchStride) + PLATFORM_CACHE_LINE_SIZE);
|
||||
bone->SetMatrixTranspose(input[i]);
|
||||
}
|
||||
|
||||
OnDataChanged(dropHistory);
|
||||
}
|
||||
|
||||
void SkinnedMeshDrawData::OnDataChanged(bool dropHistory)
|
||||
{
|
||||
// Setup previous frame bone matrices if needed
|
||||
if (_hasValidData && !dropHistory)
|
||||
{
|
||||
@@ -64,20 +81,6 @@ void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory)
|
||||
SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices);
|
||||
}
|
||||
|
||||
// Copy bones to the buffer
|
||||
const int32 count = BonesCount;
|
||||
const int32 preFetchStride = 2;
|
||||
const Matrix* input = bones;
|
||||
const auto output = (Matrix3x4*)Data.Get();
|
||||
ASSERT(Data.Count() == count * sizeof(Matrix3x4));
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
Matrix3x4* bone = output + i;
|
||||
Platform::Prefetch(bone + preFetchStride);
|
||||
Platform::Prefetch((byte*)(bone + preFetchStride) + PLATFORM_CACHE_LINE_SIZE);
|
||||
bone->SetMatrixTranspose(input[i]);
|
||||
}
|
||||
|
||||
_isDirty = true;
|
||||
_hasValidData = true;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ public:
|
||||
/// <summary>
|
||||
/// Determines whether this instance is ready for rendering.
|
||||
/// </summary>
|
||||
/// <returns>True if has valid data and can be rendered, otherwise false.</returns>
|
||||
FORCE_INLINE bool IsReady() const
|
||||
{
|
||||
return BoneMatrices != nullptr && BoneMatrices->IsAllocated();
|
||||
@@ -73,6 +72,12 @@ public:
|
||||
/// <param name="dropHistory">True if drop previous update bones used for motion blur, otherwise will keep them and do the update.</param>
|
||||
void SetData(const Matrix* bones, bool dropHistory);
|
||||
|
||||
/// <summary>
|
||||
/// After bones Data has been modified externally. Updates the bone matrices data for the GPU buffer. Ensure to call Flush before rendering.
|
||||
/// </summary>
|
||||
/// <param name="dropHistory">True if drop previous update bones used for motion blur, otherwise will keep them and do the update.</param>
|
||||
void OnDataChanged(bool dropHistory);
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the bones data buffer with the GPU by sending the data fro the CPU.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "AnimatedModel.h"
|
||||
#include "BoneSocket.h"
|
||||
#include "Engine/Core/Math/Matrix3x4.h"
|
||||
#include "Engine/Animations/Animations.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#if USE_EDITOR
|
||||
@@ -13,8 +14,6 @@
|
||||
#include "Engine/Level/SceneObjectsFactory.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
|
||||
extern Array<Matrix> UpdateBones;
|
||||
|
||||
AnimatedModel::AnimatedModel(const SpawnParams& params)
|
||||
: ModelInstanceActor(params)
|
||||
, _actualMode(AnimationUpdateMode::Never)
|
||||
@@ -114,16 +113,6 @@ void AnimatedModel::PreInitSkinningData()
|
||||
UpdateSockets();
|
||||
}
|
||||
|
||||
void AnimatedModel::UpdateSockets()
|
||||
{
|
||||
for (int32 i = 0; i < Children.Count(); i++)
|
||||
{
|
||||
auto socket = dynamic_cast<BoneSocket*>(Children[i]);
|
||||
if (socket)
|
||||
socket->UpdateTransformation();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedModel::GetCurrentPose(Array<Matrix>& nodesTransformation, bool worldSpace) const
|
||||
{
|
||||
nodesTransformation = GraphInstance.NodesPose;
|
||||
@@ -452,9 +441,19 @@ void AnimatedModel::UpdateBounds()
|
||||
BoundingSphere::FromBox(_box, _sphere);
|
||||
}
|
||||
|
||||
void AnimatedModel::OnAnimationUpdated()
|
||||
void AnimatedModel::UpdateSockets()
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("OnAnimationUpdated");
|
||||
for (int32 i = 0; i < Children.Count(); i++)
|
||||
{
|
||||
auto socket = dynamic_cast<BoneSocket*>(Children[i]);
|
||||
if (socket)
|
||||
socket->UpdateTransformation();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedModel::OnAnimationUpdated_Async()
|
||||
{
|
||||
// Update asynchronous stuff
|
||||
auto& skeleton = SkinnedModel->Skeleton;
|
||||
|
||||
// Copy pose from the master
|
||||
@@ -470,22 +469,37 @@ void AnimatedModel::OnAnimationUpdated()
|
||||
// Calculate the final bones transformations and update skinning
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Final Pose");
|
||||
UpdateBones.Resize(skeleton.Bones.Count(), false);
|
||||
for (int32 boneIndex = 0; boneIndex < skeleton.Bones.Count(); boneIndex++)
|
||||
const int32 bonesCount = skeleton.Bones.Count();
|
||||
Matrix3x4* output = (Matrix3x4*)_skinningData.Data.Get();
|
||||
ASSERT(_skinningData.Data.Count() == bonesCount * sizeof(Matrix3x4));
|
||||
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
|
||||
{
|
||||
auto& bone = skeleton.Bones[boneIndex];
|
||||
UpdateBones[boneIndex] = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex];
|
||||
Matrix matrix = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex];
|
||||
output[boneIndex].SetMatrixTranspose(matrix);
|
||||
}
|
||||
_skinningData.OnDataChanged(!PerBoneMotionBlur);
|
||||
}
|
||||
_skinningData.SetData(UpdateBones.Get(), !PerBoneMotionBlur);
|
||||
|
||||
UpdateBounds();
|
||||
_blendShapes.Update(SkinnedModel.Get());
|
||||
}
|
||||
|
||||
void AnimatedModel::OnAnimationUpdated_Sync()
|
||||
{
|
||||
// Update synchronous stuff
|
||||
UpdateSockets();
|
||||
ApplyRootMotion(GraphInstance.RootMotion);
|
||||
_blendShapes.Update(SkinnedModel.Get());
|
||||
AnimationUpdated();
|
||||
}
|
||||
|
||||
void AnimatedModel::OnAnimationUpdated()
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("OnAnimationUpdated");
|
||||
OnAnimationUpdated_Async();
|
||||
OnAnimationUpdated_Sync();
|
||||
}
|
||||
|
||||
void AnimatedModel::OnSkinnedModelChanged()
|
||||
{
|
||||
Entries.Release();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
API_CLASS() class FLAXENGINE_API AnimatedModel : public ModelInstanceActor
|
||||
{
|
||||
DECLARE_SCENE_OBJECT(AnimatedModel);
|
||||
friend class AnimationsService;
|
||||
friend class AnimationsSystem;
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
@@ -306,6 +306,8 @@ private:
|
||||
void UpdateLocalBounds();
|
||||
void UpdateBounds();
|
||||
void UpdateSockets();
|
||||
void OnAnimationUpdated_Async();
|
||||
void OnAnimationUpdated_Sync();
|
||||
void OnAnimationUpdated();
|
||||
|
||||
void OnSkinnedModelChanged();
|
||||
|
||||
@@ -45,7 +45,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets priority level of the thread.
|
||||
/// </summary>
|
||||
/// <returns>The thread priority level.</returns>
|
||||
FORCE_INLINE ThreadPriority GetPriority() const
|
||||
{
|
||||
return _priority;
|
||||
@@ -60,7 +59,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets thread ID
|
||||
/// </summary>
|
||||
/// <returns>Thread ID</returns>
|
||||
FORCE_INLINE uint64 GetID() const
|
||||
{
|
||||
return _id;
|
||||
@@ -69,7 +67,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets thread running state.
|
||||
/// </summary>
|
||||
/// <returns>True if thread is running, otherwise false</returns>
|
||||
FORCE_INLINE bool IsRunning() const
|
||||
{
|
||||
return _isRunning;
|
||||
@@ -78,7 +75,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets name of the thread.
|
||||
/// </summary>
|
||||
/// <returns>The thread name.</returns>
|
||||
FORCE_INLINE const String& GetName() const
|
||||
{
|
||||
return _name;
|
||||
|
||||
@@ -12,7 +12,7 @@ bool ProfilerCPU::Enabled = false;
|
||||
|
||||
ProfilerCPU::EventBuffer::EventBuffer()
|
||||
{
|
||||
_capacity = Math::RoundUpToPowerOf2(10 * 1000);
|
||||
_capacity = 8192;
|
||||
_capacityMask = _capacity - 1;
|
||||
_data = NewArray<Event>(_capacity);
|
||||
_head = 0;
|
||||
@@ -122,6 +122,14 @@ void ProfilerCPU::Thread::EndEvent(int32 index)
|
||||
e.End = time;
|
||||
}
|
||||
|
||||
void ProfilerCPU::Thread::EndEvent()
|
||||
{
|
||||
const double time = Platform::GetTimeSeconds() * 1000.0;
|
||||
_depth--;
|
||||
Event& e = Buffer.Get(Buffer.GetCount() - 1);
|
||||
e.End = time;
|
||||
}
|
||||
|
||||
bool ProfilerCPU::IsProfilingCurrentThread()
|
||||
{
|
||||
return Enabled && Thread::Current != nullptr;
|
||||
@@ -194,11 +202,14 @@ int32 ProfilerCPU::BeginEvent(const char* name)
|
||||
|
||||
void ProfilerCPU::EndEvent(int32 index)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
if (Enabled && Thread::Current)
|
||||
Thread::Current->EndEvent(index);
|
||||
}
|
||||
|
||||
ASSERT(Thread::Current);
|
||||
Thread::Current->EndEvent(index);
|
||||
void ProfilerCPU::EndEvent()
|
||||
{
|
||||
if (Enabled && Thread::Current)
|
||||
Thread::Current->EndEvent();
|
||||
}
|
||||
|
||||
void ProfilerCPU::Dispose()
|
||||
|
||||
@@ -289,6 +289,11 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="index">The event index returned by the BeginEvent method.</param>
|
||||
void EndEvent(int32 index);
|
||||
|
||||
/// <summary>
|
||||
/// Ends the last event running on a this thread.
|
||||
/// </summary>
|
||||
void EndEvent();
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -341,6 +346,11 @@ public:
|
||||
/// <param name="index">The event index returned by the BeginEvent method.</param>
|
||||
static void EndEvent(int32 index);
|
||||
|
||||
/// <summary>
|
||||
/// Ends the last event.
|
||||
/// </summary>
|
||||
static void EndEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Releases resources. Calls to the profiling API after Dispose are not valid.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,29 +14,21 @@
|
||||
|
||||
namespace ProfilerInternal
|
||||
{
|
||||
/// <summary>
|
||||
/// The managed events IDs.
|
||||
/// </summary>
|
||||
Array<int32> ManagedEvents;
|
||||
|
||||
/// <summary>
|
||||
/// The managed events IDs for GPU profiling.
|
||||
/// </summary>
|
||||
#if COMPILE_WITH_PROFILER
|
||||
Array<int32> ManagedEventsGPU;
|
||||
#endif
|
||||
|
||||
void BeginEvent(MonoString* nameObj)
|
||||
{
|
||||
#if COMPILE_WITH_PROFILER
|
||||
const auto index = ProfilerCPU::BeginEvent((const Char*)mono_string_chars(nameObj));
|
||||
ManagedEvents.Push(index);
|
||||
ProfilerCPU::BeginEvent((const Char*)mono_string_chars(nameObj));
|
||||
#endif
|
||||
}
|
||||
|
||||
void EndEvent()
|
||||
{
|
||||
#if COMPILE_WITH_PROFILER
|
||||
const auto index = ManagedEvents.Pop();
|
||||
ProfilerCPU::EndEvent(index);
|
||||
ProfilerCPU::EndEvent();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "JobSystem.h"
|
||||
#include "IRunnable.h"
|
||||
#include "Engine/Platform/CPUInfo.h"
|
||||
#include "Engine/Platform/Thread.h"
|
||||
#include "Engine/Platform/ConditionVariable.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#if USE_MONO
|
||||
#include "Engine/Scripting/ManagedCLR/MDomain.h"
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/threads.h>
|
||||
#endif
|
||||
|
||||
// Jobs storage perf info:
|
||||
// (500 jobs, i7 9th gen)
|
||||
// JOB_SYSTEM_USE_MUTEX=1, enqueue=130-280 cycles, dequeue=2-6 cycles
|
||||
// JOB_SYSTEM_USE_MUTEX=0, enqueue=300-700 cycles, dequeue=10-16 cycles
|
||||
// So using RingBuffer+Mutex+Signals is better than moodycamel::ConcurrentQueue
|
||||
|
||||
#define JOB_SYSTEM_ENABLED 1
|
||||
#define JOB_SYSTEM_USE_MUTEX 1
|
||||
#define JOB_SYSTEM_USE_STATS 0
|
||||
|
||||
#if JOB_SYSTEM_USE_STATS
|
||||
#include "Engine/Core/Log.h"
|
||||
#endif
|
||||
#if JOB_SYSTEM_USE_MUTEX
|
||||
#include "Engine/Core/Collections/RingBuffer.h"
|
||||
#else
|
||||
#include "ConcurrentQueue.h"
|
||||
#endif
|
||||
|
||||
#if JOB_SYSTEM_ENABLED
|
||||
|
||||
class JobSystemService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
JobSystemService()
|
||||
: EngineService(TEXT("JobSystem"), -800)
|
||||
{
|
||||
}
|
||||
|
||||
bool Init() override;
|
||||
void BeforeExit() override;
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
struct JobData
|
||||
{
|
||||
Function<void(int32)> Job;
|
||||
int32 Index;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<JobData>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
class JobSystemThread : public IRunnable
|
||||
{
|
||||
public:
|
||||
uint64 Index;
|
||||
|
||||
public:
|
||||
|
||||
// [IRunnable]
|
||||
String ToString() const override
|
||||
{
|
||||
return TEXT("JobSystemThread");
|
||||
}
|
||||
|
||||
int32 Run() override;
|
||||
|
||||
void AfterWork(bool wasKilled) override
|
||||
{
|
||||
Delete(this);
|
||||
}
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
JobSystemService JobSystemInstance;
|
||||
Thread* Threads[32] = {};
|
||||
int32 ThreadsCount = 0;
|
||||
volatile int64 ExitFlag = 0;
|
||||
volatile int64 DoneLabel = 0;
|
||||
volatile int64 NextLabel = 0;
|
||||
ConditionVariable JobsSignal;
|
||||
ConditionVariable WaitSignal;
|
||||
#if JOB_SYSTEM_USE_MUTEX
|
||||
CriticalSection JobsLocker;
|
||||
RingBuffer<JobData, InlinedAllocation<256>> Jobs;
|
||||
#else
|
||||
ConcurrentQueue<JobData> Jobs;
|
||||
#endif
|
||||
#if JOB_SYSTEM_USE_STATS
|
||||
int64 DequeueCount = 0;
|
||||
int64 DequeueSum = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool JobSystemService::Init()
|
||||
{
|
||||
ThreadsCount = Math::Min<int32>(Platform::GetCPUInfo().LogicalProcessorCount, ARRAY_COUNT(Threads));
|
||||
for (int32 i = 0; i < ThreadsCount; i++)
|
||||
{
|
||||
auto runnable = New<JobSystemThread>();
|
||||
runnable->Index = (uint64)i;
|
||||
auto thread = Thread::Create(runnable, String::Format(TEXT("Job System {0}"), i), ThreadPriority::AboveNormal);
|
||||
if (thread == nullptr)
|
||||
return true;
|
||||
Threads[i] = thread;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void JobSystemService::BeforeExit()
|
||||
{
|
||||
Platform::AtomicStore(&ExitFlag, 1);
|
||||
JobsSignal.NotifyAll();
|
||||
}
|
||||
|
||||
void JobSystemService::Dispose()
|
||||
{
|
||||
Platform::AtomicStore(&ExitFlag, 1);
|
||||
JobsSignal.NotifyAll();
|
||||
Platform::Sleep(1);
|
||||
|
||||
for (int32 i = 0; i < ThreadsCount; i++)
|
||||
{
|
||||
if (Threads[i] && Threads[i]->IsRunning())
|
||||
Threads[i]->Kill(true);
|
||||
Threads[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int32 JobSystemThread::Run()
|
||||
{
|
||||
Platform::SetThreadAffinityMask(1ull << Index);
|
||||
|
||||
JobData data;
|
||||
CriticalSection mutex;
|
||||
bool attachMonoThread = true;
|
||||
#if !JOB_SYSTEM_USE_MUTEX
|
||||
moodycamel::ConsumerToken consumerToken(Jobs);
|
||||
#endif
|
||||
while (Platform::AtomicRead(&ExitFlag) == 0)
|
||||
{
|
||||
// Try to get a job
|
||||
#if JOB_SYSTEM_USE_STATS
|
||||
const auto start = Platform::GetTimeCycles();
|
||||
#endif
|
||||
#if JOB_SYSTEM_USE_MUTEX
|
||||
JobsLocker.Lock();
|
||||
if (Jobs.Count() != 0)
|
||||
{
|
||||
data = Jobs.PeekFront();
|
||||
Jobs.PopFront();
|
||||
}
|
||||
JobsLocker.Unlock();
|
||||
#else
|
||||
if (!Jobs.try_dequeue(consumerToken, data))
|
||||
data.Job.Unbind();
|
||||
#endif
|
||||
#if JOB_SYSTEM_USE_STATS
|
||||
Platform::InterlockedIncrement(&DequeueCount);
|
||||
Platform::InterlockedAdd(&DequeueSum, Platform::GetTimeCycles() - start);
|
||||
#endif
|
||||
|
||||
if (data.Job.IsBinded())
|
||||
{
|
||||
#if USE_MONO
|
||||
// Ensure to have C# thread attached to this thead (late init due to MCore being initialized after Job System)
|
||||
if (attachMonoThread && !mono_domain_get())
|
||||
{
|
||||
const auto domain = MCore::Instance()->GetActiveDomain();
|
||||
mono_thread_attach(domain->GetNative());
|
||||
attachMonoThread = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Run job
|
||||
data.Job(data.Index);
|
||||
|
||||
// Move forward with the job queue
|
||||
Platform::InterlockedIncrement(&DoneLabel);
|
||||
WaitSignal.NotifyAll();
|
||||
|
||||
data.Job.Unbind();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wait for signal
|
||||
mutex.Lock();
|
||||
JobsSignal.Wait(mutex);
|
||||
mutex.Unlock();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int64 JobSystem::Dispatch(const Function<void(int32)>& job, int32 jobCount)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
if (jobCount <= 0)
|
||||
return 0;
|
||||
#if JOB_SYSTEM_ENABLED
|
||||
#if JOB_SYSTEM_USE_STATS
|
||||
const auto start = Platform::GetTimeCycles();
|
||||
#endif
|
||||
|
||||
JobData data;
|
||||
data.Job = job;
|
||||
|
||||
#if JOB_SYSTEM_USE_MUTEX
|
||||
JobsLocker.Lock();
|
||||
for (data.Index = 0; data.Index < jobCount; data.Index++)
|
||||
Jobs.PushBack(data);
|
||||
JobsLocker.Unlock();
|
||||
#else
|
||||
for (data.Index = 0; data.Index < jobCount; data.Index++)
|
||||
Jobs.enqueue(data);
|
||||
#endif
|
||||
const auto label = Platform::InterlockedAdd(&NextLabel, (int64)jobCount) + jobCount;
|
||||
|
||||
#if JOB_SYSTEM_USE_STATS
|
||||
LOG(Info, "Job enqueue time: {0} cycles", (int64)(Platform::GetTimeCycles() - start));
|
||||
#endif
|
||||
|
||||
if (jobCount == 1)
|
||||
JobsSignal.NotifyOne();
|
||||
else
|
||||
JobsSignal.NotifyAll();
|
||||
|
||||
return label;
|
||||
#else
|
||||
for (int32 i = 0; i < jobCount; i++)
|
||||
job(i);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void JobSystem::Wait()
|
||||
{
|
||||
#if JOB_SYSTEM_ENABLED
|
||||
Wait(Platform::AtomicRead(&NextLabel));
|
||||
#endif
|
||||
}
|
||||
|
||||
void JobSystem::Wait(int64 label)
|
||||
{
|
||||
#if JOB_SYSTEM_ENABLED
|
||||
PROFILE_CPU();
|
||||
|
||||
// Early out
|
||||
if (label <= Platform::AtomicRead(&DoneLabel))
|
||||
return;
|
||||
|
||||
// Wait on signal until input label is not yet done
|
||||
CriticalSection mutex;
|
||||
do
|
||||
{
|
||||
mutex.Lock();
|
||||
WaitSignal.Wait(mutex, 1);
|
||||
mutex.Unlock();
|
||||
} while (label > Platform::AtomicRead(&DoneLabel) && Platform::AtomicRead(&ExitFlag) == 0);
|
||||
|
||||
#if JOB_SYSTEM_USE_STATS
|
||||
LOG(Info, "Job average dequeue time: {0} cycles", DequeueSum / DequeueCount);
|
||||
DequeueSum = DequeueCount = 0;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Delegate.h"
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight multi-threaded jobs execution scheduler. Uses a pool of threads and supports work-stealing concept.
|
||||
/// </summary>
|
||||
API_CLASS(Static) class FLAXENGINE_API JobSystem
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(JobSystem);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches the job for the execution.
|
||||
/// </summary>
|
||||
/// <param name="job">The job. Argument is an index of the job execution.</param>
|
||||
/// <param name="jobCount">The job executions count.</param>
|
||||
/// <returns>The label identifying this dispatch. Can be used to wait for the execution end.</returns>
|
||||
API_FUNCTION() static int64 Dispatch(const Function<void(int32)>& job, int32 jobCount = 1);
|
||||
|
||||
/// <summary>
|
||||
/// Waits for all dispatched jobs to finish.
|
||||
/// </summary>
|
||||
API_FUNCTION() static void Wait();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for all dispatched jobs until a given label to finish (i.e. waits for a Dispatch that returned that label).
|
||||
/// </summary>
|
||||
/// <param name="label">The label.</param>
|
||||
API_FUNCTION() static void Wait(int64 label);
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "TaskGraph.h"
|
||||
#include "JobSystem.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool SortTaskGraphSystem(TaskGraphSystem* const& a, TaskGraphSystem* const& b)
|
||||
{
|
||||
return b->Order < a->Order;
|
||||
};
|
||||
}
|
||||
|
||||
TaskGraphSystem::TaskGraphSystem(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
{
|
||||
}
|
||||
|
||||
void TaskGraphSystem::AddDependency(TaskGraphSystem* system)
|
||||
{
|
||||
_dependencies.Add(system);
|
||||
}
|
||||
|
||||
void TaskGraphSystem::PreExecute(TaskGraph* graph)
|
||||
{
|
||||
}
|
||||
|
||||
void TaskGraphSystem::Execute(TaskGraph* graph)
|
||||
{
|
||||
}
|
||||
|
||||
void TaskGraphSystem::PostExecute(TaskGraph* graph)
|
||||
{
|
||||
}
|
||||
|
||||
TaskGraph::TaskGraph(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
{
|
||||
}
|
||||
|
||||
const Array<TaskGraphSystem*, InlinedAllocation<64>>& TaskGraph::GetSystems() const
|
||||
{
|
||||
return _systems;
|
||||
}
|
||||
|
||||
void TaskGraph::AddSystem(TaskGraphSystem* system)
|
||||
{
|
||||
_systems.Add(system);
|
||||
}
|
||||
|
||||
void TaskGraph::RemoveSystem(TaskGraphSystem* system)
|
||||
{
|
||||
_systems.Remove(system);
|
||||
}
|
||||
|
||||
void TaskGraph::Execute()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
for (auto system : _systems)
|
||||
system->PreExecute(this);
|
||||
|
||||
_queue.Clear();
|
||||
_remaining.Clear();
|
||||
_remaining.Add(_systems);
|
||||
|
||||
while (_remaining.HasItems())
|
||||
{
|
||||
// Find systems without dependencies or with already executed dependencies
|
||||
for (int32 i = _remaining.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
auto e = _remaining[i];
|
||||
bool hasReadyDependencies = true;
|
||||
for (auto d : e->_dependencies)
|
||||
{
|
||||
if (_remaining.Contains(d))
|
||||
{
|
||||
hasReadyDependencies = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasReadyDependencies)
|
||||
{
|
||||
_queue.Add(e);
|
||||
_remaining.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// End if no systems left
|
||||
if (_queue.IsEmpty())
|
||||
break;
|
||||
|
||||
// Execute in order
|
||||
Sorting::QuickSort(_queue.Get(), _queue.Count(), &SortTaskGraphSystem);
|
||||
_currentLabel = 0;
|
||||
for (int32 i = 0; i < _queue.Count(); i++)
|
||||
{
|
||||
_currentSystem = _queue[i];
|
||||
_currentSystem->Execute(this);
|
||||
}
|
||||
_currentSystem = nullptr;
|
||||
_queue.Clear();
|
||||
|
||||
// Wait for async jobs to finish
|
||||
JobSystem::Wait(_currentLabel);
|
||||
}
|
||||
|
||||
for (auto system : _systems)
|
||||
system->PostExecute(this);
|
||||
}
|
||||
|
||||
void TaskGraph::DispatchJob(const Function<void(int32)>& job, int32 jobCount)
|
||||
{
|
||||
ASSERT(_currentSystem);
|
||||
_currentLabel = JobSystem::Dispatch(job, jobCount);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
class TaskGraph;
|
||||
|
||||
/// <summary>
|
||||
/// System that can generate work into Task Graph for asynchronous execution.
|
||||
/// </summary>
|
||||
API_CLASS(Abstract) class FLAXENGINE_API TaskGraphSystem : public PersistentScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(TaskGraphSystem);
|
||||
friend TaskGraph;
|
||||
private:
|
||||
Array<TaskGraphSystem*, InlinedAllocation<16>> _dependencies;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The execution order of the system (systems with higher order are executed earlier).
|
||||
/// </summary>
|
||||
API_FIELD() int32 Order = 0;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Adds the dependency on the system execution. Before this system can be executed the given dependant system has to be executed first.
|
||||
/// </summary>
|
||||
/// <param name="system">The system to depend on.</param>
|
||||
API_FUNCTION() void AddDependency(TaskGraphSystem* system);
|
||||
|
||||
/// <summary>
|
||||
/// Called before executing any systems of the graph. Can be used to initialize data (synchronous).
|
||||
/// </summary>
|
||||
/// <param name="graph">The graph executing the system.</param>
|
||||
API_FUNCTION() virtual void PreExecute(TaskGraph* graph);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the system logic and schedules the asynchronous work.
|
||||
/// </summary>
|
||||
/// <param name="graph">The graph executing the system.</param>
|
||||
API_FUNCTION() virtual void Execute(TaskGraph* graph);
|
||||
|
||||
/// <summary>
|
||||
/// Called after executing all systems of the graph. Can be used to cleanup data (synchronous).
|
||||
/// </summary>
|
||||
/// <param name="graph">The graph executing the system.</param>
|
||||
API_FUNCTION() virtual void PostExecute(TaskGraph* graph);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Graph-based asynchronous tasks scheduler for high-performance computing and processing.
|
||||
/// </summary>
|
||||
API_CLASS() class FLAXENGINE_API TaskGraph : public PersistentScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(TaskGraph);
|
||||
private:
|
||||
Array<TaskGraphSystem*, InlinedAllocation<64>> _systems;
|
||||
Array<TaskGraphSystem*, InlinedAllocation<64>> _remaining;
|
||||
Array<TaskGraphSystem*, InlinedAllocation<64>> _queue;
|
||||
TaskGraphSystem* _currentSystem = nullptr;
|
||||
int64 _currentLabel = 0;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the list of systems.
|
||||
/// </summary>
|
||||
API_PROPERTY() const Array<TaskGraphSystem*, InlinedAllocation<64>>& GetSystems() const;
|
||||
|
||||
/// <summary>
|
||||
/// Adds the system to the graph for the execution.
|
||||
/// </summary>
|
||||
/// <param name="system">The system to add.</param>
|
||||
API_FUNCTION() void AddSystem(TaskGraphSystem* system);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the system from the graph.
|
||||
/// </summary>
|
||||
/// <param name="system">The system to add.</param>
|
||||
API_FUNCTION() void RemoveSystem(TaskGraphSystem* system);
|
||||
|
||||
/// <summary>
|
||||
/// Schedules the asynchronous systems execution including ordering and dependencies handling.
|
||||
/// </summary>
|
||||
API_FUNCTION() void Execute();
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches the job for the execution.
|
||||
/// </summary>
|
||||
/// <remarks>Call only from system's Execute method to properly schedule job.</remarks>
|
||||
/// <param name="job">The job. Argument is an index of the job execution.</param>
|
||||
/// <param name="jobCount">The job executions count.</param>
|
||||
API_FUNCTION() void DispatchJob(const Function<void(int32)>& job, int32 jobCount = 1);
|
||||
};
|
||||
@@ -69,7 +69,8 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
void GetValues(Array<T>& result) const
|
||||
template<typename AllocationType = HeapAllocation>
|
||||
void GetValues(Array<T, AllocationType>& result) const
|
||||
{
|
||||
result.EnsureCapacity(MaxThreads);
|
||||
for (int32 i = 0; i < MaxThreads; i++)
|
||||
@@ -134,7 +135,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void GetNotNullValues(Array<T*>& result) const
|
||||
template<typename AllocationType = HeapAllocation>
|
||||
void GetNotNullValues(Array<T*, AllocationType>& result) const
|
||||
{
|
||||
result.EnsureCapacity(MaxThreads);
|
||||
for (int32 i = 0; i < MaxThreads; i++)
|
||||
|
||||
@@ -18,13 +18,6 @@ class VisjectGraphNode;
|
||||
|
||||
class VisjectGraphBox : public GraphBox
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The cached value.
|
||||
/// </summary>
|
||||
Variant Cache;
|
||||
|
||||
public:
|
||||
|
||||
VisjectGraphBox()
|
||||
|
||||
@@ -13,5 +13,5 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("b8442186-4a70-7c85-704a-857c262d00f6")]
|
||||
[assembly: AssemblyVersion("1.1.6219")]
|
||||
[assembly: AssemblyFileVersion("1.1.6219")]
|
||||
[assembly: AssemblyVersion("1.1.6220")]
|
||||
[assembly: AssemblyFileVersion("1.1.6220")]
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
#pragma once
|
||||
|
||||
#define FLAXENGINE_NAME "FlaxEngine"
|
||||
#define FLAXENGINE_VERSION Version(1, 1, 6219)
|
||||
#define FLAXENGINE_VERSION_TEXT "1.1.6219"
|
||||
#define FLAXENGINE_VERSION Version(1, 1, 6220)
|
||||
#define FLAXENGINE_VERSION_TEXT "1.1.6220"
|
||||
#define FLAXENGINE_VERSION_MAJOR 1
|
||||
#define FLAXENGINE_VERSION_MINOR 1
|
||||
#define FLAXENGINE_VERSION_BUILD 6219
|
||||
#define FLAXENGINE_VERSION_BUILD 6220
|
||||
#define FLAXENGINE_COMPANY "Flax"
|
||||
#define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved."
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Flax.Build
|
||||
{
|
||||
@@ -71,6 +72,8 @@ namespace Flax.Build
|
||||
|
||||
private static int _depth;
|
||||
private static readonly List<Event> _events = new List<Event>(1024);
|
||||
private static readonly DateTime _startTime = DateTime.Now;
|
||||
private static readonly Stopwatch _stopwatch = Stopwatch.StartNew(); // https://stackoverflow.com/questions/1416139/how-to-get-timestamp-of-tick-precision-in-net-c
|
||||
|
||||
/// <summary>
|
||||
/// Begins the profiling event.
|
||||
@@ -81,7 +84,7 @@ namespace Flax.Build
|
||||
{
|
||||
Event e;
|
||||
e.Name = name;
|
||||
e.StartTime = DateTime.Now;
|
||||
e.StartTime = _startTime.AddTicks(_stopwatch.Elapsed.Ticks);
|
||||
e.Duration = TimeSpan.Zero;
|
||||
e.Depth = _depth++;
|
||||
e.ThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
@@ -95,7 +98,7 @@ namespace Flax.Build
|
||||
/// <param name="id">The event identifier returned by <see cref="Begin"/>.</param>
|
||||
public static void End(int id)
|
||||
{
|
||||
var endTime = DateTime.Now;
|
||||
var endTime = _startTime.AddTicks(_stopwatch.Elapsed.Ticks);
|
||||
var e = _events[id];
|
||||
e.Duration = endTime - e.StartTime;
|
||||
_events[id] = e;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
@@ -25,7 +26,7 @@ namespace Flax.Build
|
||||
}
|
||||
|
||||
Mutex singleInstanceMutex = null;
|
||||
var startTime = DateTime.Now;
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
bool failed = false;
|
||||
|
||||
try
|
||||
@@ -166,9 +167,8 @@ namespace Flax.Build
|
||||
singleInstanceMutex.Dispose();
|
||||
singleInstanceMutex = null;
|
||||
}
|
||||
|
||||
var endTime = DateTime.Now;
|
||||
Log.Info(string.Format("Total time: {0}", endTime - startTime));
|
||||
stopwatch.Stop();
|
||||
Log.Info(string.Format("Total time: {0}", stopwatch.Elapsed));
|
||||
Log.Verbose("End.");
|
||||
Log.Dispose();
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ namespace Flax.Build
|
||||
}
|
||||
}
|
||||
|
||||
var startTime = DateTime.UtcNow;
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
if (!options.HasFlag(RunOptions.NoLoggingOfRunCommand))
|
||||
{
|
||||
Log.Verbose("Running: " + app + " " + (string.IsNullOrEmpty(commandLine) ? "" : commandLine));
|
||||
@@ -397,11 +397,11 @@ namespace Flax.Build
|
||||
|
||||
if (!options.HasFlag(RunOptions.NoWaitForExit))
|
||||
{
|
||||
var buildDuration = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||||
stopwatch.Stop();
|
||||
result = proc.ExitCode;
|
||||
if (!options.HasFlag(RunOptions.NoLoggingOfRunCommand) || options.HasFlag(RunOptions.NoLoggingOfRunDuration))
|
||||
{
|
||||
Log.Info(string.Format("Took {0}s to run {1}, ExitCode={2}", buildDuration / 1000, Path.GetFileName(app), result));
|
||||
Log.Info(string.Format("Took {0}s to run {1}, ExitCode={2}", stopwatch.Elapsed.TotalSeconds, Path.GetFileName(app), result));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user