// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "Behavior.h" #include "BehaviorKnowledge.h" #include "BehaviorTreeNodes.h" #include "Engine/Engine/Engine.h" #include "Engine/Engine/Time.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/TaskGraph.h" class BehaviorSystem : public TaskGraphSystem { public: Array Behaviors; void Job(int32 index); void Execute(TaskGraph* graph) override; }; class BehaviorService : public EngineService { public: Array UpdateList; BehaviorService() : EngineService(TEXT("Behaviors"), 0) { } bool Init() override; void Dispose() override; }; BehaviorService BehaviorServiceInstance; TaskGraphSystem* Behavior::System = nullptr; void BehaviorSystem::Job(int32 index) { PROFILE_CPU_NAMED("Behavior.Job"); Behaviors[index]->UpdateAsync(); } void BehaviorSystem::Execute(TaskGraph* graph) { // Copy list of behaviors to update (in case one of them gets disabled during async jobs) if (BehaviorServiceInstance.UpdateList.Count() == 0) return; Behaviors.Clear(); Behaviors.Add(BehaviorServiceInstance.UpdateList); // Schedule work to update all behaviors in async Function job; job.Bind(this); graph->DispatchJob(job, Behaviors.Count()); } bool BehaviorService::Init() { Behavior::System = New(); Engine::UpdateGraph->AddSystem(Behavior::System); return false; } void BehaviorService::Dispose() { BehaviorServiceInstance.UpdateList.Resize(0); SAFE_DELETE(Behavior::System); } Behavior::Behavior(const SpawnParams& params) : Script(params) { _tickLateUpdate = 1; // TODO: run Behavior via Job System (use Engine::UpdateGraph) _knowledge.Behavior = this; Tree.Changed.Bind(this); } void Behavior::UpdateAsync() { if (_result != BehaviorUpdateResult::Running) return; const BehaviorTree* tree = Tree.Get(); if (!tree || !tree->Graph.Root) { _result = BehaviorUpdateResult::Failed; Finished(); return; } // Update timer _accumulatedTime += Time::Update.DeltaTime.GetTotalSeconds(); const float updateDeltaTime = 1.0f / Math::Max(tree->Graph.Root->UpdateFPS * UpdateRateScale, ZeroTolerance); if (_accumulatedTime < updateDeltaTime) return; _accumulatedTime -= updateDeltaTime; _totalTime += updateDeltaTime; // Update tree BehaviorUpdateContext context; context.Behavior = this; context.Knowledge = &_knowledge; context.Memory = _knowledge.Memory; context.RelevantNodes = &_knowledge.RelevantNodes; context.DeltaTime = updateDeltaTime; context.Time = _totalTime; const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context); if (result != BehaviorUpdateResult::Running) _result = result; if (_result != BehaviorUpdateResult::Running) { Finished(); } } void Behavior::StartLogic() { PROFILE_CPU(); // Ensure to have tree loaded on begin play CHECK(Tree && !Tree->WaitForLoaded()); BehaviorTree* tree = Tree.Get(); CHECK(tree->Graph.Root); _result = BehaviorUpdateResult::Running; // Init knowledge _knowledge.InitMemory(tree); } void Behavior::StopLogic(BehaviorUpdateResult result) { if (_result != BehaviorUpdateResult::Running || result == BehaviorUpdateResult::Running) return; PROFILE_CPU(); _accumulatedTime = 0.0f; _totalTime = 0; _result = result; } void Behavior::ResetLogic() { PROFILE_CPU(); const bool isActive = _result == BehaviorUpdateResult::Running; if (isActive) StopLogic(); // Reset state _knowledge.FreeMemory(); _accumulatedTime = 0.0f; _totalTime = 0; _result = BehaviorUpdateResult::Success; if (isActive) StartLogic(); } void Behavior::OnEnable() { BehaviorServiceInstance.UpdateList.Add(this); if (AutoStart) StartLogic(); } void Behavior::OnDisable() { BehaviorServiceInstance.UpdateList.Remove(this); } #if USE_EDITOR bool Behavior::GetNodeDebugRelevancy(const BehaviorTreeNode* node, const Behavior* behavior) { return node && behavior && node->_executionIndex != -1 && behavior->_knowledge.RelevantNodes.Get(node->_executionIndex); } String Behavior::GetNodeDebugInfo(const BehaviorTreeNode* node, Behavior* behavior) { if (!node) return String::Empty; BehaviorUpdateContext context; Platform::MemoryClear(&context, sizeof(context)); if (behavior && node->_executionIndex != -1 && behavior->_knowledge.RelevantNodes.Get(node->_executionIndex)) { // Pass behavior and knowledge data only for relevant nodes to properly access it context.Behavior = behavior; context.Knowledge = &behavior->_knowledge; context.Memory = behavior->_knowledge.Memory; context.RelevantNodes = &behavior->_knowledge.RelevantNodes; context.Time = behavior->_totalTime; } return node->GetDebugInfo(context); } #endif