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; } } }
float UKismetMathLibrary::GetTotalMilliseconds( FTimespan A ) { return A.GetTotalMilliseconds(); }
void AUISurfaceActor::HandleVirtualTouchInput(FVector ActionHandPalmLocation, FVector ActionHandFingerLocation) { FVector ActorSpaceActionFingerLocation = this->GetTransform().InverseTransformPosition(ActionHandFingerLocation); FVector ActorSpaceActionPalmLocation = this->GetTransform().InverseTransformPosition(ActionHandPalmLocation); FVector ActorSpaceActionFingerPreviousLocation = this->GetTransform().InverseTransformPosition(ActionHandPreviousFingerLocation); FVector ActorSpaceActionPalmPreviousLocation = this->GetTransform().InverseTransformPosition(ActionHandPreviousPalmLocation); FVector ActorSpaceActionFingerLocationXY = ActorSpaceActionFingerLocation; ActorSpaceActionFingerLocationXY.Z = 0.0; FVector ActorSpacePointerFingerLocationXYWorld = this->GetTransform().TransformPosition(ActorSpaceActionFingerLocationXY); FVector PointerFingerPixelCoordinates = GetViewPixelCoordinatesFromActorLocation(ActorSpaceActionFingerLocationXY); if (ActorSpaceActionFingerLocation.Z <= 0) { // finger is across plane if (!PointerFingerAcrossPlane) { // finger was not previously across plane, so handle as mouse click and set across flag to true HandleMouseDownEventAtCoordinates(PointerFingerPixelCoordinates); DrawDebugSphere(GetWorld(), ActorSpacePointerFingerLocationXYWorld, 0.6, 12, FColor::Cyan, true, 0.1); PointerFingerAcrossPlane = true; } else { // was already across plane } // handle scrolling FVector CurrentPalmPixelCoordinates = GetViewPixelCoordinatesFromActorLocation(ActorSpaceActionPalmLocation); FVector PreviousPalmPixelCoordinates = GetViewPixelCoordinatesFromActorLocation(ActorSpaceActionPalmPreviousLocation); float NumPixelsMovedY = CurrentPalmPixelCoordinates.Y - PreviousPalmPixelCoordinates.Y; // if negative means palm is moving up so should scroll down if (abs(NumPixelsMovedY) >= ScrollNumPixelsThreshold) { float WheelTicksY = NumPixelsMovedY / PixelToWheelTickScalingFactor; HandleYScrollIncrementEvent(WheelTicksY); } // Handle left/right swipe // NOTE: was a simple left swipe to go Back before, but since there were too many false positives have more complicated gesture linking left and right swipes to go Back float NumPixelsMovedX = CurrentPalmPixelCoordinates.X - PreviousPalmPixelCoordinates.X; if (abs(NumPixelsMovedX) >= SwipeLengthPixelsThreshold) { FDateTime CurrentTime = FDateTime::Now(); if (NumPixelsMovedX < 0) { // is left swipe FTimespan TimeBetweenLeftSwipes = CurrentTime - LastLeftSwipeTime; if (TimeBetweenLeftSwipes.GetTotalMilliseconds() >= MinMillisecondsBetweenSwipes) { // check if there has been enough elapsed time since last swipe LastLeftSwipeTime = CurrentTime; } } else { // is right swipe FTimespan TimeBetweenRightSwipes = CurrentTime - LastRightSwipeTime; if (TimeBetweenRightSwipes.GetTotalMilliseconds() >= MinMillisecondsBetweenSwipes) { // check if there has been enough elapsed time since last swipe LastRightSwipeTime = CurrentTime; FTimespan TimeBetweenLeftAndRightSwipes = LastRightSwipeTime - LastLeftSwipeTime; if (TimeBetweenLeftAndRightSwipes.GetTotalMilliseconds() <= MaxMillisecondsLinkingSwipes) { // check right swipe is linked to the previous left swipe HandleBackEvent(); } } } } } else { // finger is not across plane if (PointerFingerAcrossPlane) { // set across flag to false if it was set to true PointerFingerAcrossPlane = false ; HandleMouseUpEventAtCoordinates(PointerFingerPixelCoordinates); } // check for hover state if (ActorSpaceActionFingerLocation.Z <= this->HoverDistance) { // check for hovering PointerFingerIsHovering = true; DrawDebugSphere(GetWorld(), ActorSpacePointerFingerLocationXYWorld, 0.5, 12, FColor::Magenta); HandleMouseoverEventPixelCoordinates(PointerFingerPixelCoordinates); } else { if (PointerFingerIsHovering) { PointerFingerIsHovering = false; } } } // Now that we are done with Leap processing let's set the previous hand locations to the current hand locations ActionHandPreviousPalmLocation = ActionHandPalmLocation; ActionHandPreviousFingerLocation = ActionHandFingerLocation; }