// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. #include "AnimGraph.h" #include "Engine/Debug/DebugLog.h" #include "Engine/Scripting/InternalCalls.h" #include "Engine/Scripting/BinaryModule.h" #include "Engine/Scripting/ManagedCLR/MDomain.h" #include "Engine/Scripting/ManagedCLR/MMethod.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/MException.h" #include struct InternalInitData { MonoArray* Values; MonoObject* BaseModel; }; struct InternalContext { AnimGraph* Graph; AnimGraphExecutor* GraphExecutor; AnimGraph::Node* Node; uint32 NodeId; int32 BoxId; float DeltaTime; uint64 CurrentFrameIndex; MonoObject* BaseModel; MonoObject* Instance; }; struct InternalImpulse { int32 NodesCount; int32 Unused; Transform* Nodes; Vector3 RootMotionTranslation; Quaternion RootMotionRotation; float Position; float Length; }; static_assert(sizeof(InternalImpulse) == sizeof(AnimGraphImpulse), "Please update managed impulse type for Anim Graph to match the C++ backend data layout."); namespace AnimGraphInternal { bool HasConnection(InternalContext* context, int32 boxId) { const auto box = context->Node->TryGetBox(boxId); if (box == nullptr) DebugLog::ThrowArgumentOutOfRange("boxId"); return box->HasConnection(); } MonoObject* GetInputValue(InternalContext* context, int32 boxId) { const auto box = context->Node->TryGetBox(boxId); if (box == nullptr) DebugLog::ThrowArgumentOutOfRange("boxId"); if (!box->HasConnection()) DebugLog::ThrowArgument("boxId", "This box has no connection. Use HasConnection to check if can get input value."); Variant value = Variant::Null; context->GraphExecutor->GetInputValue(box, value); // Cast value to prevent implicit value conversion issues and handling this on C# side if (!(box->Type.Type == VariantType::Void && value.Type.Type == VariantType::Pointer)) value = Variant::Cast(value, box->Type); return MUtils::BoxVariant(value); } AnimGraphImpulse* GetOutputImpulseData(InternalContext* context) { const auto nodes = context->Node->GetNodes(context->GraphExecutor); context->GraphExecutor->InitNodes(nodes); return nodes; } } #if USE_EDITOR Delegate AnimGraphExecutor::DebugFlow; #endif void AnimGraphExecutor::initRuntime() { ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_HasConnection", &AnimGraphInternal::HasConnection); ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetInputValue", &AnimGraphInternal::GetInputValue); ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetOutputImpulseData", &AnimGraphInternal::GetOutputImpulseData); } void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value) { auto box = (AnimGraphBox*)boxBase; if (box->IsCacheValid()) { // Return cache value = box->Cache; return; } auto node = (AnimGraphNode*)nodeBase; auto& data = node->Data.Custom; value = Value::Null; // Skip invalid nodes if (data.Evaluate == nullptr) 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; // Peek managed object const auto obj = mono_gchandle_get_target(data.Handle); if (obj == nullptr) { LOG(Warning, "Custom node instance is null."); return; } // Evaluate node void* params[1]; params[0] = &context; MonoObject* exception = nullptr; MonoObject* result = data.Evaluate->Invoke(obj, params, &exception); if (exception) { MException ex(exception); ex.Log(LogType::Warning, TEXT("AnimGraph")); return; } // Extract result value = MUtils::UnboxVariant(result); box->Cache = value; } void AnimGraph::ClearCustomNode(Node* node) { // Clear data auto& data = node->Data.Custom; data.Evaluate = nullptr; if (data.Handle) { mono_gchandle_free(data.Handle); data.Handle = 0; } } bool AnimGraph::InitCustomNode(Node* node) { // Fetch the node logic controller type if (node->Values.Count() < 2 || node->Values[0].Type.Type != ValueType::String) { LOG(Warning, "Invalid custom node data values."); return false; } const StringView typeName(node->Values[0]); const MString typeNameStd = typeName.ToStringAnsi(); MClass* type = Scripting::FindClass(typeNameStd); if (type == nullptr) { LOG(Warning, "Invalid custom node type {0}.", typeName); return false; } // Get methods MMethod* load = type->GetMethod("Load", 1); MMethod* evaluate = type->GetMethod("Evaluate", 1); if (load == nullptr) { LOG(Warning, "Invalid custom node type {0}. Missng Load method.", typeName); return false; } if (evaluate == nullptr) { LOG(Warning, "Invalid custom node type {0}. Missng Evaluate method.", typeName); return false; } // Create node values managed array if (mono_domain_get() == nullptr) Scripting::GetScriptsDomain()->Dispatch(); const auto values = mono_array_new(mono_domain_get(), mono_get_object_class(), node->Values.Count()); for (int32 i = 0; i < node->Values.Count(); i++) { const auto v = MUtils::BoxVariant(node->Values[i]); mono_array_set(values, MonoObject*, i, v); } // Allocate managed node object (create GC handle to prevent destruction) const auto obj = type->CreateInstance(); const auto handleGC = mono_gchandle_new(obj, false); // Initialize node InternalInitData initData; initData.Values = values; initData.BaseModel = BaseModel.GetManagedInstance(); void* params[1]; params[0] = &initData; MonoObject* exception = nullptr; load->Invoke(obj, params, &exception); if (exception) { mono_gchandle_free(handleGC); MException ex(exception); ex.Log(LogType::Warning, TEXT("AnimGraph")); return false; } // Cache node data auto& data = node->Data.Custom; data.Evaluate = evaluate; data.Handle = handleGC; return false; } #if USE_EDITOR void AnimGraph::OnScriptsReloading() { // Clear all cached custom nodes for nodes from game assemblies (plugins may keep data because they are persistent) for (int32 i = 0; i < _customNodes.Count(); i++) { const auto evaluate = _customNodes[i]->Data.Custom.Evaluate; if (evaluate && Scripting::IsTypeFromGameScripts(evaluate->GetParentClass())) { ClearCustomNode(_customNodes[i]); } } } void AnimGraph::OnScriptsReloaded() { // Cache all custom nodes with no type setup for (int32 i = 0; i < _customNodes.Count(); i++) { if (_customNodes[i]->Data.Custom.Evaluate == nullptr) { InitCustomNode(_customNodes[i]); } } } #endif void AnimGraph::OnScriptsLoaded() { // Cache all custom nodes with no type setup for (int32 i = 0; i < _customNodes.Count(); i++) { if (_customNodes[i]->Data.Custom.Evaluate == nullptr) { InitCustomNode(_customNodes[i]); } } }