Are the initial properties of a replicated actor set before BeginPlay on clients?

If I have a replicated actor with a replicated property that is COND_InitialOnly:

class AMyActor : public AActor {
    /*...*/
    
    AMyActor::AMyActor {
        bReplciates = true;
        bAlwaysRelevant = true;
        NetUpdateFrequency = 1;
        bNetLoadOnClient = false
    }

    UPROPERTY(Replicated)
    int32 MyProperty;

    void GetLifetimeReplicatedProps(
         TArray<FLifetimeProperty>& OutLifetimeProps) const override
    {
        Super::GetLifetimeReplicatedProps(OutLifetimeProps);
        DOREPLIFETIME_CONDITION(AMyActor, MyProperty, COND_InitialOnly);
    }
    /*...*/
};

and then I spawn it in the game mode:

void AMyGameMode::Tick() {
   if (whatever) {
      
      A = GetWorld()->SpawnActor<AMyActor>(/*...*/ bDeferConstruction = true);
      A->MyProperty = 42;
      A->FinishSpawning()
   }

then when the AMyActor instances spawn from replication on the clients, is MyProperty guaranteed to be set to 42 before their BeginPlay is called?

That is, are the initial properties contained in the same message that instructs the clients to spawn the replicated actor? Or is it possible they could come later in a separate message after the BeginPlay of the replicated actor has already been called?

1 Like

After studying the code I think I’ve figured out that it is guaranteed. Bunches (unreal network messages) are sent via UActorChannels. When a new (bReplicates=true) actor is spawned on the server the following happens on the client:

  1. A UActorChannel is created with Actor=NULL
  2. The server prepares the initial bunch. An initial bunch contains both the spawn info and initial properties. (See “An ActorChannel bunch looks like this” in ActorChannel.h)
  3. Each bunch (including the initial bunch) is processed by one call to UActorChannel::ProcessBunch
  4. UActorChannel::ProcessBunch does (essentially) the following:
if (Actor == NULL) // this is the initial bunch
{
     bSpawnedNewActor = true;
     Actor = SerializeNewActor(/*...*/); // Calls NewObject<Actor>
}

while (/*...*/)
    Replicator->ReceivedBunch(/*...*/); // Read properties and rpcs

if (Actor && bSpawnedNewActor)
    Actor->PostNetInit(); // calls BeginPlay

So for the initial bunch all three parts of that code are active. NewObject is called to create the replicated actor, its initial properties are set, then PostNetInit is called on it which calls BeginPlay

1 Like

Continuing the discussion from Are the initial properties of a replicated actor set before BeginPlay on clients?: