Download

COND_InitialOnly replication is not working for UActorComponent how to fix that?

Actually, it works, but only if Owning actor and ActorComponent are created in the same time.
What is not working - is COND_InitialOnly replication of properties for UActorComponents which are added at runtime.

For example. AGameState - it is created at the beginning of the mach. Then at some point (ex. 5 minutes after match begin, or 2 seconds) GameMode decide it need to add some “game feature” to current match. In our architecture features are represented as UActorComponents which can be attached to GameState or PlayerState.

So the problem is that despite that UActorComponent is newly created, it’s COND_InitialOnly variables are not sent to the clients. (they will be sent to clients who join only after that component was added.)

I found the code where that happens.

bool AActor::ReplicateSubobjects(UActorChannel *Channel, FOutBunch *Bunch, FReplicationFlags *RepFlags) is replicating actors components as subobjects. The actual function which does this is

bool UActorChannel::ReplicateSubobject(UObject *Obj, FOutBunch &Bunch, const FReplicationFlags &RepFlags)

there is a code

bool NewSubobject = false;
bool bCreatedReplicator = false;
TSharedRef<FObjectReplicator>& ObjectReplicator = FindOrCreateReplicator(Obj, &bCreatedReplicator);
if (bCreatedReplicator)
{
    // This is the first time replicating this subobject
    // This bunch should be reliable and we should always return true
    // even if the object properties did not diff from the CDO
    // (this will ensure the content header chunk is sent which is all we care about
    // to spawn this on the client).
    Bunch.bReliable = true;
    NewSubobject = true;
}
bool WroteSomething = ObjectReplicator.Get().ReplicateProperties(Bunch, RepFlags);

so it effectively detects that the object is new. but RepFlags.bNetInitial is formed for the Actor, and is set to false.

I am thinking to override bool AActor::ReplicateSubobjects(UActorChannel *Channel, FOutBunch *Bunch, FReplicationFlags *RepFlags) and set RepFlags.bNetInitial before UActorComponent replication. But that looks a little bit hacky.

You could try to manually update the authority state of your game when your components are added.

Meaning, you can do a ForceNetUpdate(). Might solve your issue and it only forces 1 replication so it is optimal too.

ForceNetUpdate() will not work here. Actually correctly rewriting ReplicateSubobjects did the trick.

bool AMyGameState::ReplicateSubobjects(UActorChannel *Channel, FOutBunch *Bunch, FReplicationFlags *RepFlags)
{
check(Channel);
check(Bunch);
check(RepFlags);

bool WroteSomething = false;

for (UActorComponent* ActorComp : ReplicatedComponents)
{
    if (ActorComp && ActorComp->GetIsReplicated())
    {
        bool bNetInitial = RepFlags->bNetInitial;
        RepFlags->bNetInitial = Channel->ReplicationMap.Find(ActorComp) == nullptr;

        WroteSomething |= ActorComp->ReplicateSubobjects(Channel, Bunch, RepFlags);        // Lets the component add subobjects before replicating its own properties.
        WroteSomething |= Channel->ReplicateSubobject(ActorComp, *Bunch, *RepFlags);    // (this makes those subobjects 'supported', and from here on those objects may have reference replicated)

        RepFlags->bNetInitial = bNetInitial;
    }
}

return WroteSomething;

}

Now COND_InitialOnly works per UObject, not per AActor.