Announcement

Collapse
No announcement yet.

New blog post: Network Tips and Tricks

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

    New blog post: Network Tips and Tricks

    Hey guys! Lead engine programmer John Pollard has put together a new blog post for you all about Network Tips and Tricks.
    ------

    Basic Property Replication
    I wanted to talk about a few tips that are good to know about when dealing with replicated properties in native code.

    The full explanation of how to replicate a property is somewhat out of the scope of this article, but I can quickly go over the basics here.

    To replicate a property you need to do a few things:

    In the header of the actor class where the property is defined, you need to make sure you have the ‘replicated’ keyword as one of the parameters to the UPROPERTY declaration:
    class ENGINE_API AActor : public UObject
    {
    UPROPERTY( replicated )
    AActor * Owner;
    };

    In the implementation of the actor class, you need to implement the GetLifetimeReplicatedProps function:

    void AActor::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
    {
    DOREPLIFETIME( AActor, Owner );
    }

    In the actor’s constructor, make sure you have the bReplicates flag set to true:

    AActor::AActor( const class FPostConstructInitializeProperties & PCIP ) : Super( PCIP )
    {
    bReplicates = true;
    }

    That’s about it. The member variable ‘Owner’ will now be synchronized to all connected clients for every copy of this actor type that is currently instantiated (in this case, the base actor class).

    Conditional Property Replication
    Once a property is registered for replication, you can’t unregister it (that’s where the lifetime part comes from). The reason for this is because we bake in as much information as possible, so we can take advantage of sharing work across many connections for the same set of properties. This saves a lot of computation time.

    So how does one get more fine grain control over how this property replicates? That’s where conditional properties come in.


    By default, each replicated property has a built-in condition, and that is that they don’t replicate if they haven’t changed.

    To give you more control over how a property replicates, there is a special macro that allows you to add a secondary condition.

    This macro is called DOREPLIFETIME_CONDITION. An example of its usage can be seen below:
    void AActor::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
    {
    DOREPLIFETIME_CONDITION( AActor, ReplicatedMovement, COND_SimulatedOnly );
    }

    The ‘COND_SimulatedOnly’ flag that was passed into the condition macro will cause an extra check to be performed before even considering this property for replication. In this case, it will only replicate to clients that have a simulating copy of this actor.

    One of the big benefits of this is that it saves bandwidth; since we’ve determined that client that has the autonomous proxy version of this actor doesn’t need to know about this property (this client is setting this property directly for prediction purposes for example). Another benefit is that for the client not receiving this property, the server won’t step on this client’s local copy.

    Here is a quick glance at the list of conditions currently supported:
    • COND_InitialOnly - This property will only attempt to send on the initial bunch
    • COND_OwnerOnly - This property will only send to the actor's owner
    • COND_SkipOwner - This property send to every connection EXCEPT the owner
    • COND_SimulatedOnly - This property will only send to simulated actors
    • COND_AutonomousOnly - This property will only send to autonomous actors
    • COND_SimulatedOrPhysics - This property will send to simulated OR bRepPhysics actors
    • COND_InitialOrOwner - This property will send on the initial packet, or to the actors owner
    • COND_Custom - This property has no particular condition, but wants the ability to toggle on/off via SetCustomIsActiveOverride

    So far we’ve talked about conditions that are based off of state that is already known. This makes it easy for the engine to make the necessary optimizations while still giving you enough control over property replication.

    But what if this isn’t enough control? There is one more thing to talk about on this subject. There is a macro called DOREPLIFETIME_ACTIVE_OVERRIDE, which gives you full control over when a property does and does not replicate, using any custom condition you want. The one caveat is that this is per actor, NOT per connection. So in other words, it’s not safe to use a state that can change per connection in your custom condition. An example can be seen below.
    void AActor::PreReplication( IRepChangedPropertyTracker & ChangedPropertyTracker )
    {
    DOREPLIFETIME_ACTIVE_OVERRIDE( AActor, ReplicatedMovement, bReplicateMovement );
    }

    The property ReplicatedMovement will now only replicate if bReplicateMovement is true.

    Why not use this macro all the time? There are two main reasons to avoid this method:
    • If custom condition value changes a lot, this can slow things down.
    • You cannot use condition that can change per connection (don’t check RemoteRole here).

    Property replication conditions give a nice balance of control vs. performance. They give the engine the opportunity to optimize the time it takes to check and send the properties for many connections, while still giving the programmer fine grain control over how and when properties replicate.
    ------
    Questions about John's post? Please ask away and let us know what you think below!

    #2
    This is really interesting thank you. Is there anywhere to learn basics of Unreal networking? What is the difference between a simulated actor and a local actor, in particular why would a simulated actor want to not get updates about replicated fields it's using for prediction? Thanks!

    Comment


      #3
      Dear Epic,

      thank you for this information. Moreover, information about e.g. replication for variables inside Blueprints, networking from Blueprints, etc. are very important. What we need, would be a Blueprint way to create an HTTP request with some form data inside (key/values) and be able to receive an answer (also key/value). Afterwards, we must be enabled to execute/process the key/values from the answer.

      Example:
      We send an HTTP request to http://engine.myappname.com:9000 with this data:

      key=username
      value=John


      The answer will be:
      key=x
      value=15.47

      key=y
      value=19.78

      key=z
      value=0.0

      key=message
      value=This is John's home

      Therefore, a Blueprint block is necessary where I can specific the key (by using e.g. a string constant), select the expected type (string, float, int, etc.) and get the related output port.

      Yes, I know this is possible with C++, but a Blueprint way would be better to use. We doing research with virtual worlds and therefore the Blueprint system is more convenient and maintainable for our requirements.

      Comment


        #4
        Thanks for the explanation! I've also been looking at the networking code and I've been quite impressed so far!

        The only thing I'm worried about is how it scales with a very large amount of actors in the same area. I could be horribly wrong, but there seems to be an ActorChannel for every player in the range of every actor, e.g. with 100 players stacked on top of each other, there would be ~10000 ActorChannels being serialised every frame. And this also looks to be single-threaded with nothing else happening in the other cores at that point.

        You mention in your blog post that "we bake in as much information as possible, so we can take advantage of sharing work across many connections for the same set of properties. This saves a lot of computation time." - could you please elaborate on what is shared across many connections and what isn't? Thanks!

        Comment


          #5
          "Is there anywhere to learn basics of Unreal networking?"
          We are working on this now, stay tuned!

          "What is the difference between a simulated actor and a local actor, in particular why would a simulated actor want to not get updates about replicated fields it's using for prediction? Thanks!"
          The two conditions you might be referring to are COND_SimulatedOnly, and COND_AutonomousOnly.

          An autonomous proxy actor is simply an actor that has a role of ROLE_AutonomousProxy. By default, APlayerControllers are actors that will have this flag set, and for machines that don't own that player controller, the role will be downgraded to ROLE_SimulatedProxy. For machines that do own that player controller, it is assumed that a human will be driving the inputs and will be controlling that actor on that machine.

          Look at APawn::GetNetConnection. Then take a look at UActorChannel::ReplicateActor, and notice:

          Code:
          	UNetConnection* OwningConnection = Actor->GetNetConnection();
          	if (OwningConnection == Connection || (OwningConnection != NULL && OwningConnection->IsA(UChildConnection::StaticClass()) && ((UChildConnection*)OwningConnection)->Parent == Connection))
          	{
          		RepFlags.bNetOwner = true;
          	}
          	else
          	{
          		RepFlags.bNetOwner = false;
          	}
          This check is server side, and is what the low level check looks like to determine if you own the actor. A little bit further down, if you don't own the actor, it will be downgraded for your connection:

          Code:
          	// Save out the actor's RemoteRole, and downgrade it if necessary.
          	ENetRole const ActualRemoteRole = Actor->GetRemoteRole();
          	if (ActualRemoteRole == ROLE_AutonomousProxy)
          	{
          		if (!RepFlags.bNetOwner)
          		{
          			Actor->SetAutonomousProxy(false);
          		}
          	}
          So if you use the COND_AutonomousOnly condition, only actors that have a role of ROLE_AutonomousProxy (and not downgraded) will receive these updates. This is implementation dependent, but can be useful when you don't want simulated actors receiving that update. Maybe this property represents some client side prediction correction or something, and only autonomous actors need this update.

          If you used the COND_SimulatedOnly condition, only actors that have a role of ROLE_SimulatedProxy will receive updates. This could be useful when you want to avoid updating actors with a role of ROLE_AutonomousProxy, which might make sense if you are changing that property locally while predicting the movements of that actor, and don't want updates from the server for that property (since you will just overwrite it anyhow).

          "What we need, would be a Blueprint way to create an HTTP request with some form data inside (key/values) and be able to receive an answer (also key/value). Afterwards, we must be enabled to execute/process the key/values from the answer."
          I can't make promises on this particular example, but we definitely intend to expose this type of stuff, and more OSS (online subsystem) related features in the future.

          "but there seems to be an ActorChannel for every player in the range of every actor, e.g. with 100 players stacked on top of each other, there would be ~10000 ActorChannels"
          This would be correct for the server. It would have 100 channels opened for 100 connections (100*100). This is an extreme case that we would eventually like to get better at, but unfortunately haven't been able to get to yet.

          "And this also looks to be single-threaded with nothing else happening in the other cores at that point."
          The current push is to get dedicated servers which are expected to run on a single core with other instances, which wouldn't make sense to run multi-threaded, but this is another wishlist feature we eventually want to get to since we understand that listen servers running on multi-core machines would greatly benefit from this.

          "You mention in your blog post that "we bake in as much information as possible, so we can take advantage of sharing work across many connections for the same set of properties. This saves a lot of computation time." - could you please elaborate on what is shared across many connections and what isn't? "
          Each connection has a list of active properties that want to be checked for replication, considering conditions and active overrides. This is the pre-computed part. We can do stuff like align memory how we need to most efficiently loop over and scan for changes.

          For each connection that will be receiving this actor, we need to loop over these properties, and compare them to the last known state for that connection, and build a change list for the properties that have changed. For connections that are on the same frequency and phase, they can skip this step, and share the change list that was generated from the first connection that did the actual work.

          Hope that clears some things up!

          Comment


            #6
            Thank you, this is great! Please keep the networking education coming. I'm particularly interested in learning about how UE4 performs client-side prediction for physics-based movement. I don't yet understand the source well enough to figure this out on my own.
            Work in Progress: King of Kalimpong
            piinecone.com | youtube | @p11necone

            Comment


              #7
              Hi i have problem when trying to replicate properties.

              Whenever I try to use DOREPLIFETIME compiller fails.
              Code:
              DOREPLIFETIME(AMurnatanPlayerState, TeamNumber);
              I am using shooter example as my reference, this code produce following errors
              Code:
              1>D:\dev\workspace\unrealengine\Murnatan\Source\Murnatan\MurnatanPlayerState.cpp(88): error C2275: 'AMurnatanPlayerState' : illegal use of this type as an expression
              1>D:\dev\workspace\unrealengine\Murnatan\Source\Murnatan\MurnatanPlayerState.cpp(88): error C3861: 'DOREPLIFETIME': identifier not found
              Anyone got an idea why i cannot compile this example?

              Thanks for help

              Comment


                #8
                Hmm, a few things could be happening, but first, make sure you are including:

                #include "Net/UnrealNetwork.h"

                Comment


                  #9
                  Yeah that was it! Thanks!!

                  Now i feel pretty stupid, should notice myself. I am still learning ue4 and c++ and simetimes it can be pretty overhelming O:-)

                  Comment


                    #10
                    Np, we're here to help!

                    Comment


                      #11
                      I am struggling with replication.

                      I get this in the log LogActor:Warning: SetReplicates called on actor 'AIPawn' that is not valid for having its role modified.
                      What is goign on?Maybe I have to activate the pawn's movement somehow?

                      Comment


                        #12
                        I also have a bunch of questions related to replication for the basket ball game I'm developing. All the questions below are in the context of a dedicated server on which 2 players connect as clients. In advance, sorry for the length of this post

                        -- 1 -- Team creation

                        I have a UBBTeam class which holds references to ACharacter (player or AI) which belong to it, and a reference to the opposing team. I create 2 instances of this class and store them in AGameState:

                        Code:
                        UCLASS()
                        class UBBTeam : public UObject
                        {
                        	GENERATED_UCLASS_BODY()
                           
                            void SetOtherTeam( const UBBTeam & other_team ) { OtherTeam = &other_team; }
                            
                            UPROPERTY()
                            TWeakObjectPtr< UBBTeam > OtherTeam;
                        
                            UPROPERTY()
                            TArray< TWeakObjectPtr< ABBCharacter > > PlayerTable;
                        };
                        Code:
                        UCLASS()
                        class ABBGameState : public AGameState
                        {
                        	GENERATED_UCLASS_BODY()
                        
                            UPROPERTY()
                            TArray< UBBTeam* > TeamTable;
                        
                            virtual void PostInitializeComponents() OVERRIDE; 
                        };
                        Code:
                        void ABBGameState::PostInitializeComponents( )
                        {
                            Super::PostInitializeComponents();
                        
                            TeamTable.SetNum( 2 );
                            ScoreTable.SetNum( 2 );
                        
                            for ( int index = 0; index < 2; index++ )
                            {
                                UBBTeam * team = NewObject< UBBTeam >( );
                        
                                TeamTable[ index ] = team;
                            }
                        
                            TeamTable[ 0 ]->SetOtherTeam( *TeamTable[ 1 ] );
                            TeamTable[ 1 ]->SetOtherTeam( *TeamTable[ 0 ] );
                        }
                        The function ABBGameState::PostInitializeComponents is called 3 times: 1 time on the server, and 1 time for each client. Each time, I create both teams, fill TeamTable, and set the opposite team.

                        Is this correct, for each instance of AGameState to have its instances of UBBTeam? Or should I create the teams only on the server, and mark TeamTable as replicated?

                        -- 2 -- Character initialization
                        In my class inheriting from ACharacter, I would like to keep a reference to the UBBTeam this player belongs to, as well as its team mate.

                        Code:
                        class ABBCharacter : public ACharacter
                        {
                            GENERATED_UCLASS_BODY()
                        
                            virtual void PostInitializeComponents() OVERRIDE;
                            
                            TWeakObjectPtr< ABBCharacter > TeamMate;
                            TWeakObjectPtr< UBBTeam > Team;
                        };
                        What is the best place, or when is it best, to set TeamMate and Team? I first thought of PossessedBy(), but this is only called on the server. I also noticed that in the shooter game from Epic, they do some stuff in OnRep_PlayerState. Indeed, for the instances of ABBCharacter on the clients, I have access in this function to the game state and the player state, from which I can set the team and the team mate.

                        But here comes again the same question: should I set the weak object pointers TeamMate and Team of ABBCharacter in PossessedBy when I'm on the server, and mark those 2 properties as replicated, or should I NOT replicate those weak pointers, and set them on the server in PossessedBy and on the clients in OnRep_PlayerState?

                        -- 3 -- Referencing a spawned actor
                        When the match is started, I spawn a ball (ABBBall) in the world, in ABBGameMode::StartMatch. I get the spawned actor and assign it to a property in ABBGameState:

                        Code:
                        UCLASS()
                        class ABBGameState : public AGameState
                        {
                            GENERATED_UCLASS_BODY()
                        
                            UPROPERTY( ReplicatedUsing = OnRep_Ball )
                            TWeakObjectPtr< ABBBall > Ball;
                        }
                        Code:
                        void ABBMatchGameMode::StartMatch()
                        {
                            ABBBall * ball  = GetWorld( )->SpawnActor<ABBBall>( DefaultBallClass, FVector( 0.0f, 0.0f, 600.f ), FRotator( ) );
                        
                            GetGameState< ABBGameState >( )->Ball = ball;
                        
                            Super::StartMatch();
                        }
                        On the server, when Role == ROLE_Authority, the Ball property of ABBGameState is correctly set. I can affirm the Ball property is replicated, as the function ABBGameState::OnRep_Ball is called and Ball != NULL on the clients.

                        I would like to set a weak pointer to this ball in my character class. But whenever I access the game state on the client, the ball property is always NULL! If I replace the type of ABBGameState::Ball from a weak pointer to a regular pointer, I have the same result.

                        Why would the property be set on the server, be replicated, but be null for the clients? Does spawning this actor changes anything from an actor which would be already in the world when the game starts?
                        I have 2 hoop actors which are already present in the level. I have some weak pointers to them in my game state, which I set by iterating in the world actors. Those pointers are declared the same way the ball is declared, and those 2 pointers to the hoops are not null:

                        Code:
                        void ABBGameState::StartMatch()
                        {
                            ScoreTable[ ETeamType::Home ] = 0;
                            ScoreTable[ ETeamType::Road ] = 0;
                        
                            for ( TActorIterator<ABBHoop> iterator( GetWorld() ); iterator; ++iterator )
                            {
                                HoopTable[ iterator->Side ] = *iterator;
                            }
                        }
                        Again, in the same places in ABBCharacter where I access to the game state, HoopTable contains valid pointers to the hoop actors whereas Ball remains NULL.

                        -- 4 -- Reacting to physics events

                        I wrote some code which detects when the ball overlaps with something. And if this something is a pawn, I want to attach the ball to that pawn, to change the color of a decal under that pawn, and I want all other pawns to be informed that the ball is grabbed by a new character.

                        In the ball class, I created an event on which every player subscribes, and which informs them of the new "owner" of the ball. I have also overriden the ReceiveActorBeginOverlap function:

                        Code:
                        class ABBBall
                        {
                            DECLARE_EVENT_OneParam( ABBBall, FBallOwnerChangedEvent, const ABBCharacter * )
                            FBallOwnerChangedEvent & OnBallOwnerChanged() { return OwnerChangedEvent; }
                        
                            virtual void ReceiveActorBeginOverlap(class AActor* OtherActor) OVERRIDE;
                        
                            TWeakObjectPtr<class ABBCharacter> CharacterOwner;
                        
                        };
                        Code:
                        void ABBBall::ReceiveActorBeginOverlap(
                            class AActor* OtherActor
                            )
                        {
                            ABBCharacter
                                * character = Cast< ABBCharacter >( OtherActor );
                        
                            if ( character != NULL )
                            {
                                SetOwner( *character );
                            }
                        }
                        
                        void ABBBall::SetOwner(
                            const class ABBCharacter & owner
                            )
                        {
                            CharacterOwner = &owner;
                            
                            MeshComponent->SetSimulatePhysics( it_is_enabled );
                            MeshComponent->bGenerateOverlapEvents = it_is_enabled;
                        
                            AttachRootComponentTo( CharacterOwner->Mesh, LOCAL_RightHandSocketName, EAttachLocation::SnapToTarget );
                        
                            OwnerChangedEvent.Broadcast( &owner );
                        }
                        My problem here is that when I move a pawn on the ball, the ReceiveActorBeginOverlap function is correctly called, but both on the clients and the server. I think that this is a gameplay decision which should be run on the server, as I update CharacterOwner . But if I execute SetOwner on the server only, the ball attachment won't take place on the clients.
                        Furthermore, where should the call to OwnerChangedEvent.Broadcast should take place? On the server? On the client? Both?

                        Could you please explain me how to solve this issue?


                        I finally reached the end of this post. Apparently a replication article is in the pipe, so maybe some of my interrogations will be answered with that document. But in the meantime, as I don't know when this document will be released, that would be nice if you could help me a bit

                        Thanks in advance
                        My game dev blog : http://www.emidee.net/

                        Comment


                          #13
                          Originally posted by Crystal Voliva View Post

                          In the implementation of the actor class, you need to implement the GetLifetimeReplicatedProps function:

                          void AActor::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
                          {
                          DOREPLIFETIME( AActor, Owner );
                          }
                          Just to point out something I missed here initially as it may not be immediately obvious...or maybe it's just Friday afternoon and my brain already thinks it's the weekend...

                          Anyway, in most cases I imagine you'll want to call the superclass here so any superclass replicated values get updated So, something like:

                          Code:
                          void AActor::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
                          {
                                  Super::GetLifetimeReplicatedProps(OutLifetimeProps);
                          
                                  DOREPLIFETIME( AActor, Owner);
                          }
                          Hope that might help someone who hit the same issue I did.

                          Cheers,

                          Greg
                          Last edited by GregBooker; 05-23-2014, 11:20 AM. Reason: Removing heinous crime against the English language

                          Comment


                            #14
                            "The one caveat is that this is per actor, NOT per connection."
                            Does that's mean we can't check to who the property will be sent?

                            Let's say I want to send the life value to all my team member but not others, how can I do this? RPC call?

                            Thanks,
                            Last edited by Elvince; 11-12-2014, 12:01 PM.

                            Comment


                              #15
                              I have a related question about this

                              I copied some stuff from ShooterGame into my game.

                              Code:
                              PreReplication function:
                              // Only replicate this property for a short duration after it changes so join in progress players don't get spammed with fx when joining late
                              DOREPLIFETIME_ACTIVE_OVERRIDE(AMyCharacter, LastTakeHitInfo, GetWorld() && GetWorld()->GetTimeSeconds() < LastTakeHitTimeTimeout);
                              
                              and
                              
                              GetLifetimeReplicatedProps function:
                              DOREPLIFETIME_CONDITION(AMyCharacter, LastTakeHitInfo, COND_Custom);
                              this worked fine, but now I'm moving all of my code into Components (to make things modular)

                              Components replicate fine with DOREPLIFETIME, but I can't seem to use COND_Custom at all. it says PreReplication override did not override any base class methods. is this not supported?
                              Follow me on Twitter!
                              Developer of Elium - Prison Escape

                              Comment

                              Working...
                              X