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()
(byRuntime Resolved String
) I sometimes get race-conditions if I make the request in my actor’sPostInitializeComponents()
(even though aUGameInstanceSubsystem
is SUPPOSED to be in ready state before the first actor is instantiated) StaticLoad so the second call is happening inUInvManager::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”