TFunction - passing reference to a function - cant get it to work

Has anyone ever gotten this to work? I’ve looked through all the available questions and examples on the internet & forum, but it stubbornly refuses

I wish to replace a long switch statement that selects functions after a timer has ended, which im doing using a hidden stored parameter

header…

UFUNCTION()
void PlayCutScene(FName TheCutSceneReference,  int32 TheResumeFunctionReference);

implementation in timer callback using stored hidden parameter…

	switch (MyFunctionResumeIndex)
	{
	case 0:
		REF_REF->GetLevelController()->ResumeAfterCutScene();
		break;

	case 100:
		break;
	}

with something like this - I Understand from research that this cannot be a UFUNCTION here ???


void PlayCutScene(FName TheCutSceneReference,  TFunction<void()> TheFunctionReference);

just so I can pass in the function as a reference - mainly for readability and as an exercise to improve my coding skills. This is easy in standard c++, but not unreal c++

Then I can call the function such as


PlayCutScene("INTRO", &LevelController::ResumeAfterCutScene);
1 Like

I am currently working on getting this too work.

I want to pass callback functions to an UAsyncLoader along with the request using a FDataObject, and in the callback I want to include the thing that was loaded. I will also be potentially having multiple of these so callbacks for after the AsyncLoad() would be beneficial.

one place I intend to use these is in my inventory system so that I don’t have dozens to hundreds of unique blueprints floating in memory

for this I have an FItemDef think of this a just a USTRUCT these work as expected

USTRUCT(BlueprintType)
struct FItemDef
{
    int32 ItemID = 0;
};

then I have an inventory UInventoryComp : public ActorComponent that is each going to be calling my UAsyncLoader : public UGameInstanceSubsystem which is responsible for the AsyncLoading calls to AssetManager, and keeping track of when these can be released. the Loaded item for this will be

