Announcement

Collapse
No announcement yet.

Weird replication problem if replicated property set to class default

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Weird replication problem if replicated property set to class default

    I'm having a weird issue with replication and wondered if it's just because I'm doing something unexpected that you're not supposed to.

    To sum it up in a TLDR;

    Replication doesn't seem to work if an actor's property is set to non-default in the editor, and then changed to the default when the game starts.




    I've made a super basic example class:

    Code:
    #pragma once
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "ReplicationTest.generated.h"
    
    UCLASS( ClassGroup=(TestActor), Blueprintable, BlueprintType)
    class MVENGINE_API AReplicationTest : public AActor
    {
        GENERATED_BODY()
    
    public:
        // Sets default values for this actor's properties
        AReplicationTest();
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;
    
        // Replication notification for locking
        UFUNCTION()
        virtual void OnRep_Replication();
    
    public:
        UPROPERTY(ReplicatedUsing=OnRep_Replication, BlueprintReadWrite, EditAnywhere)
        int32 ReplicationTest = 0;
        UPROPERTY(BlueprintReadOnly, EditAnywhere)
        USceneComponent* Root;
    };
    With an implementation that lets me debug things:
    Code:
    #include "ReplicationTest.h"
    #include "Net/UnrealNetwork.h"
    
    // Sets default values
    AReplicationTest::AReplicationTest()
    {
        UE_LOG(LogTemp, Display, TEXT("%s [Client %i] %s (ReplicationTest: %i)"), __FUNCTIONW__, GPlayInEditorID, *GetName(), ReplicationTest);
        Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
        RootComponent = Root;
        SetReplicates(true);
    }
    
    void AReplicationTest::BeginPlay()
    {
        UE_LOG(LogTemp, Display, TEXT("%s [Client %i] %s (ReplicationTest: %i)"), __FUNCTIONW__, GPlayInEditorID, *GetName(), ReplicationTest);
        Super::BeginPlay();
    }
    
    void AReplicationTest::OnRep_Replication()
    {
        UE_LOG(LogTemp, Display, TEXT("%s [Client %i] %s (ReplicationTest: %i)"), __FUNCTIONW__, GPlayInEditorID, *GetName(), ReplicationTest);
    }
    
    void AReplicationTest::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const
    {
        UE_LOG(LogTemp, Display, TEXT("%s [Client %i] %s (ReplicationTest: %i)"), __FUNCTIONW__, GPlayInEditorID, *GetName(), ReplicationTest);
        Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    
        DOREPLIFETIME(AReplicationTest, ReplicationTest);
    }
    I've then added three instances of this actor to a map, ReplicationValue0, ReplicationValue1 and ReplicationValue2 and I set their 'ReplicationTest' values in the editor, Details panel to 0, 1 and 2 respectively.

    Next I create a level blueprint to do this on just the server:

    Click image for larger version  Name:	image_205286.jpg Views:	3 Size:	90.9 KB ID:	1823462

    and I run this as a dedicated server and a single player in editor, Everything works as expected - here's the console with a filter for anything with 'AReplicationTest'

    Code:
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue0 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue1 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue2 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue0 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue1 (ReplicationTest: 1)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue2 (ReplicationTest: 2)
    LogBlueprintUserMessages: [ReplicationTest_C_1] Server: AReplicationTest setup
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue0 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue1 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue2 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::GetLifetimeReplicatedProps [Client 1] Default__ReplicationTest (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::GetLifetimeReplicatedProps [Client 2] Default__ReplicationTest (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::OnRep_Replication [Client 2] ReplicationValue2 (ReplicationTest: 5)
    LogTemp: Display: AReplicationTest::OnRep_Replication [Client 2] ReplicationValue1 (ReplicationTest: 4)
    LogTemp: Display: AReplicationTest::OnRep_Replication [Client 2] ReplicationValue0 (ReplicationTest: 3)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue0 (ReplicationTest: 3)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue1 (ReplicationTest: 4)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue2 (ReplicationTest: 5)
    You can see that the new player (Client 2) that joins the server (Client 1) receives the three OnRep_Replication's before BeginPlay as the values have changed on the server. The client reports the correct 3, 4, 5 values for the respective AReplicationTest actors.

    However, I then change the blueprint to this:

    Click image for larger version  Name:	repl.jpg Views:	0 Size:	95.2 KB ID:	1823461

    What I'm expecting to happen is that the all of the actor's ReplicationTest values are set to 0 on the server, and when the client joins it receives two OnRep_Replication's after the values are set to 0 (for the two that had ReplicationTest set to 1 and 2). However, here's the log from that run:

    Code:
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue0 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue1 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue2 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue0 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue1 (ReplicationTest: 1)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue2 (ReplicationTest: 2)
    LogBlueprintUserMessages: [ReplicationTest_C_3] Server: AReplicationTest setup
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue0 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue1 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue2 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::GetLifetimeReplicatedProps [Client 1] Default__ReplicationTest (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::GetLifetimeReplicatedProps [Client 2] Default__ReplicationTest (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue0 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue1 (ReplicationTest: 1)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue2 (ReplicationTest: 2)
    You can see here that OnRep_Replication is never called on the client, and it never receives the values being set to 0. There is now a difference between the server and client, as the three actors have ReplicationTest's of 0,0,0 on the server and 0,1,2 on the client.

    Tweaking bits of code here and there to see if I could narrow down if I'm doing something, wrong, I noticed that if I change the class definition so that this:
    Code:
    int32 ReplicationTest = 0;
    is this:
    Code:
    int32 ReplicationTest = -1;
    and then run with the same blueprint above (setting all three to 0), the log shows this:
    Code:
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue2 (ReplicationTest: -1)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue1 (ReplicationTest: -1)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue0 (ReplicationTest: -1)
    LogBlueprintUserMessages: [ReplicationTest_C_3] Server: AReplicationTest setup
    LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue2 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue1 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue0 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue2 (ReplicationTest: -1)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue1 (ReplicationTest: -1)
    LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue0 (ReplicationTest: -1)
    LogTemp: Display: AReplicationTest::GetLifetimeReplicatedProps [Client 1] Default__ReplicationTest (ReplicationTest: -1)
    LogTemp: Display: AReplicationTest::GetLifetimeReplicatedProps [Client 2] Default__ReplicationTest (ReplicationTest: -1)
    LogTemp: Display: AReplicationTest::OnRep_Replication [Client 2] ReplicationValue2 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::OnRep_Replication [Client 2] ReplicationValue0 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::OnRep_Replication [Client 2] ReplicationValue1 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue2 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue1 (ReplicationTest: 0)
    LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue0 (ReplicationTest: 0)
    and everything works fine. The client receives all three OnRep_Replication's and the values once again match on the server and client.

    So it seems like replication-on-join fails to occur if the current value is equal to either the class default, or the value loaded from the editor. I can also confirm this same behaviour with blueprints.

    I've tried this in both 4.23, 4.24 and 4.25 and it appears this behaviour happens in all.

    Is this just correct, expected behaviour? is there something special I have to do when I'm replicating a value that's set to the class/BP default value? Have I done something incredibly dumb somewhere in my code (I'm not that experienced with C++ replication so not 100% sure)?

    I've attached a 4.25 blueprint version of this issue with the 3/4/5 and 0/0/0 example maps above:
    Attached Files
    Last edited by BtotheLaker; 10-22-2020, 09:13 AM.

    #2
    Have you yet tried putting
    Code:
    bAlwaysRelevant = true;
    in the actor's constructor?

    Comment


      #3
      Originally posted by jmontycastle View Post
      Have you yet tried putting
      Code:
      bAlwaysRelevant = true;
      in the actor's constructor?
      I have, but it didn't seem to help.

      It seems like the main problem is that an actor's replication doesn't occur at any point for joining clients if the actor's property is set in the constructor, then changed in postloaded, then changed back in the beginplay.

      Haven't yet found a workaround for this, or any indication of what to do to fix it. Still scratching my head trying to find out if there's a way to manually reset the replication 'cache' so that it knows the value has changed and should be sent to joining clients.

      Comment


        #4
        Originally posted by Blake J Robinson View Post
        So it seems like replication-on-join fails to occur if the current value is equal to either the class default, or the value loaded from the editor. I can also confirm this same behaviour with blueprints.
        This is correct behaviour. The Server will only send the value if it believes the Client has an out-of-date value, and (by default) the Client will only call the OnRep function if the value it receives from the Server is different to the local value it currently has.

        In C++, you can force the OnRep function to be called whenever the value is received from the Server even if it matches the clients' local value. You can achieve this with the following macro. This will at least allow you to debug whether the Server is sending anything at all.

        Code:
        DOREPLIFETIME_CONDITION_NOTIFY(AReplicationTest, ReplicationTest, COND_None, REPNOTIFY_Always);
        You can also add a parameter to the OnRep, which will be the Clients' previous value, e.g:

        Code:
        UFUNCTION() void OnRep_MyInt(const int32 PreviousValue);
        You can try comparing PreviousValue to Current Value when using REPNOTIFY_Always to see if the Server actually sent the property at all, but since the value you are changing it too matches the value it already has in editor at load time, the Server likely won't send it.

        Don't forget that replicated properties are assessed for changes at the end of a frame. If you set a bool from false->true->false in the same frame, the Server won't sent anything because it will determine that the value did not change at all.

        Comment


          #5
          Originally posted by TheJamsh View Post
          In C++, you can force the OnRep function to be called whenever the value is received from the Server even if it matches the clients' local value. You can achieve this with the following macro. This will at least allow you to debug whether the Server is sending anything at all.
          I'd actually tried this and found that the OnRep function never seems to be called. If there's nothing else I have to do with replication, it seems like something is not quite working right with replication. For example, in this scenario:

          1) Start the server,
          2) Wait any amount of time (I tried 30 seconds),
          3) Join a new client

          The authority and client are still incorrect, and the onRep is never received by the client, even if notify condition is set to always

          Code:
          LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue2 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue1 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::AReplicationTest [Client 1] ReplicationValue0 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue2 (ReplicationTest: 2)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue1 (ReplicationTest: 1)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 1] ReplicationValue0 (ReplicationTest: 0)
          LogBlueprintUserMessages: [ReplicationTest_C_1] Server: AReplicationTest setup
          LogTemp: Display: AReplicationTest::ResetReplication [Client 1] ReplicationValue0 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::ResetReplication [Client 1] ReplicationValue1 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::ResetReplication [Client 1] ReplicationValue2 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue2 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue1 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::AReplicationTest [Client 2] ReplicationValue0 (ReplicationTest: 0)
          LogTemp: Display: AMVEngineGameModeBase::PostLogin [Client 1] AReplicationTest ReplicationValue2 (ReplicationTest: 0)
          LogTemp: Display: AMVEngineGameModeBase::PostLogin [Client 1] AReplicationTest ReplicationValue1 (ReplicationTest: 0)
          LogTemp: Display: AMVEngineGameModeBase::PostLogin [Client 1] AReplicationTest ReplicationValue0 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::GetLifetimeReplicatedProps [Client 1] Default__ReplicationTest (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::GetLifetimeReplicatedProps [Client 2] Default__ReplicationTest (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue2 (ReplicationTest: 2)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue1 (ReplicationTest: 1)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 2] ReplicationValue0 (ReplicationTest: 0)
          LogBlueprintUserMessages: [ReplicationTest_C_1] Server: AReplicationTest Values are 0, 0, 0
          LogBlueprintUserMessages: [ReplicationTest_C_1] Client 1: AReplicationTest Values are 0, 1, 2
          
          LogTemp: Display: AReplicationTest::AReplicationTest [Client 3] ReplicationValue2 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::AReplicationTest [Client 3] ReplicationValue1 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::AReplicationTest [Client 3] ReplicationValue0 (ReplicationTest: 0)
          LogTemp: Display: AMVEngineGameModeBase::PostLogin [Client 1] AReplicationTest ReplicationValue2 (ReplicationTest: 0)
          LogTemp: Display: AMVEngineGameModeBase::PostLogin [Client 1] AReplicationTest ReplicationValue1 (ReplicationTest: 0)
          LogTemp: Display: AMVEngineGameModeBase::PostLogin [Client 1] AReplicationTest ReplicationValue0 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::GetLifetimeReplicatedProps [Client 3] Default__ReplicationTest (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 3] ReplicationValue2 (ReplicationTest: 2)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 3] ReplicationValue1 (ReplicationTest: 1)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 3] ReplicationValue0 (ReplicationTest: 0)
          LogBlueprintUserMessages: [ReplicationTest_C_1] Client 1: AReplicationTest Values are 0, 1, 2
          You can see above that even Client 3, which joins 30 seconds after ReplicationTest has been set to 0,0,0 for all three instances of the actor, has values that do not much the server because it never receives a replication event.

          The server logic seems to be 'if Replicated value == Constructor-set-default then don't send', which sort of makes sense. The problem, though, is that the server also seems to check 'if Replicated value == Loaded value PostLoad then don't send'.

          So the issue I'm finding is - if the server doesn't send the replicated value on login to the client in the above situation, how does the client know if the value == Constructor-set-default or Editor-set-value? Right now in my example the client will not receive the onrep and will assume it's the Editor-set-value, even though that means it differs from authority's value. Or am I getting confused?

          ---

          So a practical example in my game is locked doors. I have a notifying replicating value 'bLocked' which should be replicated. bLocked defaults to false. I then make a level with 10 doors. 5 of them have bLocked set to true in the map in the editor.

          If I start a server like this, and join a client, no replications are sent. Both the client and server believe 5 doors have bLocked as true, 5 doors have bLocked as false, just as the umap specifies.

          However, if I have the server 'load' a game before the client joins (say if I'm making a persistent world where the door's lock state is remembered begin sessions) and this load means that all 10 doors have bLocked set to false.

          When the client joins this time, again no replications are sent. The server thinks all 10 doors have bLocked set to false from loading the game. However, the client now thinks that 5 doors are locked still (since it never received a replication and it loaded the values from the umap serialization before PostLoad).

          ---

          Something interesting I've just found is that this whole problem only seems to happen if the server has changed the value from editor-set-serialized value, to class default in the first few seconds of the server being instantiated. For example:

          1) Start server. Set ReplicationTest to 0 in PostLoad. BeginPlay or the first Tick,
          2) Join a client.

          Replication never occurs. The ReplicationTest values do not match between the client or server. Server says 0,0,0 and client says 0,1, 2.

          However,

          1) Start a server. Wait 5 seconds. Set ReplicationTest to 0,
          2) Join a client.

          in this instance, the client is sent the replications before BeginPlay and debug shows onRep is called. ReplicationTest is correct on the client (0,0,0) and server (0,0, 0).

          For example, here's my example above (Start server, then join Client 3 30 seconds later, as I did before, only this time I have set the ReplicatoinTest value using a 5 second timer rather than in BeginPlay:

          Code:
          LogTemp: Display: AMVEngineGameModeBase::PostLogin [Client 1] AReplicationTest ReplicationValue2 (ReplicationTest: 0)
          LogTemp: Display: AMVEngineGameModeBase::PostLogin [Client 1] AReplicationTest ReplicationValue1 (ReplicationTest: 0)
          LogTemp: Display: AMVEngineGameModeBase::PostLogin [Client 1] AReplicationTest ReplicationValue0 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::GetLifetimeReplicatedProps [Client 3] Default__ReplicationTest (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::OnRep_Replication [Client 3] ReplicationValue2 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::OnRep_Replication [Client 3] ReplicationValue1 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 3] ReplicationValue2 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 3] ReplicationValue1 (ReplicationTest: 0)
          LogTemp: Display: AReplicationTest::BeginPlay [Client 3] ReplicationValue0 (ReplicationTest: 0)
          LogBlueprintUserMessages: [ReplicationTest_C_8] Client 2: AReplicationTest Values are 0, 0, 0
          The replication seems to be sent correctly this time, even though the only thing that has changed is that the value was changed 5 seconds after the game started, rather than in BeginPlay.

          Could this be a bug related to the server and how replicated values are cached to diff them?
          Last edited by BtotheLaker; 10-26-2020, 07:18 AM.

          Comment


            #6
            The server logic seems to be 'if Replicated value == Constructor-set-default then don't send', which sort of makes sense. The problem, though, is that the server also seems to check 'if Replicated value == Loaded value PostLoad then don't send'.
            That makes sense to me, the Client and Server shouldn't be following different paths in PostLoad (too early for that anyway) - so it's effectively redundant data. The values you set in editor are serialized from the map, so the Server and Client both know what those values are and the Server doesn't need to send them.

            PostLoad is too early to be changing values Server-Side - the actor channel is created after that and thus so is the replication shadow data. It's likely that changing properties that early means the shadow data contains those changes already. You may want to change properties later than that, such as PostInitializeComponents or BeginPlay maybe.

            Also, map-startup actors are "spawned" client-side even though they are replicated - but they will not receive any network updates until they become network relevant like anything else. Once they become network relevant, the Server will create an actor channel and the channel will resolve to the already-existing actor. You can change this behaviour by settig bNetLoadClient to false, which IIRC destroys the actor client-side once loaded, and a new actor will be spawned just like a runtime-spawned replicated actor.
            Last edited by TheJamsh; 10-26-2020, 08:50 AM.

            Comment


              #7
              Originally posted by TheJamsh View Post
              That makes sense to me, the Client and Server shouldn't be following different paths in PostLoad (too early for that anyway) - so it's effectively redundant data. The values you set in editor are serialized from the map, so the Server and Client both know what those values are and the Server doesn't need to send them.
              The issue I'm having is that the values do actually differ from the serialized map data. The serialized data has ReplicationTest for the three actors as 0,1 and 2. However, my BeginPlay on the server (or a Tick) then changes them to 0,0,0. If a client then joins after this point (when the values are 0,0,0 on the server), shouldn't the server be telling the client 'Hey two of these values differ from the serialised map that the client/server have! It's 0,0 now'?

              Right now nothing gets sent, even when I confirm that ReplicationTest for the three actors is 0,0,0 before the new client joins. Also, I can wait minutes and add a new client (using late join in the editor) and the server still doesn't send the newly joining client any kind of replication data.

              Originally posted by TheJamsh View Post
              You may want to change properties later than that, such as PostInitializeComponents or BeginPlay maybe.
              I've tried in both those functions and it doesn't seem to change. The server still fails to send replication data to the client on join.

              The only ways I've found so far to make this work as I'd expect is
              • On the server, wait 1-2 seconds after BeginPlay before changing ReplicationTest on the server. If this is done, any newly joined client will be sent replication events for the two changed ReplicationTests,
              • Have the server change the value of ReplicationTest to something else, and then back to 0 (1-2 seconds after the server has begun play). If the value has changed (say I change ReplicationTest to 5 at 1 second, and then 0 at 2 seconds, any newly joined clients are sent the replication events.
              It just seems to be the situation where I change ReplicationTest too quickly after the server has started.

              Investigating more, it doesn't seem to be that the ReplicationTest isn't sent because it's the default value. The fact that waiting 1-2 seconds and then changing to 0 working means that it seems like the server is mistaking the data for redundant when it's not. Afterall, if the map is serialized as 0,1,2 and the server doesn't tell the client 'it's 0,0,0 now' or 'it's 0,1,2 now', how should the client know whether ReplicationTest should be the map serialized data, or the class default?
              Last edited by BtotheLaker; 10-26-2020, 01:49 PM.

              Comment


                #8
                Just to confirm what I think is happening at the moment in a timeline:
                Server Starts
                Server loads map. 3 x ReplicationActor. ReplicationTests are class default of 0 for all
                Server map-serialization recalled/duplicated. ReplicationTest for each is 0,1,2 respectively
                Server BeginPlay. ReplicationTest is [0,1,2]
                Server loads save-game. Save game means that ReplicationActor ReplicationTests set to 0,0,0 respectively
                .
                . Wait 10 seconds to join a client
                .
                Client joins
                Client loads map. 3 x ReplicationActor. ReplicationTests are class default of 0 for all
                Client map-serialization recalled/duplicated. ReplicationTest for each is 0,1,2 respectively
                Client receives no replication events
                Client BeginPlay. ReplicationTest is [0,1,2]
                .
                . Wait until game time is 20 seconds
                .
                Server print ReplicationTest: [0,0,0]
                Client print ReplicationTest: [0,1,2]



                This is what I could get to work:
                Server Starts
                Server loads map. 3 x ReplicationActor. ReplicationTests are class default of 0 for all
                Server map-serialization recalled/duplicated. ReplicationTest for each is 0,1,2 respectively
                Server BeginPlay. ReplicationTest is [0,1,2]
                .
                . Server waits 1 second
                .
                Server loads save-game. Save game means that ReplicationActor ReplicationTests set to 0,0,0 respectively
                .
                . Wait 10 seconds to join a client
                .
                Client joins
                Client loads map. 3 x ReplicationActor. ReplicationTests are class default of 0 for all
                Client map-serialization recalled/duplicated. ReplicationTest for each is 0,1,2 respectively
                Client receives replication events for the 2 changed ReplicationActors. ReplicationTest of 0,0 respectively
                Client BeginPlay. ReplicationTest is [0,1,2]
                .
                . Wait until game time is 20 seconds
                .
                Server print ReplicationTest: [0,0,0]
                Client print ReplicationTest: [0,0,0]

                What I'm expecting to happen is that even if the server has no 1 second wait, the replication events should be sent to the client on join if ReplicationTest differs from the serialized values.
                Last edited by BtotheLaker; 10-26-2020, 02:34 PM.

                Comment


                  #9
                  could it be because override was not added?

                  virtual void OnRep_Replication() override;

                  or does this function not require that?

                  Comment


                    #10
                    Not sure it can override, as the function OnRep_Replication doesn't exist in the base class.

                    Comment


                      #11
                      I found this in actor.ccp

                      void AActor::OnRep_Instigator() {}

                      its not doing nothing. guess no one has implemented it yet?

                      Comment


                        #12
                        Default server behavior is that a FProperty which current value didn't change (on server), since last replication cycle, will not be picked for a replication broadcast next frame; That also applies to members of a replicated structure (UE3/UDK sends the whole struct, UE4 picks a member value change).
                        | Savior | USQLite | FSM | Object Pool | Sound Occlusion | Property Transfer | Magic Nodes | MORE |

                        Comment


                          #13
                          Originally posted by BrUnO XaVIeR View Post
                          Default server behavior is that a FProperty which current value didn't change (on server), since last replication cycle, will not be picked for a replication broadcast next frame; That also applies to members of a replicated structure (UE3/UDK sends the whole struct, UE4 picks a member value change).
                          Taking an example where ReplicationTest is set to 1 in the umap, is this caused because ReplicationTest is set to 0 in the constructor,1 when read from serialized and then 0 in the BeginPlay? (ie this all happening too fast/i the same frame and so the server thinks it never changed from 0 and doesn't replicate)?

                          Is there any kind of workaround for this problem? This seems like a bit of a big flaw - as it stands I can't figure out a way for the client to know if the value should be the constructor default value, or the map serialize value in this situation. There seems to be no way to tell? It seems less than ideal that replicated values differ on the client from the authority as soon as a player connects.

                          Is there a way to force a replication cycle, or force the caching of an FProperty for replication?
                          Last edited by BtotheLaker; 10-26-2020, 10:01 PM.

                          Comment


                            #14
                            Some people make a struct with a byte flag or a boolean property then flip it true/false just to force a replication where nothing really changed.

                            Others just make a RPC call somewhere to force update values.
                            | Savior | USQLite | FSM | Object Pool | Sound Occlusion | Property Transfer | Magic Nodes | MORE |

                            Comment


                              #15
                              I had considered an RPC call, but it seemed like it would be redundant -
                              • If i had a door that has bLocked as false in the umap, but true in the savegame, replication would work correctly and so I'd be sending an RPC on top of onrep,
                              • With something like a 'locked door' actor, the server owns the actor and so I'd have to use NetMulticast. All the door, chest, etc. statuses would be sent again to every player every time someone joins the game.
                              I guess I could send an RPC to something like the PlayerController via Client, Reliable on BeginPlay so that only the new player receives it.

                              I'm not too keen on using a struct as it feels like it would make actor setup in the editor more obfuscated/complicated/hacky. RIght now I pop down a 'Door' actor and just tick a 'Locked' checkbox. A struct would look a bit messier. I'd also read bad things about structs in structs and so have avoided them so far where not needed (though this might be older UE4 versions). It sounds like this may be the only way to get around this, though - thanks for the suggestion!

                              The only other idea I had was to use a hidden enum for the door locked state that had 'Default', 'Unlocked' and 'Locked', but this too would result in redundant data being sent (ie. if a door is unlocked in the umap it would be sending this unlocked replication, even though the door is already unlocked on the client from the client umap serialization).

                              Perhaps I should rethink how I'm doing doors/chests/etc. so that it's RPC based rather than property replication. It just seemed that property replication was perfect for something like this and what it should be used for.

                              I've submitted this as a bug. It feels like post-serialization/duplication should be the starting point for FProperty replication and not the constructor?
                              Last edited by BtotheLaker; 10-26-2020, 10:36 PM.

                              Comment

                              Working...
                              X