I have a UObject derived class that I am using as weapon state data. It’s very simple right now, but is currently causing the replication code in UE4 to crash with an access violation.
What it boils down to is that the object has an uninitialized InternalIndex, and when it goes to replicate, there isn’t adequate checks in place and UE4 crashes. That’s fine, and I’ll patch that.
However - the remaining issue is how to get it an internal index. It’s a private member in UObjectBase, and is initialized to the value I want it to be (INDEX_NONE) in most of the constructors. But not the one that gets called when you call NewObject. That one is this:
UObject::UObject(const class FPostConstructInitializeProperties& PCIP)
{
check(!PCIP.Obj || PCIP.Obj == this);
const_cast<FPostConstructInitializeProperties&>(PCIP).Obj = this;
}
As you can see, no call to any of its ancestors constructors, so no initialization of InternalIndex.
InternalIndex needs to be initialized to INDEX_NONE or the function that gives it a real index in FUObjectArray asserts.
void FUObjectArray::AllocateUObjectIndex(UObjectBase* Object)
{
int32 Index;
check(Object->InternalIndex == INDEX_NONE);
So, how do I give it an internal index the right way?
The classes header:
UCLASS()
class UWeaponState : public UObject
{
GENERATED_UCLASS_BODY()
int32 RepObjId;
int32 RepKey;
UPROPERTY(replicated)
FName weaponName;
UPROPERTY(replicated)
FName mountName;
};
The init of one of these objects:
void ARobotPlayerState::initWeaponStates()
{
if (WeaponMounts.Num() == 0)
{
for (int i = 0; i < NumberOfWeaponMounts; i++)
{
FName key = FName(*FString::Printf(TEXT("mount_%02d"), i));
UWeaponState *state = NewObject<UWeaponState>();
state->mountName = key;
state->weaponName = NAME_None;
state->RepObjId = i + 1;
state->SetFlags(RF_RootSet | RF_WasLoaded);
WeaponMounts.Add(key, state);
}
}
}
EDIT: And the function it now asserts in
bool ARobotPlayerState::ReplicateSubobjects(UActorChannel *Channel, FOutBunch *Bunch, FReplicationFlags *RepFlags)
{
check(Channel);
check(Bunch);
check(RepFlags);
bool WroteSomething = false;
if (Channel->KeyNeedsToReplicate(0, WeaponMountsRepKey)) // Does the array need to replicate?
{
TArray<FName> keys;
WeaponMounts.GetKeys(keys);
for (auto wmIt = keys.CreateIterator(); wmIt; wmIt++)
{
FName key = *wmIt;
UWeaponState * weaponState = WeaponMounts[key];
if (Channel->KeyNeedsToReplicate(weaponState->RepObjId, weaponState->RepKey))
{
WroteSomething |= Channel->ReplicateSubobject(weaponState, *Bunch, *RepFlags);
}
}
}
return WroteSomething;
}