March 31, 2014

Network Tips and Tricks

By John Pollard

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.

Now that we’ve given you a few networking tips and tricks, do you have any questions? Be sure to join us over in the forums to continue the discussion!