I am working on a replicated inventory component using FFastArraySerializerItem and FFastArraySerializer.
I am getting a strange error in the editor with my FFastArraySerializer array.
In the editor I have this message, “LogNetFastTArray: Warning: OldMap size (1) does not match item count (2)”.
The engine source has this line in FFastArraySeralizer:
UE_LOG(LogNetFastTArray, Warning, TEXT("OldMap size (%d) does not match item count (%d) for struct (%s), missing a MarkArrayDirty on element add/remove?"), OldIDToKeyMap.Num(), ArraySerializer.CachedNumItemsToConsiderForWriting, *Struct->GetOwnerStruct()->GetName());
I have marked each item as dirty and also tried using MarkArrayDirty() after changes. Not sure what could be the cause or if I am overlooking something.
FFastArraySerializerItem and FFastArraySerializer code:
USTRUCT(BlueprintType)
struct INVENTORYSYSTEM_API FInventoryItem : public FFastArraySerializerItem
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FInventoryItem")
int32 Amount;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FInventoryItem")
int32 MaxAmount;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FInventoryItem")
bool bCanStack = false;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FInventoryItem")
UInventoryItemAsset* ItemAsset = nullptr;
// Item property key value pairs for dynamic properties
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Inventory System", DisplayName = "Dynamic Item Properties")
FItemPropertyArray ItemProperties;
void PreReplicatedRemove(const struct FInventoryArray& InArraySerializer);
void PostReplicatedAdd(const struct FInventoryArray& InArraySerializer);
void PostReplicatedChange(const struct FInventoryArray& InArraySerializer);
FInventoryItem();
FInventoryItem(int32 NewAmount, int32 NewMaxAmount, bool bNewCanStack, UInventoryItemAsset* NewItemAsset);
friend bool operator== (const FInventoryItem& First, const FInventoryItem& Other)
{
return (First.ItemAsset == Other.ItemAsset && First.ItemProperties.Properties == Other.ItemProperties.Properties);
}
friend bool operator!= (const FInventoryItem& First, const FInventoryItem& Other)
{
return (First.ItemAsset != Other.ItemAsset || First.ItemProperties.Properties != Other.ItemProperties.Properties);
}
friend bool operator>= (const FInventoryItem& First, const FInventoryItem& Other)
{
return (First.Amount >= Other.Amount);
}
friend bool operator<= (const FInventoryItem& First, const FInventoryItem& Other)
{
return (First.Amount <= Other.Amount);
}
friend bool operator> (const FInventoryItem& First, const FInventoryItem& Other)
{
return (First.Amount > Other.Amount);
}
friend bool operator< (const FInventoryItem& First, const FInventoryItem& Other)
{
return (First.Amount < Other.Amount);
}
};
USTRUCT(BlueprintType)
struct FInventoryArray : public FFastArraySerializer
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TArray< FInventoryItem > Items;
bool NetDeltaSerialize(FNetDeltaSerializeInfo & DeltaParms);
FInventoryArrayChangedDelegate InventoryArrayChangedDelegate;
};
template<>
struct TStructOpsTypeTraits< FInventoryArray > : public TStructOpsTypeTraitsBase2< FInventoryArray >
{
enum
{
WithNetDeltaSerializer = true,
};
};
And the relevant inventory component code:
UInventoryComponent::UInventoryComponent(const FObjectInitializer& OI)
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = false;
SetIsReplicatedByDefault(true);
Slots.Items.Empty();
for (int i = 0; i < NumberOfSlots; i++)
{
CreateEmptySlot();
}
}
// Called when the game starts
void UInventoryComponent::BeginPlay()
{
Super::BeginPlay();
}
void UInventoryComponent::OnUnregister()
{
const AActor* lOwner = GetOwner();
if (lOwner && lOwner->HasAuthority())
{
DestroyAllSlots();
}
Super::OnUnregister();
}
#if WITH_EDITOR
void UInventoryComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
//UE_LOG(LogTemp, Warning, TEXT("%s() - %s"), *FString(__FUNCTION__), *PropertyName.ToString());
if (PropertyName == TEXT("Items"))
{
//Slots.MarkArrayDirty();
//OnInventoryUpdated.Broadcast();
}
if (PropertyName == TEXT("NumberOfSlots"))
{
Slots.Items.Empty();
for (int i = 0; i < NumberOfSlots; i++)
{
CreateEmptySlot();
}
}
}
#endif //WITH_EDITOR
void UInventoryComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UInventoryComponent, Slots);
}
bool UInventoryComponent::ReplicateSubobjects(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
bool bWroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
return bWroteSomething;
}
FInventoryArray UInventoryComponent::GetAllSlots()
{
return Slots;
}
void UInventoryComponent::CreateEmptySlot()
{
if (!GetOwner())
{
return;
}
if (GetOwner() && GetOwner()->GetLocalRole() == ROLE_AutonomousProxy)
{
CreateEmptySlot_Server();
return;
}
FInventoryItem CreatedSlot;
const int32 EmptyIndex = Slots.Items.Add(CreatedSlot);
Slots.MarkArrayDirty();
OnInventoryUpdated.Broadcast();
}
void UInventoryComponent::CreateEmptySlot_Server_Implementation()
{
CreateEmptySlot();
}
bool UInventoryComponent::CreateEmptySlot_Server_Validate()
{
return true;
}
bool UInventoryComponent::DestroySlot(FInventoryItem ItemToDestroy)
{
if (!GetOwner())
{
return false;
}
if (GetOwner() && GetOwner()->GetLocalRole() == ROLE_AutonomousProxy)
{
DestroySlot_Server(ItemToDestroy);
return DestroySlot_Server_Validate(ItemToDestroy);
}
return true;
}
void UInventoryComponent::DestroySlot_Server_Implementation(FInventoryItem ItemToDestroy)
{
DestroySlot(ItemToDestroy);
}
bool UInventoryComponent::DestroySlot_Server_Validate(FInventoryItem ItemToDestroy)
{
return true;
}
void UInventoryComponent::DestroyAllSlots()
{
if (!GetOwner())
{
return;
}
if (GetOwner() && GetOwner()->GetLocalRole() == ROLE_AutonomousProxy)
{
DestroyAllSlots_Server();
return;
}
Slots.Items.Empty();
}
void UInventoryComponent::DestroyAllSlots_Server_Implementation()
{
DestroyAllSlots();
}
bool UInventoryComponent::DestroyAllSlots_Server_Validate()
{
return true;
}
Is there something else I should be overriding or implementing to not get this error?
I even got the LyraStarterGame to see if I was initializing my structs wrong but I am stumped.
Here is what the Character blueprint looks like:
Any help is appreciated.
Thanks!