void UHTNPlannerComponent::ProcessExecutionRequest()
{
	bRequestedExecutionUpdate = false;
	if(!IsRegistered())
	{
		// it shouldn't be called, component is no longer valid
		return;
	}

	if(bIsPaused)
	{
		UE_VLOG(GetOwner(), LogHTNPlanner, Verbose, TEXT("Ignoring ProcessExecutionRequest call due to HTNPlannerComponent still being paused"));
		return;
	}

	//GEngine->AddOnScreenDebugMessage(-1, 1.5f, FColor::Yellow, TEXT("UHTNPlannerComponent::ProcessExecutionRequest()"));

	if(PendingExecution.IsValid())
	{
		ProcessPendingExecution();
		return;
	}

	if(NumStackElements == 0)
	{
		//BestPlan = nullptr;

		if(CurrentPlannerAsset->bLoop)
		{
			// finished execution of plan and we want to loop, so re-start
			RestartPlanner();
		}
		else
		{
			bIsRunning = false;
		}

		return;
	}

#if HTN_LOG_RUNTIME_STATS
	if(StartPlanningTime == FDateTime::MinValue())
	{
		StartPlanningTime = FDateTime::UtcNow();
	}
#endif // HTN_LOG_RUNTIME_STATS

	FDateTime PlanningStart = FDateTime::UtcNow();
	while(NumStackElements > 0)
	{
		// our stack is not empty
		FTimespan TimePlanned = FDateTime::UtcNow() - PlanningStart;
		if(TimePlanned.GetTotalSeconds() >= CurrentPlannerAsset->MaxSearchTime)
		{
			// we've spent our maximum allowed search time for this tick on planning, so need to continue some other time
			ScheduleExecutionUpdate();

#if HTN_LOG_RUNTIME_STATS
			CumulativeSearchTimeMs += TimePlanned.GetTotalMilliseconds();
			++CumulativeFrameCount;
#endif
			return;
		}

#if HTN_LOG_RUNTIME_STATS
		++NumNodesExpanded;
#endif

		if(PreviousPlan.IsValid())
		{
			UE_LOG(LogHTNPlanner, Warning, TEXT("%d nodes in data structure(s)"), NumStackElements);
		}

		if(bProbabilisticPlanReuse && bHitLeaf)
		{
			// we've hit a leaf node, so it's time to re-evaluate whether we're ignoring plan reuse probabilistically
			bHitLeaf = false;

			if(NumStackElements == PlanningStack.Num())
			{
				bIgnoringPlanReuse = false;		// not ignoring plan reuse if everything's still in the non-prioritized stack
			}
			else
			{
				bIgnoringPlanReuse = (FMath::FRand() <= ProbabilityIgnorePlanReuse);
			}
		}

		const FHTNStackElement StackTop = PopStackElement();
		
		if(StackTop.Cost + Cast<UTaskNetwork>(StackTop.TaskNetwork->Task)->
		   GetHeuristicCost(StackTop.WorldState, StackTop.TaskNetwork->GetMemory()) >= BestCost)
		{
			if(!bDepthFirstSearch)
			{
				// everything remaining in the heap will be at least as bad, and maybe worse
				PlanningStack.Empty();
				NumStackElements = 0;
			}

			if(PreviousPlan.IsValid())	// we're doing plan reuse
			{
				// verify that all of our values of maximum streak lengths among unprocessed nodes are still correct
				UpdateMaxStreakLengths();
			}

			continue;	// we won't find any improvements down this path
		}

		UTaskNetwork* TopNetwork = Cast<UTaskNetwork>(StackTop.TaskNetwork->Task);
		if(TopNetwork->IsEmpty(StackTop.TaskNetwork->GetMemory()))
		{
			// we've found a path leading to a legal, complete Plan
#if HTN_LOG_RUNTIME_STATS
			CumulativeSearchTimeMsTillLastImprovement = CumulativeSearchTimeMs + (FDateTime::UtcNow() - PlanningStart).GetTotalMilliseconds();

			if(BestCost == TNumericLimits<float>::Max())	// this means that this is the first time we find a valid plan
			{
				CumulativeSearchTimeMsTillFirstPlan = CumulativeSearchTimeMsTillLastImprovement;
				FirstPlanCost = StackTop.Cost;
			}
#endif
			BestPlan = StackTop.Plan;
			BestPlan->SetComplete(true);
			BestCost = StackTop.Cost;

#if HTN_LOG_RUNTIME_STATS
			DataCollector->FoundSolution(BestCost, StackTop.Plan->GetPlanSize(), StackTop.Plan->GetSearchHistory().Num(),
										 CumulativeSearchTimeMsTillLastImprovement, NumNodesExpanded);
#endif

			if(CurrentPlannerAsset->bIgnoreTaskCosts)
			{
				// the HTN Planner doesn't care about finding optimal plans, only about finding the first one
				PlanningStack.Empty();
				NumStackElements = 0;
			}
			else if(!bDepthFirstSearch)
			{
				// best-first search finds an optimal solution as first solution
				PlanningStack.Empty();
				NumStackElements = 0;
			}

			if(PreviousPlan.IsValid())	// we're doing plan reuse
			{
				if(bProbabilisticPlanReuse)
				{
					bHitLeaf = true;
				}

				// verify that all of our values of maximum streak lengths among unprocessed nodes are still correct
				UpdateMaxStreakLengths();
			}

			continue;
		}

		// find all tasks that share the highest priority amongst the tasks in the task network
		TArray<TSharedPtr<FHTNTaskInstance>> TaskInstances = TopNetwork->FindTasksWithoutPredecessors(StackTop.TaskNetwork->GetMemory());
		for(const TSharedPtr<FHTNTaskInstance>& TaskInstance : TaskInstances)
		{
			UHTNTask* Task = TaskInstance->Task;
			if(UPrimitiveTask* PrimitiveTask = Cast<UPrimitiveTask>(Task))
			{				
				if(PrimitiveTask->IsApplicable(StackTop.WorldState, TaskInstance->GetMemory()))
				{
					// prepare a new element for the stack where we'll have applied this primitive task
					FHTNStackElement NewStackElement;
					NewStackElement.Cost = 
						FMath::Max(0.f, StackTop.Cost) + PrimitiveTask->GetCost(StackTop.WorldState, TaskInstance->GetMemory());

					TSharedPtr<FHTNPlan> Plan = StackTop.Plan->Copy();
					Plan->AppendTaskInstance(TaskInstance);
					Plan->AppendSearchHistory(TaskInstance);
					TSharedPtr<FHTNTaskInstance> TaskNetwork = TopNetwork->Copy(StackTop.TaskNetwork);
					Cast<UTaskNetwork>(TaskNetwork->Task)->Remove(TaskInstance, TaskNetwork->GetMemory());
					TSharedPtr<FHTNWorldState> WorldState = StackTop.WorldState->Copy();
					PrimitiveTask->ApplyTo(WorldState, TaskInstance->GetMemory());

					NewStackElement.Plan = Plan;
					NewStackElement.TaskNetwork = TaskNetwork;
					NewStackElement.WorldState = WorldState;

					if(PreviousPlan.IsValid())
					{
						// we're doing plan reuse
						CurrentMatchingStreakLength = Plan->GetMatchingStreak(PreviousPlan, MinMatchingStreakLength);
					}

					AddStackElement(NewStackElement);	// TO DO maybe should explicitly Move the NewStackElement?
				}
				else if(PreviousPlan.IsValid())
				{
					if(bProbabilisticPlanReuse)
					{
						bHitLeaf = true;
					}
				}
			}
			else if(UCompoundTask* CompoundTask = Cast<UCompoundTask>(Task))
			{
				TArray<TSharedPtr<FHTNTaskInstance>> Decompositions = CompoundTask->FindDecompositions(*this, StackTop.WorldState, 
																									   TaskInstance->GetMemory());

				// regardless of which decomposition we pick, effect on plan will be the same
				TSharedPtr<FHTNPlan> Plan = StackTop.Plan->Copy();
				Plan->AppendSearchHistory(TaskInstance);

				if(PreviousPlan.IsValid())
				{
					// we're doing plan reuse

					if(Decompositions.Num() == 0)	// leaf node
					{
						if(bProbabilisticPlanReuse)
						{
							bHitLeaf = true;
						}
					}

					CurrentMatchingStreakLength = Plan->GetMatchingStreak(PreviousPlan, MinMatchingStreakLength);

					TArray<FHTNStackElement> NewStackElements;
					TArray<FHTNStackElement> NewFifoElements;

					for(int32 Idx = 0; Idx < Decompositions.Num(); ++Idx)
					{
						const TSharedPtr<FHTNTaskInstance>& Decomposition = Decompositions[Idx];

						// prepare a new element where we'll have decomposed this compound task
						FHTNStackElement NewStackElement;
						NewStackElement.WorldState = StackTop.WorldState;
						NewStackElement.Cost = StackTop.Cost;

						TSharedPtr<FHTNTaskInstance> TaskNetwork = TopNetwork->Copy(StackTop.TaskNetwork);
						Cast<UTaskNetwork>(TaskNetwork->Task)->Replace(TaskInstance, Decomposition, TaskNetwork->GetMemory());

						NewStackElement.Plan = Plan->Copy();
						NewStackElement.TaskNetwork = TaskNetwork;

						if(bProbabilisticPlanReuse && bIgnoringPlanReuse)
						{
							// probabilistically ignoring plan reuse, so no need to waste time computing streak lengths
							NewStackElements.Push(NewStackElement);
						}
						else
						{
							if(MaxCurrentMatchingStreakLength > 0 && CurrentMatchingStreakLength == 0)
							{
								// this element belongs in a FIFO queue
								NewFifoElements.Push(NewStackElement);
							}
							else
							{
								// this element belongs in a stack
								NewStackElements.Push(NewStackElement);
							}
						}
					}

					// first we'll add all the elements that belong in FIFO queues to their FIFO queues (in given order)
					for(int32 Idx = 0; Idx < NewFifoElements.Num(); ++Idx)
					{
						AddStackElement(NewFifoElements[Idx]);
					}

					// now we'll add all the elements that belong in some stack to those stacks (reverse order)
					while(NewStackElements.Num() > 0)
					{
						AddStackElement(NewStackElements.Pop());
					}
				}
				else
				{
					// we're not doing plan reuse
					// looping through Decompositions in reverse order so that they'll be popped off stack in correct order again
					for(int32 Idx = Decompositions.Num() - 1; Idx >= 0; --Idx)
					{
						const TSharedPtr<FHTNTaskInstance>& Decomposition = Decompositions[Idx];

						// prepare a new element for the stack where we'll have decomposed this compound task
						FHTNStackElement NewStackElement;
						NewStackElement.WorldState = StackTop.WorldState;
						NewStackElement.Cost = StackTop.Cost;

						TSharedPtr<FHTNTaskInstance> TaskNetwork = TopNetwork->Copy(StackTop.TaskNetwork);
						Cast<UTaskNetwork>(TaskNetwork->Task)->Replace(TaskInstance, Decomposition, TaskNetwork->GetMemory());

						NewStackElement.Plan = Plan->Copy();
						NewStackElement.TaskNetwork = TaskNetwork;

						AddStackElement(NewStackElement);	// TO DO maybe should explicitly Move the NewStackElement?
					}
				}
			}
			else
			{
				UE_LOG(LogHTNPlanner, Error, TEXT("UHTNPlannerComponent::ProcessExecutionRequest() encountered a Task that was neither Primitive nor Compound!"))
			}
		}

		if(PreviousPlan.IsValid())	// we're doing plan reuse
		{
			// verify that all of our values of maximum streak lengths among unprocessed nodes are still correct
			UpdateMaxStreakLengths();
		}
	}

	if(BestPlan.IsValid())
	{
#if HTN_LOG_RUNTIME_STATS
		CumulativeSearchTimeMs += (FDateTime::UtcNow() - PlanningStart).GetTotalMilliseconds();
		++CumulativeFrameCount;

		CumulativeSearchTimespan = FDateTime::UtcNow() - StartPlanningTime;

		// print runtime stats
		//UE_LOG(LogHTNPlanner, Warning, TEXT("Cumulative Search Timespan = %.2f ms"), CumulativeSearchTimespan.GetTotalMilliseconds());
		UE_LOG(LogHTNPlanner, Warning, TEXT("Cumulative Search Time = %.2f ms"), CumulativeSearchTimeMs);
		UE_LOG(LogHTNPlanner, Warning, TEXT("Cumulative Search Time Till Last Improvement = %.2f ms"), CumulativeSearchTimeMsTillLastImprovement);
		UE_LOG(LogHTNPlanner, Warning, TEXT("Cumulative Search Time First Plan = %.2f ms"), CumulativeSearchTimeMsTillFirstPlan);
		//UE_LOG(LogHTNPlanner, Warning, TEXT("Cumulative Frame Count = %d frames"), CumulativeFrameCount);
		UE_LOG(LogHTNPlanner, Warning, TEXT("Num Nodes Expanded = %d"), NumNodesExpanded);
		UE_LOG(LogHTNPlanner, Warning, TEXT("Cost of first plan found = %.2f"), FirstPlanCost);

		if(PreviousPlan.IsValid())
		{
			UE_LOG(LogHTNPlanner, Warning, TEXT("Longest matching streak = %d"), 
				   BestPlan->GetLongestMatchingStreak(PreviousPlan, MinMatchingStreakLength));
		}

		DataCollector->OptimalityProven(CumulativeSearchTimeMs, NumNodesExpanded);
#endif

		// we have a complete plan, so we'll want to execute the next task in the plan
		UE_LOG(LogHTNPlanner, Warning, TEXT("Found Plan with size = %d, cost = %.2f, search history size = %d!"), 
			   BestPlan->GetPlanSize(), BestCost, BestPlan->GetSearchHistory().Num());
		if(CurrentPlannerAsset->bExecutePlan)
		{
			PendingExecution = BestPlan->GetTaskInstanceToExecute();
			ProcessPendingExecution();
		}
		else
		{
			bIsRunning = false;
		}
	}
}