UCLASS()
ASpawnableItem : public AActor`
{
// ...
public:
    FItemDef ItemDef;
// ...
};

Items will be spawned through a combinations of DataTables, and TSoftClassPtr<> these are working fine.

the Inventory has the function to be invoked after the load

UFUNCTION()
void ReceiveAsyncLoadedItem(ASpawnableItem* LoadedItem);

the AsyncLoader is supposed to have a function

// "this is where I get the error."
// UFUNCTION()
void LoadItemAsync(const FItemDef& itemToSpawn, TFunction<void(ASpawnableItem*)>& Callback);

the error VS throws is

Error : Unable to find 'class', 'delegate', 'enum', or 'struct' with name 'TFunction'

I even went so far as to try and #include "Templates/Function.h" which does not satisfy VS

1 Like

I’m getting the same error too. I’m trying to reduce redundancy in my code by passing the function as an argument

void UpdateMeshProperties(TFunction<void(ABowlMesh*, AVehicleCarpet*)> PropertySetter);

But it keeps saying

Error: Unable to find 'class', 'delegate', 'enum', or 'struct' with name 'TFunction'

and I have included #include "Templates/Function.h" as well

I’m stuck and have no clue what to do… Should I just stick to redundant code?

this is a work around not an answer: What I ended up doing was creating my own “Callback” System

USTRUCT(BlueprintType)
struct FItemCallbackInfo
{
	GENERATED_BODY();
public:
	TSharedPtr<FStreamableHandle> Handle;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	TSoftClassPtr<ARUNSpawnItemBase> SoftItem;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	int32 itemID = -1;
	TArray<TWeakObjectPtr<UInvManager>> iRequesters;
};

the pointer could be to an interface if there are going to be multiple different types of things making the request you could also skip the USTRUCT() and UPROPERTY stuff on this if you “trust it to work”

I hold these structs in a TMap<int32, FItemCallbackInfo> ItemCallbackMap

I concieve of my “items” as actors (considering turning them into SceneComponents if I got delegates working but haven’t done it yet) then I have the following in my UAssetRepo:UGameInstanceSubsystem

// public:
EReturnState UAssetRepo::SpawnItemDefWInst(int32 ItemID, const UInvManager* iRequester, ASpawnItemBase*& outItem)
{
	FItemDataTableRow row;  // 'this is where the TSoftClassPtr lives'
	EReturnState tmp = TryGetItemRowFromTable(ItemID, row);
	if ( tmp == EReturnState::Pass )
	{
		if ( IsSyncLoadItemSafe(iRequester, ItemID, row.ItemBlueprint) )
		{
			FActorSpawnParameters SpawnParams;
			SpawnParams.bNoFail = true;
			SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
			ASpawnItemBase* resultItem = GetWorld()->SpawnActor<ASpawnItemBase>(row.ItemBlueprint.LoadSynchronous(), FTransform::Identity, SpawnParams);
			if ( resultItem != nullptr )
			{
				outItem = resultItem;
				return EReturnState::Pass;
			}

		}
		return EReturnState::Fail;
	}
	return tmp;
}

// private
// Do we even need to do Async Load in the first place
bool UAssetRepo::IsSyncLoadItemSafe(const UInvManager* iRequester, int32 ItemID, const TSoftClassPtr<ASpawnItemBase> SpawnItem)
{
	if ( SpawnItem != nullptr )
	{
		FItemCallbackInfo info = ItemCallbackMap.FindOrAdd(ItemID);
		if ( info.itemID == ItemID )
		{
			info.iRequesters.AddUnique(const_cast<UInvManager*>(iRequester));
		}
		else
		{
			info.itemID = ItemID;
			info.SoftItem = SpawnItem;
			info.iRequesters.Add(const_cast<UInvManager*>(iRequester));
		}
		if ( SpawnItem.IsValid() && !SpawnItem.IsPending() )
		{
			//ItemCallbackMap.Add(ItemID, info);
			return true;
		}
		else if ( SpawnItem.IsPending() && info.Handle != nullptr ){}
		else
		{
			FSoftObjectPath Path = SpawnItem.ToSoftObjectPath();
			info.Handle = AsyncAssetManager.RequestAsyncLoad(Path, FStreamableDelegate::CreateUObject(this, &UAssetRepo::OnAsyncItemLoaded, ItemID)/*, 0, true*/);
		}
	}
	return false;
}
// private: this is the part that would actually be replaced with the bound function
void UAssetRepo::OnAsyncItemLoaded(int32 ItemID)
{
	if ( ItemCallbackMap.Contains(ItemID))
	{
		FItemCallbackInfo Callback = ItemCallbackMap.FindRef(ItemID);
		if ( Callback.itemID == ItemID/* && (Callback.SoftItem.IsValid() && !Callback.SoftItem.IsPending())*/ )
		{
			UClass* spawnable = Callback.SoftItem.LoadSynchronous();
			FActorSpawnParameters SpawnParams;
			SpawnParams.bNoFail = true;
			SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
            // this could have a break for 
			for ( int32 ii = Callback.iRequesters.Num()-1; ii >= 0; ii-- )
			{
				if ( Callback.iRequesters[ii] != nullptr )
				{
					int32 jj = Callback.iRequesters[ii]->PendingItemCountByID(ItemID);
					for ( ; jj > 0 ; jj-- )
					{
						ASpawnItemBase* Spawned = GetWorld()->SpawnActor<ASpawnItemBase>(spawnable, FTransform::Identity, SpawnParams);
						Callback.iRequesters[ii]->ReceiveSpawnedItem(Callback.itemID, Spawned);
					}
				}
				else
				{
					Callback.iRequesters.RemoveAt(ii);
				}
			}
		}
	}
}

this does have issues:

  • I technically have to request the initial Load of an Item twice (and because my DataTable is StaticLoaded at my Subsystem::Inititialize() (by Runtime Resolved String) I sometimes get race-conditions if I make the request in my actor’s PostInitializeComponents() (even though a UGameInstanceSubsystem is SUPPOSED to be in ready state before the first actor is instantiated) StaticLoad so the second call is happening in UInvManager::TickComponent()
  • at least in PIE If I don’t make 2 calls then I have to run PIE twice. this should be fixed when I implement my “Main Menu” where the DataTable can be loaded at that point before any requests ‘should’ be happening.

with the delegate approach I would have a bunch of duplicated SpawnActor, and I would need to have my UInvManager know of FStreamableHandle which I feel would be worse regarding “Limited Responsibility of Objects”