Announcement

Collapse
No announcement yet.

Any way to catch a latent function being completed?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Any way to catch a latent function being completed?

    I have implemented a latent function to go together with my ledge climbing system. Since I am making it in C++, I would prefer to avoid using Blueprints for this particular part (since it will be part of core functionality), which is why I got myself a latent function from the KismetSystemLibrary. Now, the function is working properly except for one downside; I cannot seem to find a way to get it to fire something that I can use to determine whether the latent function has stopped or not. I prefer not to resort to using the Tick function to check every single tick.

    The code in question;
    Code:
    void UMyClass::MoveComponentTo(USceneComponent* Component, FVector TargetRelativeLocation, FRotator TargetRelativeRotation, bool bEaseOut, bool bEaseIn, float OverTime, TEnumAsByte<EMoveCompAction::Type> MoveAction, FLatentActionInfo LatentInfo)
    {
    	if (UWorld* World = ((Component != NULL) ? Component->GetWorld() : NULL))
    	{
    		FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
    		FInterpolateComponentToAction* Action = LatentActionManager.FindExistingAction<FInterpolateComponentToAction>(LatentInfo.CallbackTarget, LatentInfo.UUID);
    
    		const FVector ComponentLocation = (Component != NULL) ? Component->RelativeLocation : FVector::ZeroVector;
    		const FRotator ComponentRotation = (Component != NULL) ? Component->RelativeRotation : FRotator::ZeroRotator;
    
    		// If not currently running
    		if (Action == NULL)
    		{
    			if (MoveAction == EMoveCompAction::Move)
    			{
    				// Only act on a 'move' input if not running
    				Action = new FInterpolateComponentToAction(OverTime, LatentInfo, Component, bEaseOut, bEaseIn);
    
    				Action->TargetLocation = TargetRelativeLocation;
    				Action->TargetRotation = TargetRelativeRotation;
    
    				Action->InitialLocation = ComponentLocation;
    				Action->InitialRotation = ComponentRotation;
    
    				LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, Action);
    			}
    		}
    		else
    		{
    			if (MoveAction == EMoveCompAction::Move)
    			{
    				// A 'Move' action while moving restarts interpolation
    				Action->TotalTime = OverTime;
    				Action->TimeElapsed = 0.f;
    
    				Action->TargetLocation = TargetRelativeLocation;
    				Action->TargetRotation = TargetRelativeRotation;
    
    				Action->InitialLocation = ComponentLocation;
    				Action->InitialRotation = ComponentRotation;
    			}
    			else if (MoveAction == EMoveCompAction::Stop)
    			{
    				// 'Stop' just stops the interpolation where it is
    				Action->bInterpolating = false;
    			}
    			else if (MoveAction == EMoveCompAction::Return)
    			{
    				// Return moves back to the beginning
    				Action->TotalTime = Action->TimeElapsed;
    				Action->TimeElapsed = 0.f;
    
    				// Set our target to be our initial, and set the new initial to be the current position
    				Action->TargetLocation = Action->InitialLocation;
    				Action->TargetRotation = Action->InitialRotation;
    
    				Action->InitialLocation = ComponentLocation;
    				Action->InitialRotation = ComponentRotation;
    			}
    		}
    	}
    }
    The function that I believe it is calling when finishing a latent action;
    Code:
    void FLatentActionManager::TickLatentActionForObject(float DeltaTime, FActionList& ObjectActionList, UObject* InObject)
    {
    	typedef TPair<int32, FPendingLatentAction*> FActionListPair;
    	TArray<FActionListPair, TInlineAllocator<4>> ItemsToRemove;
    	
    	FLatentResponse Response(DeltaTime);
    	for (TMultiMap<int32, FPendingLatentAction*>::TConstIterator It(ObjectActionList); It; ++It)
    	{
    		FPendingLatentAction* Action = It.Value();
    
    		Response.bRemoveAction = false;
    
    		Action->UpdateOperation(Response);
    
    		if (Response.bRemoveAction)
    		{
    			new (ItemsToRemove) FActionListPair(TPairInitializer<int32, FPendingLatentAction*>(It.Key(), Action));
    		}
    	}
    
    	// Remove any items that were deleted
    	for (int32 i = 0; i < ItemsToRemove.Num(); ++i)
    	{
    		const FActionListPair& ItemPair = ItemsToRemove[i];
    		const int32 ItemIndex = ItemPair.Key;
    		FPendingLatentAction* DyingAction = ItemPair.Value;
    		ObjectActionList.Remove(ItemIndex, DyingAction);
    		delete DyingAction;
    	}
    
    	// Trigger any pending execution links
    	for (int32 i = 0; i < Response.LinksToExecute.Num(); ++i)
    	{
    		FLatentResponse::FExecutionInfo& LinkInfo = Response.LinksToExecute[i];
    		if (LinkInfo.LinkID != INDEX_NONE)
    		{
    			if (UObject* CallbackTarget = LinkInfo.CallbackTarget.Get())
    			{
    				check(CallbackTarget == InObject);
    
    				if (UFunction* ExecutionFunction = CallbackTarget->FindFunction(LinkInfo.ExecutionFunction))
    				{
    					CallbackTarget->ProcessEvent(ExecutionFunction, &(LinkInfo.LinkID));
    				}
    				else
    				{
    					UE_LOG(LogScript, Warning, TEXT("FLatentActionManager::ProcessLatentActions: Could not find latent action resume point named '%s' on '%s' called by '%s'"),
    						*LinkInfo.ExecutionFunction.ToString(), *(CallbackTarget->GetPathName()), *(InObject->GetPathName()));
    				}
    			}
    			else
    			{
    				UE_LOG(LogScript, Warning, TEXT("FLatentActionManager::ProcessLatentActions: CallbackTarget is None."));
    			}
    		}
    	}
    }
    Now, I got to the point of the log writing out the "could not find latent action resume point." I implemented a callback function simply known as Callback and even though the linkage should have been fine (CallbackTarget and InObject both returned the class that contained the Callback function), I still got that log message. I suspect this function has been made with Blueprint in mind (the whole pin thing), but I want to know a way through which it will call a c++ function instead.
    Last edited by cridia; 07-04-2015, 02:50 AM. Reason: Found the solution myself

    #2
    I don't know why it is that every time I ask a question here that I have been stuck on for a while, I tend to find the answer just after posting it.

    Anyway, the problem was not that I was passing the wrong parameters, the problem was that the functions in question needed to be "BlueprintCallable" in order for them to be usable for callback.

    If anyone is interested in how to implement latent functions in c++, with callback to see when things have ended, let me know.

    Comment


      #3
      I am interested in making a latent function if you can post how do that that would be great!!

      Comment


        #4
        As far as I can remember, there are two parts to making a latent function. First, you need the class that takes care of updating the latent action (for instance, if it is a latent action that deals with movement of objects, you will need a class that handles updating the location). This class will be your "latent action". If I remember correctly, that is a class based on FPendingLatentAction (only a header class, doesn't have an implementation). You can use the FInterpolateComponentToAction class or FDelayAction class as an example. At the moment I don't entirely remember which variables, but certain variables were required to make a latent action work. If I remember correctly, those are at least some sort of duration variable and a FLatentActionInfo variable (more specifically, the variables within the FLatentActionInfo are required). Also, I think I remember having to put that class into the source code itself. Don't know why, but I remember reading somewhere that the source is compiled in a different way compared to classes in projects. Maybe you could circumvent all of this by simply making it a plugin.

        Second is the function that loads in the latent action. Again, if you want a good example, you should take a look at the MoveComponentTo function within the KismetSystemLibrary:

        Code:
        void UKismetSystemLibrary::MoveComponentTo(USceneComponent* Component, FVector TargetRelativeLocation, FRotator TargetRelativeRotation, bool bEaseOut, bool bEaseIn, float OverTime, TEnumAsByte<EMoveComponentAction::Type> MoveAction, FLatentActionInfo LatentInfo)
        {
        	if (UWorld* World = ((Component != NULL) ? Component->GetWorld() : NULL))
        	{
        		FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
        		FInterpolateComponentToAction* Action = LatentActionManager.FindExistingAction<FInterpolateComponentToAction>(LatentInfo.CallbackTarget, LatentInfo.UUID);
        
        		const FVector ComponentLocation = (Component != NULL) ? Component->RelativeLocation : FVector::ZeroVector;
        		const FRotator ComponentRotation = (Component != NULL) ? Component->RelativeRotation : FRotator::ZeroRotator;
        
        		// If not currently running
        		if (Action == NULL)
        		{
        			if (MoveAction == EMoveComponentAction::Move)
        			{
        				// Only act on a 'move' input if not running
        				Action = new FInterpolateComponentToAction(OverTime, LatentInfo, Component, bEaseOut, bEaseIn);
        
        				Action->TargetLocation = TargetRelativeLocation;
        				Action->TargetRotation = TargetRelativeRotation;
        
        				Action->InitialLocation = ComponentLocation;
        				Action->InitialRotation = ComponentRotation;
        
        				LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, Action);
        			}
        		}
        		else
        		{
        			if (MoveAction == EMoveComponentAction::Move)
        			{
        				// A 'Move' action while moving restarts interpolation
        				Action->TotalTime = OverTime;
        				Action->TimeElapsed = 0.f;
        
        				Action->TargetLocation = TargetRelativeLocation;
        				Action->TargetRotation = TargetRelativeRotation;
        
        				Action->InitialLocation = ComponentLocation;
        				Action->InitialRotation = ComponentRotation;
        			}
        			else if (MoveAction == EMoveComponentAction::Stop)
        			{
        				// 'Stop' just stops the interpolation where it is
        				Action->bInterpolating = false;
        			}
        			else if (MoveAction == EMoveComponentAction::Return)
        			{
        				// Return moves back to the beginning
        				Action->TotalTime = Action->TimeElapsed;
        				Action->TimeElapsed = 0.f;
        
        				// Set our target to be our initial, and set the new initial to be the current position
        				Action->TargetLocation = Action->InitialLocation;
        				Action->TargetRotation = Action->InitialRotation;
        
        				Action->InitialLocation = ComponentLocation;
        				Action->InitialRotation = ComponentRotation;
        			}
        		}
        	}
        }
        To create a latent function (like the MoveComponentTo posted above), I believe you need at least 2 variables; the object with which the latent function is compatible and a FLatentActionInfo struct. All other variables passed into the function are dependent on the type of latent action you wish to make (for comparison sake, you can also take a look at the Delay function in the same KismetSystemLibrary, whose only necessary variable is a duration for the delay). FLatentActionInfo will be responsible for determining whether your latent function has a callback target, and I believe it is also used to make sure the latent actions you load in are unique. This struct consists out of 4 variables.
        • Linkage (an int) - I am not entirely sure what Linkage does. I believe it has to do with what pin it calls in Blueprint when it is done with the latent execution. I have it set to 1. Setting it to 0 will cause it not to call anything, even in C++, so if you want to have the latent function call something upon completion, you should not set it to 0.
        • UUID (an int) - This allows you to address each and every latent action separately even if you have the same running multiple times. Useful for if you wish to have something like pausing or resetting specific latent actions.
        • ExecutionFunction (an FName) - This is the name of the function that is called when the latent action has been completed. If you define this function in C++, be sure to make it BlueprintCallable, otherwise it won't be called.
        • CallbackTarget (UObject reference) - the class to call back (the class in which your callback function is). Not sure whether it is necessary, but I put mine in the same class as where I activated the latent function.


        As for the body of the function itself, while you can make the functionality as complicated as you want (with pause and reset and whatever), you only really need 4 things to start a latent action.
        1. first you need to get a pointer to the latent action manager. This manager will be responsible for loading your latent action class and taking care of calling "UpdateOperation" within it. This is done through calling World->GetLatentActionManager();
        2. Then you need to get a pointer of your latent action. You do this by calling YourLatentActionManagerPointer.FindExistingAction<YourLatentActionClass>(LatentInfo.CallbackTarget, LatentInfo.UUID); You check whether this one is NULL or not (and do stuff based on that information)
        3. Then you need to create an instance of your latent action class by YourLatentActionClass* YourAction = new YourLatentActionClass(your duration variable, LatentInfo, your custom variables);
        4. Finally you need to actually start the action by calling YourLatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, YourAction);


        If you want more examples of latent functions, I recommend you find the latent functions that are already present within UE4. Good examples that I already named are the MoveComponentTo and the Delay latent functions, both in the KismetSystemLibrary class.
        Last edited by cridia; 12-15-2015, 03:38 AM.

        Comment


          #5
          Wow I know this is an old *** thread, but I just want to give a huge thank you to cridia for explaining this in detail. This SHOULD BE DOCUMENTED SOMEWHERE for level streaming in C++ because without this I had no idea how to properly assign a callback function to UGameplayStatics::LoadStreamLevel. This is the correct information to this thread for example. But looking back, it seems there is a plague of people too afraid of C++ to use it so they settle for BLUEPRINTS instead, and thus the knowledge base is lacking at best in this area unfortunately. And so that thread ended up unanswered.

          Comment


            #6
            Thank for this tutorial. It's very useful!
            ​​​​ and Could you have the way to stop or inturrupt the latent that you created?

            Comment


              #7
              Timers exist and solve every issue in this thread, without needing to use the latent action manager. They're also considerably more performant.

              https://docs.unrealengine.com/en-US/...ers/index.html

              Comment

              Working...
              X