<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://vorixo.github.io/devtricks/feed.xml" rel="self" type="application/atom+xml" /><link href="https://vorixo.github.io/devtricks/" rel="alternate" type="text/html" /><updated>2026-09-23T19:01:18+00:00</updated><id>https://vorixo.github.io/devtricks/feed.xml</id><title type="html">Devtricks</title><subtitle>Programming and game development blog</subtitle><author><name>Alvaro Jover-Alvarez</name></author><entry><title type="html">Iris - Network serializers</title><link href="https://vorixo.github.io/devtricks/iris-netserializers/" rel="alternate" type="text/html" title="Iris - Network serializers" /><published>2026-02-08T00:00:00+00:00</published><updated>2026-02-08T00:00:00+00:00</updated><id>https://vorixo.github.io/devtricks/iris-netserializers</id><content type="html" xml:base="https://vorixo.github.io/devtricks/iris-netserializers/"><![CDATA[<p>In this article we will explore Iris’ serialization by implementing a custom network serializer.</p>

<h1 id="introduction">Introduction</h1>

<p>Unreal Engine’s legacy replication system offers a very simplistic way to support replication to non-POD properties by implementing <code class="language-plaintext highlighter-rouge">NetSerialize</code> or <code class="language-plaintext highlighter-rouge">NetDeltaSerialize</code>, depending on whether <a href="https://vorixo.github.io/devtricks/atomicity/">atomic or differential replication</a> is needed. In this model, serialization logic is executed as part of building replication data and may be re-run per connection, including when sending initial state to newly connected players.</p>

<p><a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/introduction-to-iris-in-unreal-engine">Iris</a> serialization differs in that it <strong>Quantizes</strong> and stores replicated state internally, allowing work to be reused across relevant connections. By centralizing and sharing this work instead of performing it separately per client, Iris reduces per-connection overhead thus, improving scalability.</p>

<p>However, implementing custom serializers in Iris is more involved than in the legacy system, as there are many moving pieces one must understand before writing one.</p>

<p>As Epic developer Peter Engstrom explained (<a href="https://forums.unrealengine.com/t/iris-why-are-netserializers-so-complex/2585061">source</a>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Generally speaking we would like for people to not having to 
implement serializer at all and cover the most common use cases.

...

...not implementing one if one can avoid it can be a big 
maintenance win.

- Peter Engstrom
</code></pre></div></div>

<p>That said, there are still scenarios where a custom serializer is necessary - such as edge cases not covered natively or for maximum bandwidth optimization (e.g., the engine’s own <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Engine/Private/Engine/HitResultNetSerializer.cpp"><code class="language-plaintext highlighter-rouge">FHitResult</code> custom serialization</a>). In this article, we’ll explore the key pieces involved and implement a serializer for a simple struct.</p>

<h1 id="network-serialization-in-iris">Network Serialization in Iris</h1>

<p>In the Iris replication system, the serialization process follows a very specific pipeline to ensure data is compressed, compared, and applied to our target. Here is the sequence of events in the order they occur:</p>

<ol>
  <li><strong>Quantize</strong>: Converts high-precision “Source” data (like a 32-bit float) into a compressed “Internal” representation (like a scaled integer).</li>
  <li><strong>IsEqual</strong>: Called during dirty-checking to determine if quantized state changed since last replication. If true, serialization is skipped entirely.</li>
  <li><strong>Serialize</strong>: Takes the quantized “Internal” state and writes it into the bitstream using a <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Net/Iris/Public/Iris/Serialization/NetBitStreamWriter.h"><code class="language-plaintext highlighter-rouge">FNetBitStreamWriter</code></a>.</li>
  <li><strong>Deserialize</strong>: Reads the bits from the <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Net/Iris/Public/Iris/Serialization/NetBitStreamReader.h"><code class="language-plaintext highlighter-rouge">FNetBitStreamReader</code></a> and reconstructs the “Internal” (quantized) state on the receiving end.</li>
  <li><strong>Dequantize</strong>: Reverses the quantization process, converting the quantized data back into the original “External” types (like a <code class="language-plaintext highlighter-rouge">FVector</code>).</li>
  <li><strong>Apply</strong>: Serializers that want to be selective about which members to modify in the target instance when applying state should implement <code class="language-plaintext highlighter-rouge">Apply</code> where the serializer is responsible for setting the members of the target instance.</li>
</ol>

<p>So… <strong>what is sent over the network?</strong> You send over the network exactly and precisely <strong>what you write in your <code class="language-plaintext highlighter-rouge">FNetBitStreamWriter</code></strong> within your <code class="language-plaintext highlighter-rouge">Serialize</code> function.</p>

<p class="notice--info"><strong>Note:</strong> Serialize and Deserialize are the only strictly required functions. Quantize and Dequantize are optional unless your “Source” data (like a class) must be converted into a simpler “Internal” POD type for serialization. Apply is optional and only used if you need custom logic to write the final value back to the target.</p>

<h1 id="serializing-a-struct-in-iris">Serializing a struct in Iris</h1>

<p>Before we get started I recommend you opening <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Net/Iris/Public/Iris/Serialization/NetSerializer.h">NetSerializer.h</a> to have an inline documentation at hand.</p>

<p>The struct we will be serializing in this example is the following one:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">USTRUCT</span><span class="p">(</span><span class="n">BlueprintType</span><span class="p">)</span>
<span class="k">struct</span> <span class="nc">FMyStruct</span>
<span class="p">{</span>
	<span class="n">GENERATED_BODY</span><span class="p">()</span>
	
	<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">,</span> <span class="n">BlueprintReadOnly</span><span class="p">)</span>
	<span class="n">FVector</span> <span class="n">StartLocation</span> <span class="o">=</span> <span class="n">FVector</span><span class="o">::</span><span class="n">ZeroVector</span><span class="p">;</span>

	<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">,</span> <span class="n">BlueprintReadOnly</span><span class="p">)</span>
	<span class="n">FVector</span> <span class="n">ImpactPoint</span> <span class="o">=</span> <span class="n">FVector</span><span class="o">::</span><span class="n">ZeroVector</span><span class="p">;</span>

	<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">BlueprintReadOnly</span><span class="p">)</span>
	<span class="kt">bool</span> <span class="n">bHasImpactPoint</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>

	<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">BlueprintReadOnly</span><span class="p">,</span> <span class="n">NotReplicated</span><span class="p">)</span>
	<span class="kt">float</span> <span class="n">Distance</span><span class="p">;</span>
<span class="p">};</span></code></pre></figure>

<p>Our serialization intentions are as follows:</p>

<ul>
  <li>Serialize always <code class="language-plaintext highlighter-rouge">StartLocation</code> and <code class="language-plaintext highlighter-rouge">bHasImpactPoint</code></li>
  <li>If <code class="language-plaintext highlighter-rouge">bHasImpactPoint</code>, serialize <code class="language-plaintext highlighter-rouge">ImpactPoint</code>, but only if <code class="language-plaintext highlighter-rouge">StartLocation</code> has a different value (thus saving some bits).</li>
  <li>Compute <code class="language-plaintext highlighter-rouge">Distance</code> on the receiving end.</li>
</ul>

<p>Note that we desire to serialize the struct with the intention of sending around on RPCs and replicate it.</p>

<h2 id="before-we-start">Before we start</h2>

<p>Remember that the intention of this article is purely informative and, as I mentioned in the introduction, it is important to understand the burden and time consumption involved in maintaining custom Iris serializers. In most cases, it is preferable to design within Iris’ constraints rather than introduce a custom serializer. Before writing one, carefully consider whether your use case really needs it - and most importantly (specially if you are new to Iris), don’t guess performance: profile.</p>

<p>I decided to serialize <code class="language-plaintext highlighter-rouge">FMyStruct</code> for this tutorial since it is a <strong>non-polymorphyc</strong> struct that doesn’t contain pointer references or dynamic state (e.g. <code class="language-plaintext highlighter-rouge">FString</code> or <code class="language-plaintext highlighter-rouge">TArray</code>), so in my opinion it is the perfect candidate for an introduction. With that said, Iris supports <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Net/Iris/Public/Iris/Serialization/PolymorphicNetSerializerImpl.h">polymorphic serializers</a> and a series of tools to enable dynamic serialization.</p>

<p>Since the documentation is scarce at the time of writing, I recommend the reader to look at the <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Net/Iris/Public/Iris/Serialization"><code class="language-plaintext highlighter-rouge">Iris\Serialization\</code></a> directory in GithHub, specially I suggest paying attention to the following files for what concerns this article:</p>

<ul>
  <li><a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Net/Iris/Public/Iris/Serialization/NetSerializer.h"><code class="language-plaintext highlighter-rouge">NetSerializer.h</code></a>: Here you will find the definition and documentation of each variable, function, class and struct that can be part of your network serializers. Contains very valuable information of how Iris’ serializers work.</li>
  <li><a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Net/Iris/Public/Iris/Serialization/PolymorphicNetSerializerImpl.h"><code class="language-plaintext highlighter-rouge">PolymorphicNetSerializerImpl.h</code></a>: In this header you will find the definition and documentation for implementing polymorphic struct serializers (such as <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/Serialization/GameplayEffectContextHandleNetSerializer.cpp"><code class="language-plaintext highlighter-rouge">FGameplayEffectContextHandle</code></a>).</li>
  <li><a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Net/Iris/Public/Iris/Serialization/NetSerializerArrayStorage.h"><code class="language-plaintext highlighter-rouge">NetSerializerArrayStorage.h</code></a>: Main hub for dynamic storage serialization.</li>
  <li><a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Net/Iris/Public/Iris/ReplicationState/PropertyNetSerializerInfoRegistry.h"><code class="language-plaintext highlighter-rouge">Iris/ReplicationState/PropertyNetSerializerInfoRegistry.h</code></a>: Here you will find a series of useful macros to register/forward your net serializers.</li>
</ul>

<p class="notice--warning">Pay attention to the comments within the showcased code sections, they provide important context!</p>

<h2 id="declaring-the-serializer">Declaring the serializer</h2>

<p>The first step to do when declaring a serializer is figuring out which serializer config we need, in this case, since our struct isn’t polymorphic, we use <code class="language-plaintext highlighter-rouge">FNetSerializerConfig</code>:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">Header</span> <span class="n">file</span> <span class="p">(.</span><span class="n">h</span><span class="p">)</span><span class="o">:</span>

<span class="n">USTRUCT</span><span class="p">()</span>
<span class="k">struct</span> <span class="nc">FMyStructNetSerializerConfig</span> <span class="o">:</span> <span class="k">public</span> <span class="n">FNetSerializerConfig</span>
<span class="p">{</span>
	<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="p">};</span>

<span class="k">namespace</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span>
<span class="p">{</span>
	<span class="n">UE_NET_DECLARE_SERIALIZER</span><span class="p">(</span><span class="n">FMyStructNetSerializer</span><span class="p">,</span> <span class="n">MYGAME_API</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p class="notice--info"><strong>Note:</strong> Polymorphic structs require to use <code class="language-plaintext highlighter-rouge">FPolymorphicStructNetSerializerConfig</code>.</p>

<h2 id="registering-the-serializer">Registering the serializer</h2>

<p>If we want replication for our type to occur through our custom Iris net serializer, we need to register it to the <code class="language-plaintext highlighter-rouge">FPropertyNetSerializerInfoRegistry</code> via some macros:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">Implementation</span> <span class="n">file</span> <span class="p">(.</span><span class="n">cpp</span><span class="p">)</span><span class="o">:</span>

<span class="k">namespace</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span>
<span class="p">{</span>
	<span class="k">struct</span> <span class="nc">FMyStructNetSerializer</span>
	<span class="p">{</span>
		<span class="p">...</span>

	<span class="nl">private:</span>
		<span class="k">class</span> <span class="nc">FRegistryDelegates</span> <span class="k">final</span> <span class="o">:</span> <span class="k">public</span> <span class="n">FNetSerializerRegistryDelegates</span>
		<span class="p">{</span>
		<span class="nl">public:</span>
			<span class="k">virtual</span> <span class="o">~</span><span class="n">FRegistryDelegates</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
			<span class="k">virtual</span> <span class="kt">void</span> <span class="n">OnPreFreezeNetSerializerRegistry</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
		<span class="p">};</span>

		<span class="k">static</span> <span class="n">FRegistryDelegates</span> <span class="n">RegistryDelegates</span><span class="p">;</span>
	<span class="p">};</span>

	<span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">FRegistryDelegates</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">RegistryDelegates</span><span class="p">;</span>

	<span class="p">...</span>
<span class="p">}</span>

<span class="k">namespace</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span>
<span class="p">{</span>
	<span class="k">static</span> <span class="k">const</span> <span class="n">FName</span> <span class="n">PropertyNetSerializerRegistry_NAME_MyStruct</span><span class="p">(</span><span class="s">"MyStruct"</span><span class="p">);</span>
	<span class="n">UE_NET_IMPLEMENT_NAMED_STRUCT_NETSERIALIZER_INFO</span><span class="p">(</span><span class="n">PropertyNetSerializerRegistry_NAME_MyStruct</span><span class="p">,</span> <span class="n">FMyStructNetSerializer</span><span class="p">);</span>

	<span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">FRegistryDelegates</span><span class="o">::~</span><span class="n">FRegistryDelegates</span><span class="p">()</span>
	<span class="p">{</span>
		<span class="n">UE_NET_UNREGISTER_NETSERIALIZER_INFO</span><span class="p">(</span><span class="n">PropertyNetSerializerRegistry_NAME_MyStruct</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">FRegistryDelegates</span><span class="o">::</span><span class="n">OnPreFreezeNetSerializerRegistry</span><span class="p">()</span>
	<span class="p">{</span>
		<span class="n">UE_NET_REGISTER_NETSERIALIZER_INFO</span><span class="p">(</span><span class="n">PropertyNetSerializerRegistry_NAME_MyStruct</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>In the case we are inheriting our struct from one that has already a net serializer implemented, we can forward* their serialization.</p>

<p class="notice--info"><strong>Note:</strong> Net Serializer forwarding allows you to reuse an existing serializer for a different type. For instance, <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Samples/Games/Lyra/Source/LyraGame/AbilitySystem/LyraGameplayEffectContext.cpp"><code class="language-plaintext highlighter-rouge">LyraGameplayEffectContext.cpp</code></a> uses <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/Serialization/GameplayEffectContextNetSerializer.cpp"><code class="language-plaintext highlighter-rouge">FGameplayEffectContext</code></a> for serialization via the <code class="language-plaintext highlighter-rouge">UE_NET_IMPLEMENT_FORWARDING_NETSERIALIZER_AND_REGISTRY_DELEGATES</code> macro. The engine also offers another utility to forward serialization of a Struct to a <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Net/Iris/Private/Iris/Serialization/InternalNetSerializers.h">last resort net serializer</a> via the <code class="language-plaintext highlighter-rouge">UE_NET_IMPLEMENT_NAMED_STRUCT_LASTRESORT_NETSERIALIZER_AND_REGISTRY_DELEGATES</code> macro (don’t use unless extrictly necessary).</p>

<h2 id="implementing-the-serializer">Implementing the serializer</h2>

<p>Now that we’ve let Iris know it should use our serializer, let’s fill the gaps, we’ll start by defining our serializer struct:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">Implementation</span> <span class="n">file</span> <span class="p">(.</span><span class="n">cpp</span><span class="p">)</span><span class="o">:</span>

<span class="k">namespace</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span>
<span class="p">{</span>
	<span class="k">struct</span> <span class="nc">FMyStructNetSerializer</span>
	<span class="p">{</span>
		<span class="k">static</span> <span class="k">const</span> <span class="n">uint32</span> <span class="n">Version</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
		
		<span class="cm">/** These flags help to define the amount of data we'll need to replicate for a specific event. */</span>
		<span class="k">enum</span> <span class="n">EReplicationFlags</span> <span class="o">:</span> <span class="n">uint8</span>
		<span class="p">{</span>
			<span class="n">HasImpact</span> <span class="o">=</span> <span class="mi">1U</span><span class="p">,</span>
			<span class="n">ImpactDifferentStart</span> <span class="o">=</span> <span class="mi">2U</span><span class="p">,</span>
		<span class="p">};</span>

		<span class="cm">/** Number of flags in the above enum. */</span>
		<span class="k">static</span> <span class="k">constexpr</span> <span class="n">uint32</span> <span class="n">ReplicatedFlagCount</span> <span class="o">=</span> <span class="mi">2U</span><span class="p">;</span>
		
		<span class="cm">/** 
		* Quantized storage representing our type. Sizes are determined by type's QuantizeType.
		* this is not the amount of data we send around
		*/</span>
		<span class="k">struct</span> <span class="nc">FQuantizedType</span>
		<span class="p">{</span>
			<span class="n">uint64</span> <span class="n">StartLocation</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
			<span class="n">uint64</span> <span class="n">ImpactPoint</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
			<span class="n">uint8</span> <span class="n">ReplicationFlags</span><span class="p">;</span>
		<span class="p">};</span>

		<span class="cm">/**
		* Needed in order to calculate external state size and alignment
		* and provide default implementations of some functions.
		*/</span>
		<span class="k">typedef</span> <span class="n">FMyStruct</span> <span class="n">SourceType</span><span class="p">;</span>

		<span class="cm">/**
		* A typedef for the QuantizedType is optional unless the SourceType isn't POD. Assumed to be SourceType if not specified.
		* The QuantizedType needs to be POD.
		*/</span>
		<span class="k">typedef</span> <span class="n">FQuantizedType</span> <span class="n">QuantizedType</span><span class="p">;</span>
		
		<span class="k">typedef</span> <span class="n">FMyStructNetSerializerConfig</span> <span class="n">ConfigType</span><span class="p">;</span>

		<span class="cm">/** DefaultConfig is optional but highly recommended as the serializer can then be used without requiring special configuration setup. */</span>
		<span class="k">static</span> <span class="k">const</span> <span class="n">ConfigType</span> <span class="n">DefaultConfig</span><span class="p">;</span>
		
		<span class="k">static</span> <span class="kt">void</span> <span class="n">Serialize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetSerializeArgs</span><span class="o">&amp;</span><span class="p">);</span>
		<span class="k">static</span> <span class="kt">void</span> <span class="n">Deserialize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetDeserializeArgs</span><span class="o">&amp;</span><span class="p">);</span>
		<span class="k">static</span> <span class="kt">void</span> <span class="n">Quantize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetQuantizeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">);</span>
		<span class="k">static</span> <span class="kt">void</span> <span class="n">Dequantize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetDequantizeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">);</span>		
		<span class="k">static</span> <span class="kt">bool</span> <span class="n">IsEqual</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetIsEqualArgs</span><span class="o">&amp;</span><span class="p">);</span>
		<span class="k">static</span> <span class="kt">void</span> <span class="n">Apply</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetApplyArgs</span><span class="o">&amp;</span><span class="p">);</span>

	<span class="nl">private:</span>
		<span class="cm">/** Registering our Net Serializer */</span>
		<span class="k">class</span> <span class="nc">FRegistryDelegates</span> <span class="k">final</span> <span class="o">:</span> <span class="k">public</span> <span class="n">FNetSerializerRegistryDelegates</span>
		<span class="p">{</span>
		<span class="nl">public:</span>
			<span class="k">virtual</span> <span class="o">~</span><span class="n">FRegistryDelegates</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
			<span class="k">virtual</span> <span class="kt">void</span> <span class="n">OnPreFreezeNetSerializerRegistry</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
		<span class="p">};</span>

		<span class="k">static</span> <span class="n">FRegistryDelegates</span> <span class="n">RegistryDelegates</span><span class="p">;</span>
	<span class="p">};</span>

	<span class="k">const</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">ConfigType</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">DefaultConfig</span><span class="p">;</span>
	<span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">FRegistryDelegates</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">RegistryDelegates</span><span class="p">;</span>
	
	<span class="cm">/** Macro that Constructs our Net Serializer and provides Iris our struct's size and alignment. */</span>
	<span class="n">UE_NET_IMPLEMENT_SERIALIZER</span><span class="p">(</span><span class="n">FMyStructNetSerializer</span><span class="p">);</span>

	<span class="p">...</span>
<span class="p">}</span></code></pre></figure>

<p>Once it’s defined we can start implementing each one of our methods immediately below <code class="language-plaintext highlighter-rouge">UE_NET_IMPLEMENT_SERIALIZER(FMyStructNetSerializer);</code>. For that, we’ll go in the order of operations defined in the above section.</p>

<h3 id="1-quantize">1. Quantize</h3>

<p>First, we Quantize our data:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">Implementation</span> <span class="nf">file</span> <span class="p">(.</span><span class="n">cpp</span><span class="p">)</span><span class="o">:</span>

<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">Quantize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetQuantizeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">Source</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source</span><span class="p">);</span>
	<span class="n">QuantizedType</span> <span class="n">TempValue</span> <span class="o">=</span> <span class="p">{};</span>

	<span class="c1">// We set our replication flags based on "Source" state.</span>
	<span class="n">uint8</span> <span class="n">ReplicationFlags</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
	<span class="n">ReplicationFlags</span> <span class="o">|=</span> <span class="n">Source</span><span class="p">.</span><span class="n">bHasImpactPoint</span> <span class="o">?</span> <span class="n">EReplicationFlags</span><span class="o">::</span><span class="n">HasImpact</span> <span class="o">:</span> <span class="mi">0U</span><span class="p">;</span>
	<span class="n">ReplicationFlags</span> <span class="o">|=</span> <span class="n">Source</span><span class="p">.</span><span class="n">StartLocation</span> <span class="o">!=</span> <span class="n">Source</span><span class="p">.</span><span class="n">ImpactPoint</span> <span class="o">?</span> <span class="n">EReplicationFlags</span><span class="o">::</span><span class="n">ImpactDifferentStart</span> <span class="o">:</span> <span class="mi">0U</span><span class="p">;</span>

	<span class="n">TempValue</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">=</span> <span class="n">ReplicationFlags</span><span class="p">;</span>

	<span class="c1">// Start location quantize</span>
	<span class="p">{</span>
		<span class="c1">// We use the FVectorNetQuantizeNetSerializer to Quantize the StartLocation vector.</span>
		<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

		<span class="n">FNetQuantizeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Source</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">);</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TempValue</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
		<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Quantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="c1">// Impact point quantize</span>
	<span class="k">if</span> <span class="p">((</span><span class="n">TempValue</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">HasImpact</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">TempValue</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">ImpactDifferentStart</span><span class="p">))</span>
	<span class="p">{</span>
		<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

		<span class="n">FNetQuantizeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Source</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">);</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TempValue</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
		<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Quantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="c1">// Finally assign by ref our quantized data to target</span>
	<span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">Target</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Target</span><span class="p">);</span>
	<span class="n">Target</span> <span class="o">=</span> <span class="n">TempValue</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>In this step, we set our <code class="language-plaintext highlighter-rouge">ReplicationFlags</code> based on the <code class="language-plaintext highlighter-rouge">Source</code> state. If <code class="language-plaintext highlighter-rouge">bHasImpactPoint</code> is false, we skip both the quantization and transmission of <code class="language-plaintext highlighter-rouge">ImpactPoint</code>. Similarly, we compare <code class="language-plaintext highlighter-rouge">ImpactPoint</code> to <code class="language-plaintext highlighter-rouge">StartLocation</code>; if they are identical, we avoid sending <code class="language-plaintext highlighter-rouge">ImpactPoint</code> over the network, as we can simply use the <code class="language-plaintext highlighter-rouge">StartLocation</code> value we’ve already provided.</p>

<p>The remainder of the implementation is syntax sugar, primarily acting as a wrapper to forward the specialized Quantize calls to the appropriate sub-serializers.</p>

<p class="notice--info"><strong>Note:</strong> Further Quantization is possible in <code class="language-plaintext highlighter-rouge">FVector</code>, see: <code class="language-plaintext highlighter-rouge">FVectorNetQuantize10NetSerializer</code>, <code class="language-plaintext highlighter-rouge">FVectorNetQuantize100NetSerializer</code> in <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Net/Iris/Public/Iris/Serialization/PackedVectorNetSerializers.h"><code class="language-plaintext highlighter-rouge">PackedVectorNetSerializers.h</code></a>.</p>

<h3 id="2-isequal">2. IsEqual</h3>

<p>This function is a requirement if your <code class="language-plaintext highlighter-rouge">FNetSerializer</code> has a <code class="language-plaintext highlighter-rouge">Quantize</code> function. <code class="language-plaintext highlighter-rouge">IsEqual</code> job is to compare the Quantized (internal) state of a property against its previously cached version to see if anything has actually changed.</p>

<p>If <code class="language-plaintext highlighter-rouge">IsEqual</code> returns true, Iris assumes the data is identical and completely skips serialization, saving you from sending a redundant packet. Note that you have to implement both branches, when the data is quantized, and dequantized.</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">Implementation</span> <span class="nf">file</span> <span class="p">(.</span><span class="n">cpp</span><span class="p">)</span><span class="o">:</span>
  
<span class="kt">bool</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">IsEqual</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetIsEqualArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">bStateIsQuantized</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="c1">// If the data is quantized we can simply Memcmp it</span>
		<span class="k">const</span> <span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">QuantizedValue0</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source0</span><span class="p">);</span>
		<span class="k">const</span> <span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">QuantizedValue1</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source1</span><span class="p">);</span>
		<span class="k">return</span> <span class="n">FPlatformMemory</span><span class="o">::</span><span class="n">Memcmp</span><span class="p">(</span><span class="o">&amp;</span><span class="n">QuantizedValue0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">QuantizedValue1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">QuantizedType</span><span class="p">))</span> <span class="o">==</span> <span class="mi">0</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="k">else</span>
	<span class="p">{</span>
		<span class="k">const</span> <span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">SourceValue0</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source0</span><span class="p">);</span>
		<span class="k">const</span> <span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">SourceValue1</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source1</span><span class="p">);</span>

		<span class="k">if</span> <span class="p">(</span><span class="n">SourceValue0</span><span class="p">.</span><span class="n">bHasImpactPoint</span> <span class="o">!=</span> <span class="n">SourceValue1</span><span class="p">.</span><span class="n">bHasImpactPoint</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
		<span class="p">}</span>
		
		<span class="cm">/**
		* When the data is dequantize it, we quantize it again to compare it
		* This is a bit expensive, so bear in mind, you could also perform
		* a normal == comparison.
		*/</span>
		<span class="n">QuantizedType</span> <span class="n">QuantizedValue0</span> <span class="o">=</span> <span class="p">{};</span>
		<span class="n">QuantizedType</span> <span class="n">QuantizedValue1</span> <span class="o">=</span> <span class="p">{};</span>

		<span class="n">FNetQuantizeArgs</span> <span class="n">QuantizeArgs</span> <span class="o">=</span> <span class="p">{};</span>
		<span class="n">QuantizeArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">Args</span><span class="p">.</span><span class="n">NetSerializerConfig</span><span class="p">;</span>

		<span class="n">QuantizeArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source0</span><span class="p">);</span>
		<span class="n">QuantizeArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">QuantizedValue0</span><span class="p">);</span>
		<span class="n">Quantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">QuantizeArgs</span><span class="p">);</span>

		<span class="n">QuantizeArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source1</span><span class="p">);</span>
		<span class="n">QuantizeArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">QuantizedValue1</span><span class="p">);</span>
		<span class="n">Quantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">QuantizeArgs</span><span class="p">);</span>

		<span class="k">return</span> <span class="n">FPlatformMemory</span><span class="o">::</span><span class="n">Memcmp</span><span class="p">(</span><span class="o">&amp;</span><span class="n">QuantizedValue0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">QuantizedValue1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">QuantizedType</span><span class="p">))</span> <span class="o">==</span> <span class="mi">0</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h3 id="3-serialize">3. Serialize</h3>

<p>The next step is to send the quantized data over the network:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">Implementation</span> <span class="nf">file</span> <span class="p">(.</span><span class="n">cpp</span><span class="p">)</span><span class="o">:</span>

<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">Serialize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetSerializeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
<span class="p">{</span>
	<span class="c1">// Acquire the quantized data.</span>
	<span class="k">const</span> <span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">Value</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source</span><span class="p">);</span>
	
	<span class="c1">// This is where we write to send fluff through the network</span>
	<span class="n">FNetBitStreamWriter</span><span class="o">*</span> <span class="n">Writer</span> <span class="o">=</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetBitStreamWriter</span><span class="p">();</span>

	<span class="c1">// We send our replication flags (using just 2 bits)</span>
	<span class="k">const</span> <span class="n">uint8</span> <span class="n">ReplicationFlags</span> <span class="o">=</span> <span class="n">Value</span><span class="p">.</span><span class="n">ReplicationFlags</span><span class="p">;</span>
	<span class="n">Writer</span><span class="o">-&gt;</span><span class="n">WriteBits</span><span class="p">(</span><span class="n">ReplicationFlags</span><span class="p">,</span> <span class="n">ReplicatedFlagCount</span><span class="p">);</span>

	<span class="c1">// Start location</span>
	<span class="p">{</span>
		<span class="n">UE_NET_TRACE_SCOPE</span><span class="p">(</span><span class="n">StartLocation</span><span class="p">,</span> <span class="o">*</span><span class="n">Writer</span><span class="p">,</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetTraceCollector</span><span class="p">(),</span> <span class="n">ENetTraceVerbosity</span><span class="o">::</span><span class="n">Verbose</span><span class="p">);</span>
		<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

		<span class="n">FNetSerializeArgs</span> <span class="n">VectorNetSerializeArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
		<span class="n">VectorNetSerializeArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Value</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
		<span class="n">VectorNetSerializeArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
		<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Serialize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">VectorNetSerializeArgs</span><span class="p">);</span>
	<span class="p">}</span>
	
	<span class="c1">// Impact point</span>
	<span class="k">if</span> <span class="p">((</span><span class="n">Value</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">HasImpact</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">Value</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">ImpactDifferentStart</span><span class="p">))</span>
	<span class="p">{</span>
		<span class="n">UE_NET_TRACE_SCOPE</span><span class="p">(</span><span class="n">ImpactPoint</span><span class="p">,</span> <span class="o">*</span><span class="n">Writer</span><span class="p">,</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetTraceCollector</span><span class="p">(),</span> <span class="n">ENetTraceVerbosity</span><span class="o">::</span><span class="n">Verbose</span><span class="p">);</span>
		<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

		<span class="n">FNetSerializeArgs</span> <span class="n">VectorNetSerializeArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
		<span class="n">VectorNetSerializeArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Value</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
		<span class="n">VectorNetSerializeArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
		<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Serialize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">VectorNetSerializeArgs</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>As seen above, we write our replication flags directly to the <code class="language-plaintext highlighter-rouge">FNetBitStreamWriter</code> using only 2 bits. The other two properties simply forward their serialization logic to the <code class="language-plaintext highlighter-rouge">FVectorNetQuantizeNetSerializer</code>. By checking the flags first, we ensure <code class="language-plaintext highlighter-rouge">ImpactPoint</code> bits are only written when necessary, keeping the packet small.</p>

<h3 id="4-deserialize">4. Deserialize</h3>

<p>Third, we deserialize the quantized data:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">Implementation</span> <span class="nf">file</span> <span class="p">(.</span><span class="n">cpp</span><span class="p">)</span><span class="o">:</span>

<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">Deserialize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetDeserializeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">Target</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Target</span><span class="p">);</span>

	<span class="c1">// This is where we read the quantized data from</span>
	<span class="n">FNetBitStreamReader</span><span class="o">*</span> <span class="n">Reader</span> <span class="o">=</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetBitStreamReader</span><span class="p">();</span>
	
	<span class="c1">// We read the flags (2 bits) and store them in Target</span>
	<span class="n">Target</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">=</span> <span class="n">Reader</span><span class="o">-&gt;</span><span class="n">ReadBits</span><span class="p">(</span><span class="n">ReplicatedFlagCount</span><span class="p">);</span>

	<span class="c1">// Start location</span>
	<span class="p">{</span>
		<span class="n">UE_NET_TRACE_SCOPE</span><span class="p">(</span><span class="n">StartLocation</span><span class="p">,</span> <span class="o">*</span><span class="n">Reader</span><span class="p">,</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetTraceCollector</span><span class="p">(),</span> <span class="n">ENetTraceVerbosity</span><span class="o">::</span><span class="n">Verbose</span><span class="p">);</span>
		<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

		<span class="n">FNetDeserializeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Target</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
		<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="c1">// Impact point</span>
	<span class="k">if</span> <span class="p">((</span><span class="n">Target</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">HasImpact</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">Target</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">ImpactDifferentStart</span><span class="p">))</span>
	<span class="p">{</span>
		<span class="n">UE_NET_TRACE_SCOPE</span><span class="p">(</span><span class="n">ImpactPoint</span><span class="p">,</span> <span class="o">*</span><span class="n">Reader</span><span class="p">,</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetTraceCollector</span><span class="p">(),</span> <span class="n">ENetTraceVerbosity</span><span class="o">::</span><span class="n">Verbose</span><span class="p">);</span>
		<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

		<span class="n">FNetDeserializeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Target</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
		<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Deserialize will read from the <code class="language-plaintext highlighter-rouge">FNetBitStreamReader</code> the quantized data we serialized. Note how our replication flags get passed along and are actually avoiding us to read data we don’t need.</p>

<h3 id="5-dequantize">5. Dequantize</h3>

<p>After we deserialize we need to recompose our data back to a dequantized state:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">Implementation</span> <span class="nf">file</span> <span class="p">(.</span><span class="n">cpp</span><span class="p">)</span><span class="o">:</span>

<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">Dequantize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetDequantizeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">Source</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source</span><span class="p">);</span>
	<span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">Target</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Target</span><span class="p">);</span>

	<span class="k">const</span> <span class="n">uint8</span> <span class="n">ReplicationFlags</span> <span class="o">=</span> <span class="n">Source</span><span class="p">.</span><span class="n">ReplicationFlags</span><span class="p">;</span>
	
	<span class="c1">// Start location</span>
	<span class="p">{</span>
		<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

		<span class="n">FNetDequantizeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Source</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
		<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Target</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">);</span>
		<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Dequantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="cm">/**
	* The logic below Dequantizes ImpactPoint if deemed necessary by our flags.
	* ImpactPoint is set to StartLocation if they aren't different.
	*/</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">HasImpact</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">ImpactDifferentStart</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

			<span class="n">FNetDequantizeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Source</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Target</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">);</span>
			<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Dequantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
		<span class="p">}</span>
		<span class="k">else</span>
		<span class="p">{</span>
			<span class="n">Target</span><span class="p">.</span><span class="n">ImpactPoint</span> <span class="o">=</span> <span class="n">Target</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">;</span>
		<span class="p">}</span>
	<span class="p">}</span>
	<span class="k">else</span>
	<span class="p">{</span>
		<span class="c1">// We reset it to 0 intentionally, although we could simply skip it.</span>
		<span class="n">Target</span><span class="p">.</span><span class="n">ImpactPoint</span> <span class="o">=</span> <span class="n">FVector</span><span class="o">::</span><span class="n">ZeroVector</span><span class="p">;</span>
	<span class="p">}</span>
	
	<span class="n">Target</span><span class="p">.</span><span class="n">bHasImpactPoint</span> <span class="o">=</span> <span class="p">(</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">HasImpact</span><span class="p">)</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>After deserializing, we must recompose the data into its original form. Here, we dequantize <code class="language-plaintext highlighter-rouge">StartLocation</code> and check our flags to determine how to handle <code class="language-plaintext highlighter-rouge">ImpactPoint</code>. If the flags indicate the impact point is identical to the start, we simply copy the value over, otherwise, we either dequantize <code class="language-plaintext highlighter-rouge">ImpactPoint</code> or zero it out.</p>

<p>By the end of this process, the Target struct is fully restored. Note that while I left <code class="language-plaintext highlighter-rouge">Distance</code> out here to handle it in <code class="language-plaintext highlighter-rouge">Apply</code>, it would be equally valid to compute it during this stage.</p>

<h3 id="6-optional-apply">6. (Optional) Apply</h3>

<p>In this example <code class="language-plaintext highlighter-rouge">Apply</code> is not necessary, but I’m implementing it for demonstration. This step occurs after dequantization and is responsible for writing the data into the actual target. While Iris does this <em>automagically</em> by default, overriding it allows you to skip specific properties or perform additional logic/fixup - like calculating <code class="language-plaintext highlighter-rouge">Distance</code> - before the game uses the values.</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">Implementation</span> <span class="nf">file</span> <span class="p">(.</span><span class="n">cpp</span><span class="p">)</span><span class="o">:</span>

<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">Apply</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetApplyArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">Source</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source</span><span class="p">);</span>
	<span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">Target</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Target</span><span class="p">);</span>

	<span class="n">Target</span><span class="p">.</span><span class="n">bHasImpactPoint</span> <span class="o">=</span> <span class="n">Source</span><span class="p">.</span><span class="n">bHasImpactPoint</span><span class="p">;</span>
	<span class="n">Target</span><span class="p">.</span><span class="n">ImpactPoint</span> <span class="o">=</span> <span class="n">Source</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">;</span>
	<span class="n">Target</span><span class="p">.</span><span class="n">StartLocation</span> <span class="o">=</span> <span class="n">Source</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">;</span>
	<span class="n">Target</span><span class="p">.</span><span class="n">Distance</span> <span class="o">=</span> <span class="p">(</span><span class="n">Target</span><span class="p">.</span><span class="n">ImpactPoint</span> <span class="o">-</span> <span class="n">Target</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">).</span><span class="n">Size</span><span class="p">();</span>
<span class="p">}</span></code></pre></figure>

<p>Et voila, serializer done! That was… interesting!</p>

<h2 id="full-code">Full code</h2>

<p>I provide below the full code of the complete serializer so that you can copy and paste it directly without the need of going fragment by fragment:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">Header</span> <span class="n">file</span> <span class="p">(.</span><span class="n">h</span><span class="p">)</span><span class="o">:</span> 

<span class="n">USTRUCT</span><span class="p">(</span><span class="n">BlueprintType</span><span class="p">)</span>
<span class="k">struct</span> <span class="nc">FMyStruct</span>
<span class="p">{</span>
	<span class="n">GENERATED_BODY</span><span class="p">()</span>
	
	<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">,</span> <span class="n">BlueprintReadOnly</span><span class="p">)</span>
	<span class="n">FVector</span> <span class="n">StartLocation</span> <span class="o">=</span> <span class="n">FVector</span><span class="o">::</span><span class="n">ZeroVector</span><span class="p">;</span>

	<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">,</span> <span class="n">BlueprintReadOnly</span><span class="p">)</span>
	<span class="n">FVector</span> <span class="n">ImpactPoint</span> <span class="o">=</span> <span class="n">FVector</span><span class="o">::</span><span class="n">ZeroVector</span><span class="p">;</span>

	<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">BlueprintReadOnly</span><span class="p">)</span>
	<span class="kt">bool</span> <span class="n">bHasImpactPoint</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>

	<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">BlueprintReadOnly</span><span class="p">,</span> <span class="n">NotReplicated</span><span class="p">)</span>
	<span class="kt">float</span> <span class="n">Distance</span><span class="p">;</span>
<span class="p">};</span>

<span class="n">USTRUCT</span><span class="p">()</span>
<span class="k">struct</span> <span class="nc">FMyStructNetSerializerConfig</span> <span class="o">:</span> <span class="k">public</span> <span class="n">FNetSerializerConfig</span>
<span class="p">{</span>
	<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="p">};</span>

<span class="k">namespace</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span>
<span class="p">{</span>
	<span class="n">UE_NET_DECLARE_SERIALIZER</span><span class="p">(</span><span class="n">FMyStructNetSerializer</span><span class="p">,</span> <span class="n">MYGAME_API</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">Implementation</span> <span class="n">file</span> <span class="p">(.</span><span class="n">cpp</span><span class="p">)</span><span class="o">:</span>

<span class="k">namespace</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span>
<span class="p">{</span>
	<span class="k">struct</span> <span class="nc">FMyStructNetSerializer</span>
	<span class="p">{</span>
		<span class="c1">// Version</span>
		<span class="k">static</span> <span class="k">const</span> <span class="n">uint32</span> <span class="n">Version</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
		
		<span class="k">enum</span> <span class="n">EReplicationFlags</span> <span class="o">:</span> <span class="n">uint8</span>
		<span class="p">{</span>
			<span class="n">HasImpact</span> <span class="o">=</span> <span class="mi">1U</span><span class="p">,</span>
			<span class="n">ImpactDifferentStart</span> <span class="o">=</span> <span class="mi">2U</span><span class="p">,</span>
		<span class="p">};</span>

		<span class="k">static</span> <span class="k">constexpr</span> <span class="n">uint32</span> <span class="n">ReplicatedFlagCount</span> <span class="o">=</span> <span class="mi">2U</span><span class="p">;</span>
		
		<span class="k">struct</span> <span class="nc">FQuantizedType</span>
		<span class="p">{</span>
			<span class="n">uint64</span> <span class="n">StartLocation</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
			<span class="n">uint64</span> <span class="n">ImpactPoint</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
			<span class="n">uint8</span> <span class="n">ReplicationFlags</span><span class="p">;</span>
		<span class="p">};</span>

		<span class="k">typedef</span> <span class="n">FMyStruct</span> <span class="n">SourceType</span><span class="p">;</span>
		<span class="k">typedef</span> <span class="n">FQuantizedType</span> <span class="n">QuantizedType</span><span class="p">;</span>
		<span class="k">typedef</span> <span class="n">FMyStructNetSerializerConfig</span> <span class="n">ConfigType</span><span class="p">;</span>

		<span class="k">static</span> <span class="k">const</span> <span class="n">ConfigType</span> <span class="n">DefaultConfig</span><span class="p">;</span>
		
		<span class="k">static</span> <span class="kt">void</span> <span class="n">Serialize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetSerializeArgs</span><span class="o">&amp;</span><span class="p">);</span>
		<span class="k">static</span> <span class="kt">void</span> <span class="n">Deserialize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetDeserializeArgs</span><span class="o">&amp;</span><span class="p">);</span>
		<span class="k">static</span> <span class="kt">void</span> <span class="n">Quantize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetQuantizeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">);</span>
		<span class="k">static</span> <span class="kt">void</span> <span class="n">Dequantize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetDequantizeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">);</span>
		<span class="k">static</span> <span class="kt">bool</span> <span class="n">IsEqual</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetIsEqualArgs</span><span class="o">&amp;</span><span class="p">);</span>

		<span class="k">static</span> <span class="kt">void</span> <span class="n">Apply</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetApplyArgs</span><span class="o">&amp;</span><span class="p">);</span>

	<span class="nl">private:</span>
		<span class="k">class</span> <span class="nc">FRegistryDelegates</span> <span class="k">final</span> <span class="o">:</span> <span class="k">public</span> <span class="n">FNetSerializerRegistryDelegates</span>
		<span class="p">{</span>
		<span class="nl">public:</span>
			<span class="k">virtual</span> <span class="o">~</span><span class="n">FRegistryDelegates</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
			<span class="k">virtual</span> <span class="kt">void</span> <span class="n">OnPreFreezeNetSerializerRegistry</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
		<span class="p">};</span>

		<span class="k">static</span> <span class="n">FRegistryDelegates</span> <span class="n">RegistryDelegates</span><span class="p">;</span>
	<span class="p">};</span>

	<span class="k">const</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">ConfigType</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">DefaultConfig</span><span class="p">;</span>
	<span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">FRegistryDelegates</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">RegistryDelegates</span><span class="p">;</span>
	
	<span class="n">UE_NET_IMPLEMENT_SERIALIZER</span><span class="p">(</span><span class="n">FMyStructNetSerializer</span><span class="p">);</span>
	
	<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">Serialize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetSerializeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">const</span> <span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">Value</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source</span><span class="p">);</span>

		<span class="n">FNetBitStreamWriter</span><span class="o">*</span> <span class="n">Writer</span> <span class="o">=</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetBitStreamWriter</span><span class="p">();</span>

		<span class="c1">// Replicated flags</span>
		<span class="k">const</span> <span class="n">uint8</span> <span class="n">ReplicationFlags</span> <span class="o">=</span> <span class="n">Value</span><span class="p">.</span><span class="n">ReplicationFlags</span><span class="p">;</span>
		<span class="n">Writer</span><span class="o">-&gt;</span><span class="n">WriteBits</span><span class="p">(</span><span class="n">ReplicationFlags</span><span class="p">,</span> <span class="n">ReplicatedFlagCount</span><span class="p">);</span>

		<span class="c1">// Start location</span>
		<span class="p">{</span>
			<span class="n">UE_NET_TRACE_SCOPE</span><span class="p">(</span><span class="n">StartLocation</span><span class="p">,</span> <span class="o">*</span><span class="n">Writer</span><span class="p">,</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetTraceCollector</span><span class="p">(),</span> <span class="n">ENetTraceVerbosity</span><span class="o">::</span><span class="n">Verbose</span><span class="p">);</span>
			<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

			<span class="n">FNetSerializeArgs</span> <span class="n">VectorNetSerializeArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
			<span class="n">VectorNetSerializeArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Value</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
			<span class="n">VectorNetSerializeArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
			<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Serialize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">VectorNetSerializeArgs</span><span class="p">);</span>
		<span class="p">}</span>
		
		<span class="c1">// Impact point</span>
		<span class="k">if</span> <span class="p">((</span><span class="n">Value</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">HasImpact</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">Value</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">ImpactDifferentStart</span><span class="p">))</span>
		<span class="p">{</span>
			<span class="n">UE_NET_TRACE_SCOPE</span><span class="p">(</span><span class="n">ImpactPoint</span><span class="p">,</span> <span class="o">*</span><span class="n">Writer</span><span class="p">,</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetTraceCollector</span><span class="p">(),</span> <span class="n">ENetTraceVerbosity</span><span class="o">::</span><span class="n">Verbose</span><span class="p">);</span>
			<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

			<span class="n">FNetSerializeArgs</span> <span class="n">VectorNetSerializeArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
			<span class="n">VectorNetSerializeArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Value</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
			<span class="n">VectorNetSerializeArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
			<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Serialize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">VectorNetSerializeArgs</span><span class="p">);</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">Deserialize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetDeserializeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">QuantizedType</span> <span class="n">TempValue</span> <span class="o">=</span> <span class="p">{};</span>

		<span class="n">FNetBitStreamReader</span><span class="o">*</span> <span class="n">Reader</span> <span class="o">=</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetBitStreamReader</span><span class="p">();</span>
		<span class="n">TempValue</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">=</span> <span class="n">Reader</span><span class="o">-&gt;</span><span class="n">ReadBits</span><span class="p">(</span><span class="n">ReplicatedFlagCount</span><span class="p">);</span>

		<span class="c1">// Start location</span>
		<span class="p">{</span>
			<span class="n">UE_NET_TRACE_SCOPE</span><span class="p">(</span><span class="n">StartLocation</span><span class="p">,</span> <span class="o">*</span><span class="n">Reader</span><span class="p">,</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetTraceCollector</span><span class="p">(),</span> <span class="n">ENetTraceVerbosity</span><span class="o">::</span><span class="n">Verbose</span><span class="p">);</span>
			<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

			<span class="n">FNetDeserializeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TempValue</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
			<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
		<span class="p">}</span>

		<span class="c1">// Impact point</span>
		<span class="k">if</span> <span class="p">((</span><span class="n">TempValue</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">HasImpact</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">TempValue</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">ImpactDifferentStart</span><span class="p">))</span>
		<span class="p">{</span>
			<span class="n">UE_NET_TRACE_SCOPE</span><span class="p">(</span><span class="n">ImpactPoint</span><span class="p">,</span> <span class="o">*</span><span class="n">Reader</span><span class="p">,</span> <span class="n">Context</span><span class="p">.</span><span class="n">GetTraceCollector</span><span class="p">(),</span> <span class="n">ENetTraceVerbosity</span><span class="o">::</span><span class="n">Verbose</span><span class="p">);</span>
			<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

			<span class="n">FNetDeserializeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TempValue</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
			<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
		<span class="p">}</span>

		<span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">Target</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Target</span><span class="p">);</span>
		<span class="n">Target</span> <span class="o">=</span> <span class="n">TempValue</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">Quantize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetQuantizeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">const</span> <span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">Source</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source</span><span class="p">);</span>
		<span class="n">QuantizedType</span> <span class="n">TempValue</span> <span class="o">=</span> <span class="p">{};</span>

		<span class="n">uint8</span> <span class="n">ReplicationFlags</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

		<span class="n">ReplicationFlags</span> <span class="o">|=</span> <span class="n">Source</span><span class="p">.</span><span class="n">bHasImpactPoint</span> <span class="o">?</span> <span class="n">EReplicationFlags</span><span class="o">::</span><span class="n">HasImpact</span> <span class="o">:</span> <span class="mi">0U</span><span class="p">;</span>
		<span class="n">ReplicationFlags</span> <span class="o">|=</span> <span class="n">Source</span><span class="p">.</span><span class="n">StartLocation</span> <span class="o">!=</span> <span class="n">Source</span><span class="p">.</span><span class="n">ImpactPoint</span> <span class="o">?</span> <span class="n">EReplicationFlags</span><span class="o">::</span><span class="n">ImpactDifferentStart</span> <span class="o">:</span> <span class="mi">0U</span><span class="p">;</span>

		<span class="n">TempValue</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">=</span> <span class="n">ReplicationFlags</span><span class="p">;</span>

		<span class="c1">// Start location</span>
		<span class="p">{</span>
			<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

			<span class="n">FNetQuantizeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Source</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">);</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TempValue</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
			<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Quantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
		<span class="p">}</span>

		<span class="c1">// Impact point</span>
		<span class="k">if</span> <span class="p">((</span><span class="n">TempValue</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">HasImpact</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">TempValue</span><span class="p">.</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">ImpactDifferentStart</span><span class="p">))</span>
		<span class="p">{</span>
			<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

			<span class="n">FNetQuantizeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Source</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">);</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TempValue</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
			<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Quantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
		<span class="p">}</span>

		<span class="c1">// Finally copy-over quantized data to target</span>
		<span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">Target</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Target</span><span class="p">);</span>
		<span class="n">Target</span> <span class="o">=</span> <span class="n">TempValue</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">Dequantize</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetDequantizeArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">const</span> <span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">Source</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source</span><span class="p">);</span>
		<span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">Target</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Target</span><span class="p">);</span>

		<span class="k">const</span> <span class="n">uint8</span> <span class="n">ReplicationFlags</span> <span class="o">=</span> <span class="n">Source</span><span class="p">.</span><span class="n">ReplicationFlags</span><span class="p">;</span>
		
		<span class="c1">// Start location</span>
		<span class="p">{</span>
			<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

			<span class="n">FNetDequantizeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Source</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
			<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Target</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">);</span>
			<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Dequantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
		<span class="p">}</span>

		<span class="c1">// Impact point</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">HasImpact</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="k">if</span> <span class="p">(</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">ImpactDifferentStart</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="k">const</span> <span class="n">FNetSerializer</span><span class="o">&amp;</span> <span class="n">VectorSerializer</span> <span class="o">=</span> <span class="n">UE_NET_GET_SERIALIZER</span><span class="p">(</span><span class="n">FVectorNetQuantizeNetSerializer</span><span class="p">);</span>

				<span class="n">FNetDequantizeArgs</span> <span class="n">MemberArgs</span> <span class="o">=</span> <span class="n">Args</span><span class="p">;</span>
				<span class="n">MemberArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">NetSerializerConfigParam</span><span class="p">(</span><span class="n">VectorSerializer</span><span class="p">.</span><span class="n">DefaultConfig</span><span class="p">);</span>
				<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Source</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
				<span class="n">MemberArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Target</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">);</span>
				<span class="n">VectorSerializer</span><span class="p">.</span><span class="n">Dequantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">MemberArgs</span><span class="p">);</span>
			<span class="p">}</span>
			<span class="k">else</span>
			<span class="p">{</span>
				<span class="n">Target</span><span class="p">.</span><span class="n">ImpactPoint</span> <span class="o">=</span> <span class="n">Target</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">;</span>
			<span class="p">}</span>
		<span class="p">}</span>
		<span class="k">else</span>
		<span class="p">{</span>
			<span class="n">Target</span><span class="p">.</span><span class="n">ImpactPoint</span> <span class="o">=</span> <span class="n">FVector</span><span class="o">::</span><span class="n">ZeroVector</span><span class="p">;</span>
		<span class="p">}</span>
		
		<span class="n">Target</span><span class="p">.</span><span class="n">bHasImpactPoint</span> <span class="o">=</span> <span class="p">(</span><span class="n">ReplicationFlags</span> <span class="o">&amp;</span> <span class="n">HasImpact</span><span class="p">)</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="kt">bool</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">IsEqual</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetIsEqualArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">bStateIsQuantized</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="k">const</span> <span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">QuantizedValue0</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source0</span><span class="p">);</span>
			<span class="k">const</span> <span class="n">QuantizedType</span><span class="o">&amp;</span> <span class="n">QuantizedValue1</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">QuantizedType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source1</span><span class="p">);</span>
			<span class="k">return</span> <span class="n">FPlatformMemory</span><span class="o">::</span><span class="n">Memcmp</span><span class="p">(</span><span class="o">&amp;</span><span class="n">QuantizedValue0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">QuantizedValue1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">QuantizedType</span><span class="p">))</span> <span class="o">==</span> <span class="mi">0</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="k">else</span>
		<span class="p">{</span>
			<span class="k">const</span> <span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">SourceValue0</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source0</span><span class="p">);</span>
			<span class="k">const</span> <span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">SourceValue1</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source1</span><span class="p">);</span>
	
			<span class="k">if</span> <span class="p">(</span><span class="n">SourceValue0</span><span class="p">.</span><span class="n">bHasImpactPoint</span> <span class="o">!=</span> <span class="n">SourceValue1</span><span class="p">.</span><span class="n">bHasImpactPoint</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
			<span class="p">}</span>
			
			<span class="n">QuantizedType</span> <span class="n">QuantizedValue0</span> <span class="o">=</span> <span class="p">{};</span>
			<span class="n">QuantizedType</span> <span class="n">QuantizedValue1</span> <span class="o">=</span> <span class="p">{};</span>
	
			<span class="n">FNetQuantizeArgs</span> <span class="n">QuantizeArgs</span> <span class="o">=</span> <span class="p">{};</span>
			<span class="n">QuantizeArgs</span><span class="p">.</span><span class="n">NetSerializerConfig</span> <span class="o">=</span> <span class="n">Args</span><span class="p">.</span><span class="n">NetSerializerConfig</span><span class="p">;</span>
	
			<span class="n">QuantizeArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source0</span><span class="p">);</span>
			<span class="n">QuantizeArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">QuantizedValue0</span><span class="p">);</span>
			<span class="n">Quantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">QuantizeArgs</span><span class="p">);</span>
	
			<span class="n">QuantizeArgs</span><span class="p">.</span><span class="n">Source</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source1</span><span class="p">);</span>
			<span class="n">QuantizeArgs</span><span class="p">.</span><span class="n">Target</span> <span class="o">=</span> <span class="n">NetSerializerValuePointer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">QuantizedValue1</span><span class="p">);</span>
			<span class="n">Quantize</span><span class="p">(</span><span class="n">Context</span><span class="p">,</span> <span class="n">QuantizeArgs</span><span class="p">);</span>
	
			<span class="k">return</span> <span class="n">FPlatformMemory</span><span class="o">::</span><span class="n">Memcmp</span><span class="p">(</span><span class="o">&amp;</span><span class="n">QuantizedValue0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">QuantizedValue1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">QuantizedType</span><span class="p">))</span> <span class="o">==</span> <span class="mi">0</span><span class="p">;</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">Apply</span><span class="p">(</span><span class="n">FNetSerializationContext</span><span class="o">&amp;</span> <span class="n">Context</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetApplyArgs</span><span class="o">&amp;</span> <span class="n">Args</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">const</span> <span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">Source</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Source</span><span class="p">);</span>
		<span class="n">SourceType</span><span class="o">&amp;</span> <span class="n">Target</span> <span class="o">=</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">SourceType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">Args</span><span class="p">.</span><span class="n">Target</span><span class="p">);</span>

		<span class="n">Target</span><span class="p">.</span><span class="n">bHasImpactPoint</span> <span class="o">=</span> <span class="n">Source</span><span class="p">.</span><span class="n">bHasImpactPoint</span><span class="p">;</span>
		<span class="n">Target</span><span class="p">.</span><span class="n">ImpactPoint</span> <span class="o">=</span> <span class="n">Source</span><span class="p">.</span><span class="n">ImpactPoint</span><span class="p">;</span>
		<span class="n">Target</span><span class="p">.</span><span class="n">StartLocation</span> <span class="o">=</span> <span class="n">Source</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">;</span>
		<span class="n">Target</span><span class="p">.</span><span class="n">Distance</span> <span class="o">=</span> <span class="p">(</span><span class="n">Target</span><span class="p">.</span><span class="n">ImpactPoint</span> <span class="o">-</span> <span class="n">Target</span><span class="p">.</span><span class="n">StartLocation</span><span class="p">).</span><span class="n">Size</span><span class="p">();</span>

		<span class="n">UE_LOG</span><span class="p">(</span><span class="n">LogTemp</span><span class="p">,</span> <span class="n">Warning</span><span class="p">,</span> <span class="n">TEXT</span><span class="p">(</span><span class="s">"FMyStructNetSerializer::Apply"</span><span class="p">));</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">namespace</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span>
<span class="p">{</span>
	<span class="k">static</span> <span class="k">const</span> <span class="n">FName</span> <span class="n">PropertyNetSerializerRegistry_NAME_MyStruct</span><span class="p">(</span><span class="s">"MyStruct"</span><span class="p">);</span>
	<span class="n">UE_NET_IMPLEMENT_NAMED_STRUCT_NETSERIALIZER_INFO</span><span class="p">(</span><span class="n">PropertyNetSerializerRegistry_NAME_MyStruct</span><span class="p">,</span> <span class="n">FMyStructNetSerializer</span><span class="p">);</span>
	
	<span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">FRegistryDelegates</span><span class="o">::~</span><span class="n">FRegistryDelegates</span><span class="p">()</span>
	<span class="p">{</span>
		<span class="n">UE_NET_UNREGISTER_NETSERIALIZER_INFO</span><span class="p">(</span><span class="n">PropertyNetSerializerRegistry_NAME_MyStruct</span><span class="p">);</span>
	<span class="p">}</span>
	
	<span class="kt">void</span> <span class="n">FMyStructNetSerializer</span><span class="o">::</span><span class="n">FRegistryDelegates</span><span class="o">::</span><span class="n">OnPreFreezeNetSerializerRegistry</span><span class="p">()</span>
	<span class="p">{</span>
		<span class="n">UE_NET_REGISTER_NETSERIALIZER_INFO</span><span class="p">(</span><span class="n">PropertyNetSerializerRegistry_NAME_MyStruct</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h1 id="conclusion">Conclusion</h1>

<p>Well, that was certainly an adventure! I’m sure you can now see why Peter Engstrom said, “Not implementing one if one can avoid it can be a big maintenance win”.</p>

<p>Iris serializers offer many more options than I was able to explore in this article. I encourage you all to do your own experiments and exploration (please, don’t just take my word for it!), let this post serve as a helping hand to get you started!</p>

<p>And… yeah! Feel free to <a href="https://twitter.com/vorixo">contact me</a> (and follow me! hehe) if you find any issues with the article or have any questions about the whole topic.</p>

<p>Enjoy, vori.</p>]]></content><author><name>Alvaro Jover-Alvarez</name></author><category term="Videogames Development" /><category term="Multiplayer" /><category term="UE5" /><category term="Networking" /><category term="Serialization" /><category term="Quantization" /><category term="Iris" /><summary type="html"><![CDATA[In this post we'll learn how to add replication support (serialization) to intricate types in Unreal Engine 5 Iris' networking system.]]></summary></entry><entry><title type="html">Iris - Replication filters</title><link href="https://vorixo.github.io/devtricks/iris-replication-filter/" rel="alternate" type="text/html" title="Iris - Replication filters" /><published>2025-11-09T00:00:00+00:00</published><updated>2025-11-09T00:00:00+00:00</updated><id>https://vorixo.github.io/devtricks/iris-replication-filter</id><content type="html" xml:base="https://vorixo.github.io/devtricks/iris-replication-filter/"><![CDATA[<p>In this article we will dive a tiny little bit on Iris’ replication system and we’ll learn how to setup a simple replication filter.</p>

<h1 id="introduction---being-an-early-adopter">Introduction - Being an early adopter</h1>

<p>Most of my articles/tutorials revolve around the vanilla replication system of Unreal Engine, which is still pretty industry standard to date. However, Epic’s been cooking now for several years a new implementation to their replication system called Iris.</p>

<p><a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/introduction-to-iris-in-unreal-engine">Iris</a> has been designed from the ground up to minimize the CPU overhead introduced by property replication by maintaining a quantized copy of all replicated data, sharing work amongst connections, providing thus, the opportunity to do more work in parallel; alongside Iris’ default push-based replication model.</p>

<p>Epic’s intention with Iris is for it to become industry standard, some games, like Splitgate, are making use of it. Although as Epic points out on the <a href="https://dev.epicgames.com/community/learning/tutorials/z08b/unreal-engine-iris-faq">Iris’ FAQ</a> - the system is still Experimental and lacks support of basic features like <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/demonetdriver-and-streamers-in-unreal-engine">replays</a>.</p>

<p>With that said, early adoption helps the industry tremendously to uncover hidden bugs and priorize developments efforts, and sometimes, even cross-collaborations arise! See the recent CD Projekt + Epic work in The Witcher 4 technical demo:</p>

<iframe width="480" height="270" src="https://www.youtube.com/embed/ji0Hfiswcjo" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>

<h1 id="the-replication-filter">The replication filter</h1>

<p>A <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/iris-filtering-in-unreal-engine">replication filter</a> is a system that determines what objects are replicated to which connections.</p>

<p>We’ve done this already in my previous <a href="https://vorixo.github.io/devtricks/procgen/">proc-gen article</a> overriding Unreal’s legacy <code class="language-plaintext highlighter-rouge">AActor::IsNetRelevantFor</code> function. This function is evaluated whenever the Actor’s replication is considered for update, which is normally tied to its <code class="language-plaintext highlighter-rouge">NetUpdateFrequency</code>. Returning <code class="language-plaintext highlighter-rouge">false</code> for an arbitrary connection would mean that such connection won’t get sent the last replication update for the Actor.</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">bool</span> <span class="n">AProjectile</span><span class="o">::</span><span class="n">IsNetRelevantFor</span><span class="p">(</span><span class="k">const</span> <span class="n">AActor</span><span class="o">*</span> <span class="n">RealViewer</span><span class="p">,</span> <span class="k">const</span> <span class="n">AActor</span><span class="o">*</span> <span class="n">ViewTarget</span><span class="p">,</span> <span class="k">const</span> <span class="n">FVector</span><span class="o">&amp;</span> <span class="n">SrcLocation</span><span class="p">)</span> <span class="k">const</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="k">const</span> <span class="n">APlayerController</span><span class="o">*</span> <span class="n">PC</span> <span class="o">=</span> <span class="n">Cast</span><span class="o">&lt;</span><span class="n">APlayerController</span><span class="o">&gt;</span><span class="p">(</span><span class="n">RealViewer</span><span class="p">))</span>
	<span class="p">{</span>
		<span class="c1">// The projectile's owner is never relevant since it runs its own local simulation</span>
		<span class="k">const</span> <span class="n">APlayerController</span><span class="o">*</span> <span class="n">ProjectileOnwer</span> <span class="o">=</span> <span class="n">GetPlayerOwner</span><span class="p">();</span>
		<span class="k">return</span> <span class="p">(</span><span class="n">GetPlayerOwner</span><span class="p">()</span> <span class="o">!=</span> <span class="n">PC</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">Super</span><span class="o">::</span><span class="n">IsNetRelevantFor</span><span class="p">(</span><span class="n">RealViewer</span><span class="p">,</span> <span class="n">ViewTarget</span><span class="p">,</span> <span class="n">SrcLocation</span><span class="p">));</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">Super</span><span class="o">::</span><span class="n">IsNetRelevantFor</span><span class="p">(</span><span class="n">RealViewer</span><span class="p">,</span> <span class="n">ViewTarget</span><span class="p">,</span> <span class="n">SrcLocation</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>In the code above, we are disallowing replication to the projectile’s owner. As pointed aswell in the procgen article, there are probably better ways to do this through the <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/replication-graph-in-unreal-engine">Replication Graph</a>.</p>

<h2 id="the-replication-filter-in-iris">The replication filter in Iris</h2>

<p>However, Iris does not support the Replication Graph, since it has its own scheme for prioritizing and filtering objects to different connections. In this section, we’ll learn how to setup a simple replication filter in Iris, mimicking the example we saw above.</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="n">AProjectile</span><span class="o">::</span><span class="n">BeginPlay</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">BeginPlay</span><span class="p">();</span>
	
	<span class="k">if</span> <span class="p">(</span><span class="n">HasAuthority</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="n">UReplicationSystem</span><span class="o">*</span> <span class="n">ReplicationSystem</span> <span class="o">=</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span><span class="o">::</span><span class="n">FReplicationSystemUtil</span><span class="o">::</span><span class="n">GetReplicationSystem</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
		<span class="n">UEngineReplicationBridge</span><span class="o">*</span> <span class="n">ActorReplicationBridge</span> <span class="o">=</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span><span class="o">::</span><span class="n">FReplicationSystemUtil</span><span class="o">::</span><span class="n">GetActorReplicationBridge</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
		<span class="n">UE</span><span class="o">::</span><span class="n">Net</span><span class="o">::</span><span class="n">FNetRefHandle</span> <span class="n">RepActorNetRefHandle</span> <span class="o">=</span> <span class="n">ActorReplicationBridge</span><span class="o">-&gt;</span><span class="n">GetReplicatedRefHandle</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>

		<span class="k">constexpr</span> <span class="n">int32</span> <span class="n">MaxConnections</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span> <span class="c1">// Replace for max amount of connections supported on your game.</span>
		<span class="n">TBitArray</span><span class="o">&lt;&gt;</span> <span class="n">Connections</span><span class="p">;</span>
		<span class="n">Connections</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="nb">false</span><span class="p">,</span> <span class="n">MaxConnections</span><span class="p">);</span>

		<span class="k">const</span> <span class="n">APlayerController</span><span class="o">*</span> <span class="n">PlayerController</span> <span class="o">=</span> <span class="n">GetPlayerOwner</span><span class="p">();</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">PlayerController</span> <span class="o">&amp;&amp;</span> <span class="n">PlayerController</span><span class="o">-&gt;</span><span class="n">GetNetConnection</span><span class="p">())</span>
		<span class="p">{</span>
			<span class="n">UE</span><span class="o">::</span><span class="n">Net</span><span class="o">::</span><span class="n">FConnectionHandle</span> <span class="n">ConnectionId</span> <span class="o">=</span> <span class="n">PlayerController</span><span class="o">-&gt;</span><span class="n">GetNetConnection</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetConnectionHandle</span><span class="p">();</span>
			<span class="n">Connections</span><span class="p">.</span><span class="n">Insert</span><span class="p">(</span><span class="nb">true</span><span class="p">,</span> <span class="n">ConnectionId</span><span class="p">.</span><span class="n">GetParentConnectionId</span><span class="p">());</span>
			<span class="n">ReplicationSystem</span><span class="o">-&gt;</span><span class="n">SetConnectionFilter</span><span class="p">(</span><span class="n">RepActorNetRefHandle</span><span class="p">,</span> <span class="n">Connections</span><span class="p">,</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span><span class="o">::</span><span class="n">ENetFilterStatus</span><span class="o">::</span><span class="n">Disallow</span><span class="p">);</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>In the above code we’ve disallowed replication to the projectile owner player. Note how the above code is event-driven rather than poll-based.</p>

<p class="notice--info">Similar logic applies to add allowed connections by using <code class="language-plaintext highlighter-rouge">UE::Net::ENetFilterStatus::Allow</code>.</p>

<p>The same way, to clear the filter, we can simply do the following:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="n">AProjectile</span><span class="o">::</span><span class="n">ClearFilter</span><span class="p">()</span>
<span class="p">{</span>	
	<span class="k">if</span> <span class="p">(</span><span class="n">HasAuthority</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="n">UReplicationSystem</span><span class="o">*</span> <span class="n">ReplicationSystem</span> <span class="o">=</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span><span class="o">::</span><span class="n">FReplicationSystemUtil</span><span class="o">::</span><span class="n">GetReplicationSystem</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
		<span class="n">UEngineReplicationBridge</span><span class="o">*</span> <span class="n">ActorReplicationBridge</span> <span class="o">=</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span><span class="o">::</span><span class="n">FReplicationSystemUtil</span><span class="o">::</span><span class="n">GetActorReplicationBridge</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
		<span class="n">UE</span><span class="o">::</span><span class="n">Net</span><span class="o">::</span><span class="n">FNetRefHandle</span> <span class="n">RepActorNetRefHandle</span> <span class="o">=</span> <span class="n">ActorReplicationBridge</span><span class="o">-&gt;</span><span class="n">GetReplicatedRefHandle</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>

		<span class="n">TBitArray</span><span class="o">&lt;&gt;</span> <span class="n">NoConnections</span><span class="p">;</span>
		<span class="n">ReplicationSystem</span><span class="o">-&gt;</span><span class="n">SetConnectionFilter</span><span class="p">(</span><span class="n">RepActorNetRefHandle</span><span class="p">,</span> <span class="n">NoConnections</span><span class="p">,</span> <span class="n">UE</span><span class="o">::</span><span class="n">Net</span><span class="o">::</span><span class="n">ENetFilterStatus</span><span class="o">::</span><span class="n">Disallow</span><span class="p">);</span>

	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>With this, now you know how to skip or allow replication to specific connections, however, this is just a start!</p>

<h1 id="additional-resources">Additional resources</h1>

<p>Besides this post, you can also take a look in these resources/files:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">Engine\Plugins\Runtime\ReplicationSystemTestPlugin\Source\Private\Tests\ReplicationSystem\Filtering\TestFiltering.cpp</code>: A test suite for filtering replication to connections.</li>
  <li><a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/iris-filtering-in-unreal-engine">Official Epic filtering documentation</a>: Provides more in depth examples as well as other types of filtering - such as group or dynamic filters.</li>
</ul>

<p>Finally, I strongly recommend everyone to take a look at Epic’s Unreal Fest talk about Iris, where they dive into the design behind the Iris replication system:</p>

<iframe width="480" height="270" src="https://www.youtube.com/embed/K472O2rVvG0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>

<p>Thanks Epic!</p>

<h1 id="conclusion">Conclusion</h1>

<p>Today we explored together a simple way to set up replication filters in Iris with a very trivial example.</p>

<p>As I always say, I encourage you all to do your own experiments and exploration, let this article serve as a helping hand to get started in the matter!</p>

<p>As always, feel free to <a href="https://twitter.com/vorixo">contact me</a> (and follow me! hehe) if you find any issues with the article or have any questions about the whole topic.</p>

<p>Enjoy, vori.</p>]]></content><author><name>Alvaro Jover-Alvarez</name></author><category term="Videogames Development" /><category term="Multiplayer" /><category term="UE5" /><category term="Networking" /><category term="Iris" /><summary type="html"><![CDATA[In this post we'll learn how to use simple replication filters in Unreal Engine Iris' networking system.]]></summary></entry><entry><title type="html">Networked Physics and UNetworkPhysicsComponent in Unreal Engine 5.8</title><link href="https://vorixo.github.io/devtricks/phys-prediction-use/" rel="alternate" type="text/html" title="Networked Physics and UNetworkPhysicsComponent in Unreal Engine 5.8" /><published>2024-05-04T00:00:00+00:00</published><updated>2026-09-23T00:00:00+00:00</updated><id>https://vorixo.github.io/devtricks/phys-prediction-use</id><content type="html" xml:base="https://vorixo.github.io/devtricks/phys-prediction-use/"><![CDATA[<p>In our <a href="https://vorixo.github.io/devtricks/phys-prediction-show/">introduction to physics prediction</a> we explored the physics prediction system bundled with Unreal Engine. Now, let’s build a rolling boulder in <strong>Unreal Engine 5.8</strong> and do a hands-on together!</p>

<h1 id="introduction">Introduction</h1>

<p>Predicting and correcting physics is one of the most complicated topics when it comes to network prediction. The nature of the complexity comes by the fact that if we hit a chain of physics objects on the client, we’d expect all the physics to react instantaneously, without having to wait for the server. But not only that, these physics objects should smoothly correct their position on the clients if there are incongruences with what the server expects. So you can imagine how difficult this can get with complex physics chain reactions.</p>

<p>Fortunately, Epic has been working on a solution for these problems bundled in their Networked Physics system. Before digging into the matter, I strongly recommend everyone to read <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/networked-physics-overview">Epic’s Networked Physics Overview</a>, which explains the different physics replication modes.</p>

<p>This article follows <strong><a href="https://dev.epicgames.com/community/learning/tutorials/MoBq/unreal-engine-networked-physics-pawn-tutorial">Networked Physics - Pawn Tutorial by MBobbo (Markus Boberg)</a></strong>, the creator of Unreal Engine’s networked physics prediction system. His tutorial is the implementation reference for this article. We follow its input flow, physics callback, history registration, input merging and decay, and physics-object handling. Credit where it’s due!</p>

<p class="notice--info"><strong>UE 5.8:</strong> The code below adapts that implementation to our rolling boulder and adds <strong>networked actions for jumping</strong>, a feature introduced in 5.8. Follow the written settings below when configuring your project.</p>

<h1 id="making-a-rolling-boulder">Making a rolling boulder</h1>

<p>The ‘rolling boulder’ we are going to create may serve as a “hello world” for those that would like to get introduced to the matter, as this system can be used to make vehicles or physics controllable objects. Because… what’s easier than a rolling sphere?</p>

<p>This is still a toy example. We’ll keep the movement simple and spend our time on getting inputs into the physics simulation, recording the state we need for rewind, and replaying those inputs when the server corrects us.</p>

<p>So… as I always say, don’t trust random articles from the internet and please, do your own research! If you think that any of the information shared in here is off, you can always <a href="https://twitter.com/vorixo">contact me on twitter</a> and we can fix it together as a community effort! Now… let’s get into it :D!</p>

<h2 id="the-setup">The setup</h2>

<p>In <code class="language-plaintext highlighter-rouge">Project Settings -&gt; Physics</code>, turn on <strong>Enable Physics Prediction</strong>. In UE 5.8 this synchronizes the client and server physics frames, and enables the client’s physics history when an actor uses Resimulation.</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/phys-prediction-show/enableit.jpg" alt="Network Physics Prediction" class="align-center" /></p>

<p>Under <code class="language-plaintext highlighter-rouge">Physics -&gt; Framerate</code>, enable <strong>Tick Physics Async</strong> and choose an <strong>Async Fixed Time Step Size</strong>, for example <code class="language-plaintext highlighter-rouge">0.016666667</code> for 60 physics steps per second. This is the physics rate, not a requirement to render at 60 FPS!</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/phys-prediction-use/physics-settings.jpg" alt="Physics Settings" class="align-center" /></p>

<p>Set <strong>Max Supported Latency Prediction</strong> to cover the round-trip latency you intend to support, with some room for jitter. A larger history costs more memory. You do <strong>not</strong> need to also enable <strong>Enable Physics History Capture</strong> for this setup: the 5.8 settings explicitly reserve that switch for manually enabling history, including on the server.</p>

<p>Our pawn needs <strong>Replicates</strong>, <strong>Replicate Movement</strong>, and <strong>Physics Replication Mode = Resimulation</strong> so we can predict its movement locally and resimulate it after a server correction. Set these on a Blueprint child of <code class="language-plaintext highlighter-rouge">ARollingBallPawn</code> then, add a sphere as the root component, a spring arm and camera for your controls.</p>

<p>Other replicated physics actors can use <strong>Predictive Interpolation</strong>. Resimulation isn’t restricted to controlled pawns, though: the UE 5.8 source still recommends it for physics objects that interact with a resimulated pawn (<code class="language-plaintext highlighter-rouge">EPhysicsReplicationMode</code> in <code class="language-plaintext highlighter-rouge">EngineTypes.h</code>). Choose their mode with those interactions in mind, rather than setting every replicated physics object in the level to Resimulation.</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/phys-prediction-use/boulderactor.jpg" alt="Boulder Actor" class="align-center" /></p>

<p>And to finish up the setup, let’s configure the root static mesh:</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/phys-prediction-use/rootmeshsettings.jpg" alt="Root mesh" class="align-center" /></p>

<p>The simulating sphere must be the <strong>root component</strong>, with collision enabled. Keep <strong>Component Replicates</strong> off for the mesh, but keep <strong>Replicate Movement</strong> on for the actor and <strong>Replicate Physics to Autonomous Proxy</strong> on for the mesh. The actor’s movement replication carries the authoritative rigid body state; the Network Physics component carries our inputs and custom state. We need both!</p>

<p class="notice--info"><strong>Note:</strong> Following MBobbo’s setup, raise the replicated movement quantization levels under <code class="language-plaintext highlighter-rouge">Replication -&gt; Advanced</code>, so the physics replication system has more precise position, velocity and rotation data to work with. Set the pawn’s spawn collision handling to <strong>Always Spawn, Ignore Collisions</strong>. Keep <strong>Enable Async Physics Tick</strong> off on the pawn: our physics-thread work runs through the Chaos simulation callback.</p>

<p>For this example, follow MBobbo’s <strong>Iris</strong> setup. Enable the <strong>Iris</strong> plugin in the editor (and restart when prompted), then add these settings to <code class="language-plaintext highlighter-rouge">Config/DefaultEngine.ini</code>:</p>

<figure class="highlight"><pre><code class="language-ini" data-lang="ini"><span class="nn">[SystemSettings]</span>
<span class="py">net.IsPushModelEnabled</span><span class="p">=</span><span class="s">1</span>
<span class="py">net.Iris.PushModelMode</span><span class="p">=</span><span class="s">1</span>
<span class="py">net.Iris.UseIrisReplication</span><span class="p">=</span><span class="s">1</span>
<span class="py">net.SubObjects.DefaultUseSubObjectReplicationList</span><span class="p">=</span><span class="s">1</span></code></pre></figure>

<p>In your module’s <code class="language-plaintext highlighter-rouge">.Build.cs</code>, add the dependencies used by the headers below, and enable its Iris build support. Keep any other dependencies your project already needs:</p>

<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="n">PublicDependencyModuleNames</span><span class="p">.</span><span class="nf">AddRange</span><span class="p">(</span><span class="k">new</span> <span class="kt">string</span><span class="p">[]</span>
<span class="p">{</span>
    <span class="s">"Core"</span><span class="p">,</span> <span class="s">"CoreUObject"</span><span class="p">,</span> <span class="s">"Engine"</span><span class="p">,</span> <span class="s">"PhysicsCore"</span><span class="p">,</span> <span class="s">"Chaos"</span><span class="p">,</span> <span class="s">"NetCore"</span>
<span class="p">});</span>
<span class="nf">SetupIrisSupport</span><span class="p">(</span><span class="n">Target</span><span class="p">);</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">SetupIrisSupport</code> configures compilation; the plugin and ini settings complete this example’s Iris setup. Our payloads derive from <code class="language-plaintext highlighter-rouge">FNetworkPhysicsPayload</code> and describe their replicated data with <code class="language-plaintext highlighter-rouge">UPROPERTY</code>.</p>

<h2 id="coding-the-physics-boulder">Coding the physics boulder</h2>

<p>The <code class="language-plaintext highlighter-rouge">UNetworkPhysicsComponent</code> handles networking and history for our inputs and custom states. As MBobbo explains, rigid body movement is replicated through the actor, while our custom state must contain the gameplay data needed to reproduce the simulation. Here that includes which jump request has already produced an action. The jump effect itself has its own <code class="language-plaintext highlighter-rouge">FNetworkPhysicsActionPayload</code>.</p>

<p>Let’s put the following in <code class="language-plaintext highlighter-rouge">RollingBallPawn.h</code>. Replace <code class="language-plaintext highlighter-rouge">MYPROJECT_API</code> with your module’s API macro. The generated header must remain the last include.</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="cp">#pragma once
</span>
<span class="cp">#include</span> <span class="cpf">"CoreMinimal.h"</span><span class="cp">
#include</span> <span class="cpf">"GameFramework/Pawn.h"</span><span class="cp">
#include</span> <span class="cpf">"Chaos/SimCallbackObject.h"</span><span class="cp">
#include</span> <span class="cpf">"Physics/NetworkPhysicsComponent.h"</span><span class="cp">
#include</span> <span class="cpf">"RollingBallPawn.generated.h"</span><span class="cp">
</span>
<span class="k">class</span> <span class="nc">FRollingBallAsync</span><span class="p">;</span>

<span class="n">UCLASS</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">MYPROJECT_API</span> <span class="n">ARollingBallPawn</span> <span class="o">:</span> <span class="k">public</span> <span class="n">APawn</span>
<span class="p">{</span>
	<span class="n">GENERATED_BODY</span><span class="p">()</span>

<span class="nl">public:</span>
	<span class="n">ARollingBallPawn</span><span class="p">();</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">PostInitializeComponents</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">Tick</span><span class="p">(</span><span class="kt">float</span> <span class="n">DeltaTime</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>

	<span class="n">UFUNCTION</span><span class="p">(</span><span class="n">BlueprintCallable</span><span class="p">)</span>
	<span class="kt">void</span> <span class="n">SetThrottleInput</span><span class="p">(</span><span class="kt">float</span> <span class="n">Value</span><span class="p">);</span>

	<span class="n">UFUNCTION</span><span class="p">(</span><span class="n">BlueprintCallable</span><span class="p">)</span>
	<span class="kt">void</span> <span class="n">SetSteeringInput</span><span class="p">(</span><span class="kt">float</span> <span class="n">Value</span><span class="p">);</span>

	<span class="n">UFUNCTION</span><span class="p">(</span><span class="n">BlueprintCallable</span><span class="p">)</span>
	<span class="kt">void</span> <span class="n">SetTravelDirectionInput</span><span class="p">(</span><span class="n">FRotator</span> <span class="n">Value</span><span class="p">);</span>

	<span class="n">UFUNCTION</span><span class="p">(</span><span class="n">BlueprintCallable</span><span class="p">)</span>
	<span class="kt">void</span> <span class="n">Jump</span><span class="p">();</span>

<span class="nl">protected:</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">EndPlay</span><span class="p">(</span><span class="k">const</span> <span class="n">EEndPlayReason</span><span class="o">::</span><span class="n">Type</span> <span class="n">EndPlayReason</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>

<span class="nl">private:</span>
	<span class="n">FRollingBallAsync</span><span class="o">*</span> <span class="n">RollingBallAsync</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>

	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="n">TObjectPtr</span><span class="o">&lt;</span><span class="n">UNetworkPhysicsComponent</span><span class="o">&gt;</span> <span class="n">NetworkPhysicsComponent</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>

	<span class="kt">float</span> <span class="n">ThrottleInput_External</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">SteeringInput_External</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>
	<span class="n">FRotator</span> <span class="n">TravelDirection_External</span> <span class="o">=</span> <span class="n">FRotator</span><span class="o">::</span><span class="n">ZeroRotator</span><span class="p">;</span>
	<span class="n">uint32</span> <span class="n">JumpRequest_External</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">};</span>

<span class="n">USTRUCT</span><span class="p">()</span>
<span class="k">struct</span> <span class="nc">FNetworkBallInputs</span> <span class="o">:</span> <span class="k">public</span> <span class="n">FNetworkPhysicsPayload</span>
<span class="p">{</span>
	<span class="n">GENERATED_BODY</span><span class="p">()</span>

	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="kt">float</span> <span class="n">ThrottleInput</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>

	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="kt">float</span> <span class="n">SteeringInput</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>

	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="n">FRotator</span> <span class="n">TravelDirection</span> <span class="o">=</span> <span class="n">FRotator</span><span class="o">::</span><span class="n">ZeroRotator</span><span class="p">;</span>

	<span class="c1">// Persistent request, so a press isn't lost between game and physics ticks.</span>
	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="n">uint32</span> <span class="n">JumpRequest</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">InterpolateData</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">MinData</span><span class="p">,</span>
		<span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">MaxData</span><span class="p">,</span> <span class="kt">float</span> <span class="n">LerpAlpha</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">MergeData</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">FromData</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">DecayData</span><span class="p">(</span><span class="kt">float</span> <span class="n">DecayAmount</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">bool</span> <span class="n">CompareData</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">PredictedData</span><span class="p">)</span> <span class="k">const</span> <span class="k">override</span><span class="p">;</span>
<span class="p">};</span>

<span class="n">USTRUCT</span><span class="p">()</span>
<span class="k">struct</span> <span class="nc">FNetworkBallStates</span> <span class="o">:</span> <span class="k">public</span> <span class="n">FNetworkPhysicsPayload</span>
<span class="p">{</span>
	<span class="n">GENERATED_BODY</span><span class="p">()</span>

	<span class="c1">// Records which request has already produced an action.</span>
	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="n">uint32</span> <span class="n">LastJumpRequest</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">InterpolateData</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">MinData</span><span class="p">,</span>
		<span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">MaxData</span><span class="p">,</span> <span class="kt">float</span> <span class="n">LerpAlpha</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">bool</span> <span class="n">CompareData</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">PredictedData</span><span class="p">)</span> <span class="k">const</span> <span class="k">override</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">// UE 5.8 addition: the jump effect is a networked physics action.</span>
<span class="n">USTRUCT</span><span class="p">()</span>
<span class="k">struct</span> <span class="nc">FBallJumpAction</span> <span class="o">:</span> <span class="k">public</span> <span class="n">FNetworkPhysicsActionPayload</span>
<span class="p">{</span>
	<span class="n">GENERATED_BODY</span><span class="p">()</span>

	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="n">uint32</span> <span class="n">JumpRequest</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

	<span class="k">virtual</span> <span class="k">const</span> <span class="n">EActionAuthorStyle</span> <span class="n">GetAuthorStyle</span><span class="p">()</span> <span class="k">const</span> <span class="k">override</span>
	<span class="p">{</span>
		<span class="k">return</span> <span class="n">EActionAuthorStyle</span><span class="o">::</span><span class="n">Predicted</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">virtual</span> <span class="kt">bool</span> <span class="n">IsNearlyEqual</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsActionPayload</span><span class="o">&amp;</span> <span class="n">Other</span><span class="p">)</span> <span class="k">const</span> <span class="k">override</span>
	<span class="p">{</span>
		<span class="k">return</span> <span class="n">JumpRequest</span> <span class="o">==</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">FBallJumpAction</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">Other</span><span class="p">).</span><span class="n">JumpRequest</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="nc">FAsyncInputRollingBall</span> <span class="o">:</span> <span class="k">public</span> <span class="n">Chaos</span><span class="o">::</span><span class="n">FSimCallbackInput</span>
<span class="p">{</span>
	<span class="kt">float</span> <span class="n">ThrottleInput</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">SteeringInput</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>
	<span class="n">FRotator</span> <span class="n">TravelDirection</span> <span class="o">=</span> <span class="n">FRotator</span><span class="o">::</span><span class="n">ZeroRotator</span><span class="p">;</span>
	<span class="n">uint32</span> <span class="n">JumpRequest</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

	<span class="kt">void</span> <span class="n">Reset</span><span class="p">()</span>
	<span class="p">{</span>
		<span class="n">ThrottleInput</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>
		<span class="n">SteeringInput</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>
		<span class="n">TravelDirection</span> <span class="o">=</span> <span class="n">FRotator</span><span class="o">::</span><span class="n">ZeroRotator</span><span class="p">;</span>
		<span class="n">JumpRequest</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="nc">FAsyncOutputRollingBall</span> <span class="o">:</span> <span class="k">public</span> <span class="n">Chaos</span><span class="o">::</span><span class="n">FSimCallbackOutput</span>
<span class="p">{</span>
	<span class="kt">void</span> <span class="n">Reset</span><span class="p">()</span> <span class="p">{}</span>
<span class="p">};</span>

<span class="k">class</span> <span class="nc">FRollingBallAsync</span> <span class="o">:</span> <span class="k">public</span> <span class="n">Chaos</span><span class="o">::</span><span class="n">TSimCallbackObject</span><span class="o">&lt;</span><span class="n">FAsyncInputRollingBall</span><span class="p">,</span>
	<span class="n">FAsyncOutputRollingBall</span><span class="p">,</span> <span class="n">Chaos</span><span class="o">::</span><span class="n">ESimCallbackOptions</span><span class="o">::</span><span class="n">Presimulate</span>
	<span class="o">|</span> <span class="n">Chaos</span><span class="o">::</span><span class="n">ESimCallbackOptions</span><span class="o">::</span><span class="n">PhysicsObjectUnregister</span> <span class="o">|</span> <span class="n">Chaos</span><span class="o">::</span><span class="n">ESimCallbackOptions</span><span class="o">::</span><span class="n">Rewind</span><span class="o">&gt;</span>
	<span class="p">,</span> <span class="k">public</span> <span class="n">TNetworkPhysicsInputState_Internal</span><span class="o">&lt;</span><span class="n">FNetworkBallInputs</span><span class="p">,</span> <span class="n">FNetworkBallStates</span><span class="o">&gt;</span>
	<span class="p">,</span> <span class="k">public</span> <span class="n">INetworkPhysicsActionHandler_Internal</span>
<span class="p">{</span>
	<span class="k">friend</span> <span class="n">ARollingBallPawn</span><span class="p">;</span>

	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">OnPostInitialize_Internal</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">ProcessInputs_Internal</span><span class="p">(</span><span class="n">int32</span> <span class="n">PhysicsStep</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">OnPreSimulate_Internal</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">OnPhysicsObjectUnregistered_Internal</span><span class="p">(</span><span class="n">Chaos</span><span class="o">::</span><span class="n">FConstPhysicsObjectHandle</span> <span class="n">Object</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>

	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">BuildInput_Internal</span><span class="p">(</span><span class="n">FNetworkBallInputs</span><span class="o">&amp;</span> <span class="n">Input</span><span class="p">)</span> <span class="k">const</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">ValidateInput_Internal</span><span class="p">(</span><span class="n">FNetworkBallInputs</span><span class="o">&amp;</span> <span class="n">Input</span><span class="p">)</span> <span class="k">const</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">ApplyInput_Internal</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkBallInputs</span><span class="o">&amp;</span> <span class="n">Input</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">BuildState_Internal</span><span class="p">(</span><span class="n">FNetworkBallStates</span><span class="o">&amp;</span> <span class="n">State</span><span class="p">)</span> <span class="k">const</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">ApplyState_Internal</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkBallStates</span><span class="o">&amp;</span> <span class="n">State</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">ApplyAction_Internal</span><span class="p">(</span><span class="k">const</span> <span class="n">TInstancedStruct</span><span class="o">&lt;</span><span class="n">FNetworkPhysicsActionPayload</span><span class="o">&gt;&amp;</span> <span class="n">Action</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>

	<span class="n">Chaos</span><span class="o">::</span><span class="n">FConstPhysicsObjectHandle</span> <span class="n">PhysicsObject</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
	<span class="n">FAsyncNetworkPhysicsComponent</span><span class="o">*</span> <span class="n">NetworkPhysics_Internal</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">ThrottleInput_Internal</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">SteeringInput_Internal</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>
	<span class="n">FRotator</span> <span class="n">TravelDirection_Internal</span> <span class="o">=</span> <span class="n">FRotator</span><span class="o">::</span><span class="n">ZeroRotator</span><span class="p">;</span>
	<span class="n">uint32</span> <span class="n">JumpRequest_Internal</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
	<span class="n">uint32</span> <span class="n">LastJumpRequest_Internal</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">};</span></code></pre></figure>

<p>The layout follows the reference: the pawn holds game-thread input, the payloads describe networked data, the async input/output structs carry data between threads, and <code class="language-plaintext highlighter-rouge">FRollingBallAsync</code> owns the physics-thread logic. The addition for 5.8 is <code class="language-plaintext highlighter-rouge">FBallJumpAction</code> and the <code class="language-plaintext highlighter-rouge">INetworkPhysicsActionHandler_Internal</code> interface. There are no custom serializers here, the payloads describe their networked fields with <code class="language-plaintext highlighter-rouge">UPROPERTY</code>.</p>

<p>Now, <code class="language-plaintext highlighter-rouge">RollingBallPawn.cpp</code>. Each part has a job: the pawn gathers input on the game thread, a marshaled snapshot crosses to the physics thread, and the simulation callback applies forces there.</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="cp">#include</span> <span class="cpf">"RollingBallPawn.h"</span><span class="cp">
</span>
<span class="cp">#include</span> <span class="cpf">"Chaos/PhysicsObjectInternalInterface.h"</span><span class="cp">
#include</span> <span class="cpf">"Components/PrimitiveComponent.h"</span><span class="cp">
#include</span> <span class="cpf">"PBDRigidsSolver.h"</span><span class="cp">
#include</span> <span class="cpf">"Physics/Experimental/PhysScene_Chaos.h"</span><span class="cp">
</span>
<span class="n">ARollingBallPawn</span><span class="o">::</span><span class="n">ARollingBallPawn</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">PrimaryActorTick</span><span class="p">.</span><span class="n">bCanEverTick</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">UPhysicsSettings</span><span class="o">::</span><span class="n">Get</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">PhysicsPrediction</span><span class="p">.</span><span class="n">bEnablePhysicsPrediction</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">NetworkPhysicsComponent</span> <span class="o">=</span> <span class="n">CreateDefaultSubobject</span><span class="o">&lt;</span><span class="n">UNetworkPhysicsComponent</span><span class="o">&gt;</span><span class="p">(</span><span class="n">TEXT</span><span class="p">(</span><span class="s">"NetworkPhysicsComponent"</span><span class="p">));</span>
		<span class="n">NetworkPhysicsComponent</span><span class="o">-&gt;</span><span class="n">SetNetAddressable</span><span class="p">();</span>
		<span class="n">NetworkPhysicsComponent</span><span class="o">-&gt;</span><span class="n">SetIsReplicated</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">ARollingBallPawn</span><span class="o">::</span><span class="n">PostInitializeComponents</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">PostInitializeComponents</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">IsGameWorld</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">UPrimitiveComponent</span><span class="o">*</span> <span class="n">RootSimulatedComponent</span> <span class="o">=</span> <span class="n">Cast</span><span class="o">&lt;</span><span class="n">UPrimitiveComponent</span><span class="o">&gt;</span><span class="p">(</span><span class="n">GetRootComponent</span><span class="p">());</span>
	<span class="n">FPhysScene</span><span class="o">*</span> <span class="n">PhysScene</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetPhysicsScene</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">RootSimulatedComponent</span> <span class="o">||</span> <span class="o">!</span><span class="n">PhysScene</span> <span class="o">||</span> <span class="o">!</span><span class="n">PhysScene</span><span class="o">-&gt;</span><span class="n">GetSolver</span><span class="p">()</span> <span class="o">||</span> <span class="o">!</span><span class="n">NetworkPhysicsComponent</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">RollingBallAsync</span> <span class="o">=</span> <span class="n">PhysScene</span><span class="o">-&gt;</span><span class="n">GetSolver</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">CreateAndRegisterSimCallbackObject_External</span><span class="o">&lt;</span><span class="n">FRollingBallAsync</span><span class="o">&gt;</span><span class="p">();</span>
	<span class="c1">// Initialize these pointers before the callback is handed to the physics thread.</span>
	<span class="n">RollingBallAsync</span><span class="o">-&gt;</span><span class="n">PhysicsObject</span> <span class="o">=</span> <span class="n">RootSimulatedComponent</span><span class="o">-&gt;</span><span class="n">GetPhysicsObjectByName</span><span class="p">(</span><span class="n">NAME_None</span><span class="p">);</span>
	<span class="n">RollingBallAsync</span><span class="o">-&gt;</span><span class="n">NetworkPhysics_Internal</span> <span class="o">=</span> <span class="n">NetworkPhysicsComponent</span><span class="o">-&gt;</span><span class="n">GetNetworkPhysicsComponent_Internal</span><span class="p">();</span>
	<span class="n">NetworkPhysicsComponent</span><span class="o">-&gt;</span><span class="n">CreateDataHistory</span><span class="o">&lt;</span><span class="n">FNetworkBallInputs</span><span class="p">,</span> <span class="n">FNetworkBallStates</span><span class="o">&gt;</span><span class="p">(</span><span class="n">RollingBallAsync</span><span class="p">);</span>
	<span class="n">NetworkPhysicsComponent</span><span class="o">-&gt;</span><span class="n">SetActionHandler</span><span class="p">(</span><span class="n">RollingBallAsync</span><span class="p">);</span>
	<span class="n">NetworkPhysicsComponent</span><span class="o">-&gt;</span><span class="n">SetCompareInputToTriggerRewind</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
	<span class="n">NetworkPhysicsComponent</span><span class="o">-&gt;</span><span class="n">SetCompareStateToTriggerRewind</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">ARollingBallPawn</span><span class="o">::</span><span class="n">Tick</span><span class="p">(</span><span class="kt">float</span> <span class="n">DeltaTime</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">Tick</span><span class="p">(</span><span class="n">DeltaTime</span><span class="p">);</span>
	<span class="c1">// Check local control BEFORE requesting a producer buffer, as in MBobbo's tutorial.</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">RollingBallAsync</span> <span class="o">&amp;&amp;</span> <span class="n">IsLocallyControlled</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">FAsyncInputRollingBall</span><span class="o">*</span> <span class="n">AsyncInput</span> <span class="o">=</span> <span class="n">RollingBallAsync</span><span class="o">-&gt;</span><span class="n">GetProducerInputData_External</span><span class="p">())</span>
		<span class="p">{</span>
			<span class="n">AsyncInput</span><span class="o">-&gt;</span><span class="n">ThrottleInput</span> <span class="o">=</span> <span class="n">ThrottleInput_External</span><span class="p">;</span>
			<span class="n">AsyncInput</span><span class="o">-&gt;</span><span class="n">SteeringInput</span> <span class="o">=</span> <span class="n">SteeringInput_External</span><span class="p">;</span>
			<span class="n">AsyncInput</span><span class="o">-&gt;</span><span class="n">TravelDirection</span> <span class="o">=</span> <span class="n">TravelDirection_External</span><span class="p">;</span>
			<span class="n">AsyncInput</span><span class="o">-&gt;</span><span class="n">JumpRequest</span> <span class="o">=</span> <span class="n">JumpRequest_External</span><span class="p">;</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">ARollingBallPawn</span><span class="o">::</span><span class="n">SetThrottleInput</span><span class="p">(</span><span class="kt">float</span> <span class="n">Value</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">ThrottleInput_External</span> <span class="o">=</span> <span class="n">FMath</span><span class="o">::</span><span class="n">Clamp</span><span class="p">(</span><span class="n">Value</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.</span><span class="n">f</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">ARollingBallPawn</span><span class="o">::</span><span class="n">SetSteeringInput</span><span class="p">(</span><span class="kt">float</span> <span class="n">Value</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">SteeringInput_External</span> <span class="o">=</span> <span class="n">FMath</span><span class="o">::</span><span class="n">Clamp</span><span class="p">(</span><span class="n">Value</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.</span><span class="n">f</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">ARollingBallPawn</span><span class="o">::</span><span class="n">SetTravelDirectionInput</span><span class="p">(</span><span class="n">FRotator</span> <span class="n">Value</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">TravelDirection_External</span> <span class="o">=</span> <span class="n">FRotator</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">FRotator</span><span class="o">::</span><span class="n">NormalizeAxis</span><span class="p">(</span><span class="n">Value</span><span class="p">.</span><span class="n">Yaw</span><span class="p">),</span> <span class="mf">0.0</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">ARollingBallPawn</span><span class="o">::</span><span class="n">Jump</span><span class="p">()</span>
<span class="p">{</span>
	<span class="o">++</span><span class="n">JumpRequest_External</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">ARollingBallPawn</span><span class="o">::</span><span class="n">EndPlay</span><span class="p">(</span><span class="k">const</span> <span class="n">EEndPlayReason</span><span class="o">::</span><span class="n">Type</span> <span class="n">EndPlayReason</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">RollingBallAsync</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="c1">// Stop the network callback before releasing its input/state/action handler.</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">NetworkPhysicsComponent</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">NetworkPhysicsComponent</span><span class="o">-&gt;</span><span class="n">SetActionHandler</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">);</span>
			<span class="n">NetworkPhysicsComponent</span><span class="o">-&gt;</span><span class="n">RemoveDataHistory</span><span class="p">();</span>
			<span class="n">NetworkPhysicsComponent</span><span class="o">-&gt;</span><span class="n">UnregisterComponent</span><span class="p">();</span>
		<span class="p">}</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">FPhysScene</span><span class="o">*</span> <span class="n">PhysScene</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetPhysicsScene</span><span class="p">())</span>
		<span class="p">{</span>
			<span class="k">if</span> <span class="p">(</span><span class="n">Chaos</span><span class="o">::</span><span class="n">FPhysicsSolver</span><span class="o">*</span> <span class="n">Solver</span> <span class="o">=</span> <span class="n">PhysScene</span><span class="o">-&gt;</span><span class="n">GetSolver</span><span class="p">())</span>
			<span class="p">{</span>
				<span class="n">Solver</span><span class="o">-&gt;</span><span class="n">UnregisterAndFreeSimCallbackObject_External</span><span class="p">(</span><span class="n">RollingBallAsync</span><span class="p">);</span>
			<span class="p">}</span>
		<span class="p">}</span>
		<span class="n">RollingBallAsync</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">EndPlay</span><span class="p">(</span><span class="n">EndPlayReason</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FNetworkBallInputs</span><span class="o">::</span><span class="n">InterpolateData</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">MinData</span><span class="p">,</span>
	<span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">MaxData</span><span class="p">,</span> <span class="kt">float</span> <span class="n">LerpAlpha</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="n">FNetworkBallInputs</span><span class="o">&amp;</span> <span class="n">Min</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">FNetworkBallInputs</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">MinData</span><span class="p">);</span>
	<span class="k">const</span> <span class="n">FNetworkBallInputs</span><span class="o">&amp;</span> <span class="n">Max</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">FNetworkBallInputs</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">MaxData</span><span class="p">);</span>
	<span class="n">ThrottleInput</span> <span class="o">=</span> <span class="n">FMath</span><span class="o">::</span><span class="n">Lerp</span><span class="p">(</span><span class="n">Min</span><span class="p">.</span><span class="n">ThrottleInput</span><span class="p">,</span> <span class="n">Max</span><span class="p">.</span><span class="n">ThrottleInput</span><span class="p">,</span> <span class="n">LerpAlpha</span><span class="p">);</span>
	<span class="n">SteeringInput</span> <span class="o">=</span> <span class="n">FMath</span><span class="o">::</span><span class="n">Lerp</span><span class="p">(</span><span class="n">Min</span><span class="p">.</span><span class="n">SteeringInput</span><span class="p">,</span> <span class="n">Max</span><span class="p">.</span><span class="n">SteeringInput</span><span class="p">,</span> <span class="n">LerpAlpha</span><span class="p">);</span>
	<span class="n">TravelDirection</span> <span class="o">=</span> <span class="n">FMath</span><span class="o">::</span><span class="n">Lerp</span><span class="p">(</span><span class="n">Min</span><span class="p">.</span><span class="n">TravelDirection</span><span class="p">,</span> <span class="n">Max</span><span class="p">.</span><span class="n">TravelDirection</span><span class="p">,</span> <span class="n">LerpAlpha</span><span class="p">);</span>
	<span class="n">JumpRequest</span> <span class="o">=</span> <span class="n">LerpAlpha</span> <span class="o">&lt;</span> <span class="mf">1.</span><span class="n">f</span> <span class="o">?</span> <span class="n">Min</span><span class="p">.</span><span class="n">JumpRequest</span> <span class="o">:</span> <span class="n">Max</span><span class="p">.</span><span class="n">JumpRequest</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FNetworkBallInputs</span><span class="o">::</span><span class="n">MergeData</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">FromData</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="n">FNetworkBallInputs</span><span class="o">&amp;</span> <span class="n">From</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">FNetworkBallInputs</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">FromData</span><span class="p">);</span>
	<span class="n">ThrottleInput</span> <span class="o">=</span> <span class="n">FMath</span><span class="o">::</span><span class="n">Max</span><span class="p">(</span><span class="n">ThrottleInput</span><span class="p">,</span> <span class="n">From</span><span class="p">.</span><span class="n">ThrottleInput</span><span class="p">);</span>
	<span class="n">SteeringInput</span> <span class="o">=</span> <span class="n">FMath</span><span class="o">::</span><span class="n">Max</span><span class="p">(</span><span class="n">SteeringInput</span><span class="p">,</span> <span class="n">From</span><span class="p">.</span><span class="n">SteeringInput</span><span class="p">);</span>
	<span class="c1">// Retain the newer frame's direction and discrete request identifier.</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FNetworkBallInputs</span><span class="o">::</span><span class="n">DecayData</span><span class="p">(</span><span class="kt">float</span> <span class="n">DecayAmount</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">ThrottleInput</span> <span class="o">*=</span> <span class="mf">1.</span><span class="n">f</span> <span class="o">-</span> <span class="n">DecayAmount</span><span class="p">;</span>
	<span class="n">SteeringInput</span> <span class="o">*=</span> <span class="mf">1.</span><span class="n">f</span> <span class="o">-</span> <span class="n">DecayAmount</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">FNetworkBallInputs</span><span class="o">::</span><span class="n">CompareData</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">PredictedData</span><span class="p">)</span> <span class="k">const</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="n">FNetworkBallInputs</span><span class="o">&amp;</span> <span class="n">Predicted</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">FNetworkBallInputs</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">PredictedData</span><span class="p">);</span>
	<span class="k">return</span> <span class="n">ThrottleInput</span> <span class="o">==</span> <span class="n">Predicted</span><span class="p">.</span><span class="n">ThrottleInput</span>
		<span class="o">&amp;&amp;</span> <span class="n">SteeringInput</span> <span class="o">==</span> <span class="n">Predicted</span><span class="p">.</span><span class="n">SteeringInput</span>
		<span class="o">&amp;&amp;</span> <span class="n">TravelDirection</span> <span class="o">==</span> <span class="n">Predicted</span><span class="p">.</span><span class="n">TravelDirection</span>
		<span class="o">&amp;&amp;</span> <span class="n">JumpRequest</span> <span class="o">==</span> <span class="n">Predicted</span><span class="p">.</span><span class="n">JumpRequest</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FNetworkBallStates</span><span class="o">::</span><span class="n">InterpolateData</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">MinData</span><span class="p">,</span>
	<span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">MaxData</span><span class="p">,</span> <span class="kt">float</span> <span class="n">LerpAlpha</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="n">FNetworkBallStates</span><span class="o">&amp;</span> <span class="n">Min</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">FNetworkBallStates</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">MinData</span><span class="p">);</span>
	<span class="k">const</span> <span class="n">FNetworkBallStates</span><span class="o">&amp;</span> <span class="n">Max</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">FNetworkBallStates</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">MaxData</span><span class="p">);</span>
	<span class="n">LastJumpRequest</span> <span class="o">=</span> <span class="n">LerpAlpha</span> <span class="o">&lt;</span> <span class="mf">1.</span><span class="n">f</span> <span class="o">?</span> <span class="n">Min</span><span class="p">.</span><span class="n">LastJumpRequest</span> <span class="o">:</span> <span class="n">Max</span><span class="p">.</span><span class="n">LastJumpRequest</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">FNetworkBallStates</span><span class="o">::</span><span class="n">CompareData</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsPayload</span><span class="o">&amp;</span> <span class="n">PredictedData</span><span class="p">)</span> <span class="k">const</span>
<span class="p">{</span>
	<span class="k">return</span> <span class="n">LastJumpRequest</span> <span class="o">==</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">FNetworkBallStates</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">PredictedData</span><span class="p">).</span><span class="n">LastJumpRequest</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FRollingBallAsync</span><span class="o">::</span><span class="n">OnPostInitialize_Internal</span><span class="p">()</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">PhysicsObject</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">Chaos</span><span class="o">::</span><span class="n">FWritePhysicsObjectInterface_Internal</span> <span class="n">Interface</span> <span class="o">=</span> <span class="n">Chaos</span><span class="o">::</span><span class="n">FPhysicsObjectInternalInterface</span><span class="o">::</span><span class="n">GetWrite</span><span class="p">();</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">Chaos</span><span class="o">::</span><span class="n">FPBDRigidParticleHandle</span><span class="o">*</span> <span class="n">ParticleHandle</span> <span class="o">=</span> <span class="n">Interface</span><span class="p">.</span><span class="n">GetRigidParticle</span><span class="p">(</span><span class="n">PhysicsObject</span><span class="p">))</span>
		<span class="p">{</span>
			<span class="n">ParticleHandle</span><span class="o">-&gt;</span><span class="n">SetSleepType</span><span class="p">(</span><span class="n">Chaos</span><span class="o">::</span><span class="n">ESleepType</span><span class="o">::</span><span class="n">NeverSleep</span><span class="p">);</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FRollingBallAsync</span><span class="o">::</span><span class="n">ProcessInputs_Internal</span><span class="p">(</span><span class="n">int32</span> <span class="n">PhysicsStep</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="n">FAsyncInputRollingBall</span><span class="o">*</span> <span class="n">AsyncInput</span> <span class="o">=</span> <span class="n">GetConsumerInput_Internal</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">AsyncInput</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="n">Chaos</span><span class="o">::</span><span class="n">FPhysicsSolverBase</span><span class="o">*</span> <span class="n">BaseSolver</span> <span class="o">=</span> <span class="n">GetSolver</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">BaseSolver</span> <span class="o">||</span> <span class="n">BaseSolver</span><span class="o">-&gt;</span><span class="n">IsResimming</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="c1">// Historical/networked controls are supplied by ApplyInput_Internal.</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="n">ThrottleInput_Internal</span> <span class="o">=</span> <span class="n">AsyncInput</span><span class="o">-&gt;</span><span class="n">ThrottleInput</span><span class="p">;</span>
	<span class="n">SteeringInput_Internal</span> <span class="o">=</span> <span class="n">AsyncInput</span><span class="o">-&gt;</span><span class="n">SteeringInput</span><span class="p">;</span>
	<span class="n">TravelDirection_Internal</span> <span class="o">=</span> <span class="n">AsyncInput</span><span class="o">-&gt;</span><span class="n">TravelDirection</span><span class="p">;</span>
	<span class="n">JumpRequest_Internal</span> <span class="o">=</span> <span class="n">AsyncInput</span><span class="o">-&gt;</span><span class="n">JumpRequest</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FRollingBallAsync</span><span class="o">::</span><span class="n">OnPreSimulate_Internal</span><span class="p">()</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">PhysicsObject</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="n">Chaos</span><span class="o">::</span><span class="n">FWritePhysicsObjectInterface_Internal</span> <span class="n">Interface</span> <span class="o">=</span> <span class="n">Chaos</span><span class="o">::</span><span class="n">FPhysicsObjectInternalInterface</span><span class="o">::</span><span class="n">GetWrite</span><span class="p">();</span>
	<span class="n">Chaos</span><span class="o">::</span><span class="n">FPBDRigidParticleHandle</span><span class="o">*</span> <span class="n">ParticleHandle</span> <span class="o">=</span> <span class="n">Interface</span><span class="p">.</span><span class="n">GetRigidParticle</span><span class="p">(</span><span class="n">PhysicsObject</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ParticleHandle</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">const</span> <span class="n">FVector</span> <span class="n">Forward</span> <span class="o">=</span> <span class="n">TravelDirection_Internal</span><span class="p">.</span><span class="n">Vector</span><span class="p">();</span>
	<span class="k">const</span> <span class="n">FVector</span> <span class="n">Right</span> <span class="o">=</span> <span class="n">FRotationMatrix</span><span class="p">(</span><span class="n">TravelDirection_Internal</span><span class="p">).</span><span class="n">GetUnitAxis</span><span class="p">(</span><span class="n">EAxis</span><span class="o">::</span><span class="n">Y</span><span class="p">);</span>
	<span class="k">const</span> <span class="n">FVector</span> <span class="n">Force</span> <span class="o">=</span> <span class="p">(</span><span class="n">ThrottleInput_Internal</span> <span class="o">*</span> <span class="n">Forward</span> <span class="o">+</span> <span class="n">SteeringInput_Internal</span> <span class="o">*</span> <span class="n">Right</span><span class="p">)</span> <span class="o">*</span> <span class="mf">80000.</span><span class="n">f</span><span class="p">;</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">Force</span><span class="p">.</span><span class="n">SizeSquared</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">UE_SMALL_NUMBER</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">ParticleHandle</span><span class="o">-&gt;</span><span class="n">AddForce</span><span class="p">(</span><span class="n">Force</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">NetworkPhysics_Internal</span> <span class="o">&amp;&amp;</span> <span class="n">JumpRequest_Internal</span> <span class="o">!=</span> <span class="n">LastJumpRequest_Internal</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">FBallJumpAction</span> <span class="n">Action</span><span class="p">;</span>
		<span class="n">Action</span><span class="p">.</span><span class="n">JumpRequest</span> <span class="o">=</span> <span class="n">JumpRequest_Internal</span><span class="p">;</span>
		<span class="c1">// Author on both client and server, including during resimulation.</span>
		<span class="k">const</span> <span class="n">int32</span> <span class="n">NextFrame</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">Chaos</span><span class="o">::</span><span class="n">FPhysicsSolver</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">GetSolver</span><span class="p">())</span><span class="o">-&gt;</span><span class="n">GetCurrentFrame</span><span class="p">()</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
		<span class="n">NetworkPhysics_Internal</span><span class="o">-&gt;</span><span class="n">EnqueueScheduledActionAtFrame_Internal</span><span class="p">(</span><span class="n">Action</span><span class="p">,</span> <span class="n">uint32</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="n">NextFrame</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
		<span class="n">LastJumpRequest_Internal</span> <span class="o">=</span> <span class="n">JumpRequest_Internal</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FRollingBallAsync</span><span class="o">::</span><span class="n">ApplyAction_Internal</span><span class="p">(</span><span class="k">const</span> <span class="n">TInstancedStruct</span><span class="o">&lt;</span><span class="n">FNetworkPhysicsActionPayload</span><span class="o">&gt;&amp;</span> <span class="n">Action</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">PhysicsObject</span> <span class="o">||</span> <span class="o">!</span><span class="n">Action</span><span class="p">.</span><span class="n">GetPtr</span><span class="o">&lt;</span><span class="n">FBallJumpAction</span><span class="o">&gt;</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="n">Chaos</span><span class="o">::</span><span class="n">FWritePhysicsObjectInterface_Internal</span> <span class="n">Interface</span> <span class="o">=</span> <span class="n">Chaos</span><span class="o">::</span><span class="n">FPhysicsObjectInternalInterface</span><span class="o">::</span><span class="n">GetWrite</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">Chaos</span><span class="o">::</span><span class="n">FPBDRigidParticleHandle</span><span class="o">*</span> <span class="n">ParticleHandle</span> <span class="o">=</span> <span class="n">Interface</span><span class="p">.</span><span class="n">GetRigidParticle</span><span class="p">(</span><span class="n">PhysicsObject</span><span class="p">))</span>
	<span class="p">{</span>
		<span class="c1">// The action, rather than the request, applies a 500 cm/s upward velocity change.</span>
		<span class="n">ParticleHandle</span><span class="o">-&gt;</span><span class="n">SetLinearImpulseVelocity</span><span class="p">(</span>
			<span class="n">ParticleHandle</span><span class="o">-&gt;</span><span class="n">LinearImpulseVelocity</span><span class="p">()</span> <span class="o">+</span> <span class="n">FVector</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">500</span><span class="p">),</span> <span class="nb">true</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FRollingBallAsync</span><span class="o">::</span><span class="n">OnPhysicsObjectUnregistered_Internal</span><span class="p">(</span><span class="n">Chaos</span><span class="o">::</span><span class="n">FConstPhysicsObjectHandle</span> <span class="n">Object</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">PhysicsObject</span> <span class="o">==</span> <span class="n">Object</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">PhysicsObject</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FRollingBallAsync</span><span class="o">::</span><span class="n">BuildInput_Internal</span><span class="p">(</span><span class="n">FNetworkBallInputs</span><span class="o">&amp;</span> <span class="n">Input</span><span class="p">)</span> <span class="k">const</span>
<span class="p">{</span>
	<span class="n">Input</span><span class="p">.</span><span class="n">ThrottleInput</span> <span class="o">=</span> <span class="n">ThrottleInput_Internal</span><span class="p">;</span>
	<span class="n">Input</span><span class="p">.</span><span class="n">SteeringInput</span> <span class="o">=</span> <span class="n">SteeringInput_Internal</span><span class="p">;</span>
	<span class="n">Input</span><span class="p">.</span><span class="n">TravelDirection</span> <span class="o">=</span> <span class="n">TravelDirection_Internal</span><span class="p">;</span>
	<span class="n">Input</span><span class="p">.</span><span class="n">JumpRequest</span> <span class="o">=</span> <span class="n">JumpRequest_Internal</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FRollingBallAsync</span><span class="o">::</span><span class="n">ValidateInput_Internal</span><span class="p">(</span><span class="n">FNetworkBallInputs</span><span class="o">&amp;</span> <span class="n">Input</span><span class="p">)</span> <span class="k">const</span>
<span class="p">{</span>
	<span class="n">Input</span><span class="p">.</span><span class="n">ThrottleInput</span> <span class="o">=</span> <span class="n">FMath</span><span class="o">::</span><span class="n">Clamp</span><span class="p">(</span><span class="n">Input</span><span class="p">.</span><span class="n">ThrottleInput</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.</span><span class="n">f</span><span class="p">);</span>
	<span class="n">Input</span><span class="p">.</span><span class="n">SteeringInput</span> <span class="o">=</span> <span class="n">FMath</span><span class="o">::</span><span class="n">Clamp</span><span class="p">(</span><span class="n">Input</span><span class="p">.</span><span class="n">SteeringInput</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.</span><span class="n">f</span><span class="p">);</span>
	<span class="n">Input</span><span class="p">.</span><span class="n">TravelDirection</span> <span class="o">=</span> <span class="n">FRotator</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">FRotator</span><span class="o">::</span><span class="n">NormalizeAxis</span><span class="p">(</span><span class="n">Input</span><span class="p">.</span><span class="n">TravelDirection</span><span class="p">.</span><span class="n">Yaw</span><span class="p">),</span> <span class="mf">0.0</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FRollingBallAsync</span><span class="o">::</span><span class="n">ApplyInput_Internal</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkBallInputs</span><span class="o">&amp;</span> <span class="n">Input</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">ThrottleInput_Internal</span> <span class="o">=</span> <span class="n">Input</span><span class="p">.</span><span class="n">ThrottleInput</span><span class="p">;</span>
	<span class="n">SteeringInput_Internal</span> <span class="o">=</span> <span class="n">Input</span><span class="p">.</span><span class="n">SteeringInput</span><span class="p">;</span>
	<span class="n">TravelDirection_Internal</span> <span class="o">=</span> <span class="n">Input</span><span class="p">.</span><span class="n">TravelDirection</span><span class="p">;</span>
	<span class="n">JumpRequest_Internal</span> <span class="o">=</span> <span class="n">Input</span><span class="p">.</span><span class="n">JumpRequest</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FRollingBallAsync</span><span class="o">::</span><span class="n">BuildState_Internal</span><span class="p">(</span><span class="n">FNetworkBallStates</span><span class="o">&amp;</span> <span class="n">State</span><span class="p">)</span> <span class="k">const</span>
<span class="p">{</span>
	<span class="n">State</span><span class="p">.</span><span class="n">LastJumpRequest</span> <span class="o">=</span> <span class="n">LastJumpRequest_Internal</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">FRollingBallAsync</span><span class="o">::</span><span class="n">ApplyState_Internal</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkBallStates</span><span class="o">&amp;</span> <span class="n">State</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">LastJumpRequest_Internal</span> <span class="o">=</span> <span class="n">State</span><span class="p">.</span><span class="n">LastJumpRequest</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>One detail that’s easy to miss: <code class="language-plaintext highlighter-rouge">Tick</code> checks <code class="language-plaintext highlighter-rouge">IsLocallyControlled()</code> <strong>before</strong> requesting a producer buffer, just like MBobbo’s implementation. Remote pawns get their input through <code class="language-plaintext highlighter-rouge">ApplyInput_Internal</code>. During resimulation, the autonomous pawn also uses that recorded input, so <code class="language-plaintext highlighter-rouge">ProcessInputs_Internal</code> leaves its async input alone.</p>

<p class="notice--info"><strong>Note:</strong> This is still a “hello world”. It clamps continuous input, but doesn’t implement grounded checks, jump cooldowns or gameplay validation of jump requests.</p>

<h3 id="connecting-the-controls">Connecting the controls</h3>

<p>Set your Blueprint child as the GameMode’s Default Pawn Class, so the server spawns and possesses one pawn for each player. The owning client needs possession for its input RPCs to reach the server.</p>

<p>From your locally controlled pawn’s input events:</p>

<ul>
  <li>Call <code class="language-plaintext highlighter-rouge">SetThrottleInput</code> and <code class="language-plaintext highlighter-rouge">SetSteeringInput</code> with values in <code class="language-plaintext highlighter-rouge">[-1, 1]</code>. With Enhanced Input, also send zero on <code class="language-plaintext highlighter-rouge">Completed</code> / <code class="language-plaintext highlighter-rouge">Canceled</code>, so releasing a key doesn’t leave the last value held down.</li>
  <li>Call <code class="language-plaintext highlighter-rouge">SetTravelDirectionInput</code> with the controller or camera rotation. This example uses yaw only, so looking up doesn’t make the ball fly.</li>
  <li>Call <code class="language-plaintext highlighter-rouge">Jump</code> once on the jump action’s <code class="language-plaintext highlighter-rouge">Started</code> event, not every frame while it is held.</li>
</ul>

<p>The camera and input mapping are yours to choose. Keep the camera on a spring arm or another setup that doesn’t inherit the rolling mesh’s rotation unless that’s the ride you want, hehe.</p>

<p>If everything is connected, we’re aiming for the behaviour shown in this video:</p>

<iframe width="480" height="270" src="https://www.youtube.com/embed/kFtZqNlcg3U" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>

<p>The video shows the boulder with latency applied to the server and client. At the end I display the difference between jump inputs being sent through the <code class="language-plaintext highlighter-rouge">NetworkPhysicsComponent</code>, and jumps performed only on the client; you can see how the latter get corrected, making the local sphere snap back to the ground.</p>

<h3 id="implementation-details">Implementation details</h3>

<p>Now that we are all on the same page, let’s connect this back to MBobbo’s tutorial.</p>

<p>We create the physics callback and register <code class="language-plaintext highlighter-rouge">CreateDataHistory&lt;FNetworkBallInputs, FNetworkBallStates&gt;</code> in <code class="language-plaintext highlighter-rouge">PostInitializeComponents</code>, as he does. The <code class="language-plaintext highlighter-rouge">_External</code> variables belong to the game thread; the <code class="language-plaintext highlighter-rouge">_Internal</code> variables belong to the physics thread. The async input struct carries the local player’s controls between them.</p>

<p>We also use his callback flags: <code class="language-plaintext highlighter-rouge">Presimulate</code>, <code class="language-plaintext highlighter-rouge">PhysicsObjectUnregister</code> and <code class="language-plaintext highlighter-rouge">Rewind</code>, with <code class="language-plaintext highlighter-rouge">ProcessInputs_Internal</code> processing the marshaled input. UE 5.8.2 supports this flow; its native <code class="language-plaintext highlighter-rouge">ProcessInputsInternal</code> flag is optional for this setup.</p>

<p>The flow remains: apply networked or historical input, process fresh local input when appropriate, build the input history, then simulate. <code class="language-plaintext highlighter-rouge">OnPreSimulate_Internal</code> applies forces through the rigid particle handle.</p>

<p>Our added travel direction and jump request have their own handling: retain the newer direction when merging, and never interpolate or decay a request identifier as if it were a movement axis.</p>

<p>UE 5.8 records custom state after the solve by default and stamps it for the beginning of the next physics frame. We let the engine manage that timing.</p>

<h3 id="jumping-with-ue-58-networked-actions">Jumping with UE 5.8 networked actions</h3>

<p>Here’s the new part! UE 5.8 adds actions alongside inputs and states. We use <code class="language-plaintext highlighter-rouge">FBallJumpAction</code> for the jump effect and register our callback with <code class="language-plaintext highlighter-rouge">SetActionHandler</code>. The <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-engine-5-8-release-notes">UE 5.8 release notes</a> describe actions being predicted, confirmed by the server and applied during resimulation.</p>

<p>The jump follows three steps:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">Jump()</code> advances a request identifier on the game thread. That request travels in the normal input payload, so both the client and server can produce the action from the same input stream.</li>
  <li>In <code class="language-plaintext highlighter-rouge">OnPreSimulate_Internal</code>, a new request produces <code class="language-plaintext highlighter-rouge">FBallJumpAction</code> through <code class="language-plaintext highlighter-rouge">EnqueueScheduledActionAtFrame_Internal</code>. Its author style is <strong>Predicted</strong>: clients predict it, and the server independently authors the authoritative action. <code class="language-plaintext highlighter-rouge">LastJumpRequest</code> is custom rewind state so replay can regenerate the action from historical input.</li>
  <li><code class="language-plaintext highlighter-rouge">ApplyAction_Internal</code> applies the upward velocity change. The movement callback doesn’t also apply a jump impulse; doing both would jump twice!</li>
</ol>

<p><strong>Timing matters:</strong> in 5.8.2, action dispatch happens before our <code class="language-plaintext highlighter-rouge">OnPreSimulate_Internal</code> callback. We explicitly schedule the action for <strong>the next physics frame</strong>. This also covers the first action, before the component has an initialized action history. The scheduling API converts the local frame to the server timeline; we don’t invoke the handler ourselves or manually overwrite <code class="language-plaintext highlighter-rouge">ServerFrame</code>.</p>

<p>We must also keep authoring actions during resimulation. Only fresh game-thread input is skipped during replay; the simulation still needs to regenerate its predictions. Server-confirmed actions replace matching predictions and are replayed from action history.</p>

<p class="notice--info">While testing, <code class="language-plaintext highlighter-rouge">np2.Resim.NetworkedActions.EnableDebugLogs=1</code> lets you follow action prediction, server authoring and confirmation in the logs.</p>

<p>With <strong>Predicted</strong> actions, enqueuing only from the client’s Blueprint jump event would leave the server without its own action. The server must independently produce that action from the request. <strong>Proposed</strong> actions can send a client’s candidate to the server, but even then the server must author a matching action and compare it before accepting the proposal. Neither style automatically grants a client permission to jump. Our normal input path supplies the request, and the physics simulation authors the action on both machines.</p>

<p>This toy example allows an air jump for each observed new request. Multiple presses folded into one input still produce one action. Grounded checks, cooldowns and other rules belong in the simulation before authoring the action, with any persistent gameplay state included in the rewind state.</p>

<p>The 5.8 additions were checked in <code class="language-plaintext highlighter-rouge">Engine/Source/Runtime/Engine/Public/Physics/NetworkPhysicsComponent.h</code> and its implementation under <code class="language-plaintext highlighter-rouge">Private/PhysicsEngine</code>. For the base implementation and its settings, keep <a href="https://dev.epicgames.com/community/learning/tutorials/MoBq/unreal-engine-networked-physics-pawn-tutorial">MBobbo’s original tutorial</a> as the reference.</p>

<h1 id="additional-resources">Additional resources</h1>

<p>Besides this post and MBobbo’s tutorial, you can also take a look at systems from the engine that use this technology:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">UChaosVehicleMovementComponent</code>: Chaos simple vehicle system. It demonstrates traits-based registration, another approach you’ll encounter in engine code.</li>
  <li><code class="language-plaintext highlighter-rouge">UChaosMoverBackendComponent</code>: the networked physics backend in the <strong>ChaosMover</strong> plugin. For more background, see the physics-based <a href="https://vorixo.github.io/devtricks/mover-show/">Mover</a> article.</li>
</ul>

<p>Finally, I strongly recommend everyone to take a look at Epic’s GDC talk about Chaos, where they talk a bit about their physics system:</p>

<iframe width="480" height="270" src="https://www.youtube.com/embed/WPsRfZ8rxOg?start=2319" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>

<p>Thanks Epic!</p>

<h1 id="conclusion">Conclusion</h1>

<p>Today we explored together the <code class="language-plaintext highlighter-rouge">UNetworkPhysicsComponent</code> and built our “hello world” physics boulder in UE 5.8, following MBobbo’s implementation and adding networked actions for jumping. The ball is still simple; the important part is understanding which input drives each physics frame, and which gameplay state needs to come back when we rewind.</p>

<p>The <a href="https://github.com/vorixo/ExperimentalArcadeVehicleSampleProject">experimental arcade vehicle sample</a> repo is an additional resource. Check its engine version before copying code. Feel free to open issues or pull requests there, the main intention is to create a “hello world” example the community can learn from.</p>

<p>As always, feel free to <a href="https://twitter.com/vorixo">contact me</a> (and follow me! hehe) if you find any issues with the article or have any questions about the whole topic.</p>

<p>Enjoy, vori.</p>]]></content><author><name>Alvaro Jover-Alvarez</name></author><category term="Videogames Development" /><category term="Multiplayer" /><category term="UE5" /><category term="Physics Prediction" /><category term="Networking" /><summary type="html"><![CDATA[In this post we'll build a rolling physics boulder with UNetworkPhysicsComponent, physics prediction and resimulation in Unreal Engine 5.8.]]></summary></entry><entry><title type="html">How to use the Contextual Animation Plugin in your Multiplayer Games (UE 5.3+) w/ Doğa</title><link href="https://vorixo.github.io/devtricks/contextual-anim/" rel="alternate" type="text/html" title="How to use the Contextual Animation Plugin in your Multiplayer Games (UE 5.3+) w/ Doğa" /><published>2024-03-01T00:00:00+00:00</published><updated>2024-03-01T00:00:00+00:00</updated><id>https://vorixo.github.io/devtricks/contextual-anim</id><content type="html" xml:base="https://vorixo.github.io/devtricks/contextual-anim/"><![CDATA[<p>In this post we will learn how to use the Contextual Animation Plugin for our synced animations. This article wouldn’t be possible without <a href="https://github.com/dyanikoglu">Doğa</a>’s contributions.</p>

<h1 id="introduction">Introduction</h1>

<p>Unreal Engine released with a Contextual Animation System to create synced animations for specific actions, we could hear about it first in the Unreal Engine 5 revelation video:</p>

<iframe width="480" height="270" src="https://www.youtube.com/embed/qC5KtatMcUw?start=307" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>

<p>This system allows us to play animations that require some sort of synchronization, in both transform, and timing. In order to activate the feature, open <code class="language-plaintext highlighter-rouge">Edit</code> and within <code class="language-plaintext highlighter-rouge">Plugins</code> windows, turn on <code class="language-plaintext highlighter-rouge">Motion Warping</code> and <code class="language-plaintext highlighter-rouge">Contextual Animation</code>.</p>

<h1 id="how-to-setup-a-contextual-animation">How to setup a Contextual Animation</h1>

<p><strong>All the Actors</strong> part of a Contextual Animation require to have a <code class="language-plaintext highlighter-rouge">ContextualAnimSceneActorComponent</code>. The Plugin handles alignment of these Actors using <a href="https://docs.unrealengine.com/5.0/en-US/motion-warping-in-unreal-engine/">Motion Warping</a>. Therefore, <strong>only the Actor initiating the interaction</strong> and requiring synchronization towards the designated transform needs to be equipped with a <code class="language-plaintext highlighter-rouge">MotionWarpingComponent</code>.</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/contextual-anim/actor-comps.jpg" alt="Contextual Animation Components" class="align-center" /></p>

<h2 id="contextual-anim-scene">Contextual Anim Scene</h2>

<p>Once we have granted all the required components to our Actors we can create our first Contextual Anim Scene Asset. For that, create a <code class="language-plaintext highlighter-rouge">Contextual Anim Scene</code> (CAS) asset in the Content Browser.</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/contextual-anim/creating-cas.jpg" alt="Creating CAS Asset" class="align-center" /></p>

<p>If we open the newly created asset, we’ll find a viewport, a timeline and a series of properties we’ll tackle next. But first let’s focus on the <code class="language-plaintext highlighter-rouge">Roles Asset</code>.</p>

<p>This Data Asset defines the multiple roles by name in our Contextual Animation as in the image below.</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/contextual-anim/cad-roles.jpg" alt="Roles Asset" class="align-center" /></p>

<p>We recommend to provide somewhat generic names, as these assets are meant to be reused amongst multiple <code class="language-plaintext highlighter-rouge">Contextual Anim Scenes</code>. In the image <code class="language-plaintext highlighter-rouge">Interactor</code> is the Actor that starts the interaction, and <code class="language-plaintext highlighter-rouge">Target</code> is the Actor we interact with.</p>

<p>Then, in the CAS asset we assign the <code class="language-plaintext highlighter-rouge">Roles Asset</code> we just created, and for our example, we set <code class="language-plaintext highlighter-rouge">Target</code> as the <code class="language-plaintext highlighter-rouge">Primary Role</code> (the Actor we want to interact with). Then, add as many entries to the <code class="language-plaintext highlighter-rouge">Override Preview Data</code> array as participants are in our contextual animation, defined also by the <code class="language-plaintext highlighter-rouge">Roles Asset</code>.</p>

<p>In our case we have two participants, so we extend the array with two elements as follows:</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/contextual-anim/overridepreview.jpg" alt="Override preview data" class="align-center" /></p>

<p>In my case, the female mannequin starts the interaction.</p>

<p class="notice--info"><strong>Note:</strong> Contextual Animations can also be used against non-animated Actors (ie: sitting on a chair), which would also need to be part of your Roles Asset.</p>

<h2 id="animation-sequence-requirements-and-montages">Animation Sequence requirements (and Montages)</h2>

<p>After configuring our CAS asset with all the required properties we are ready to start creating animation tracks. But before we start, let’s take a look at the requirements of the Animation Sequences composing our animation track.</p>

<p>The Contextual Animations System employs <a href="https://docs.unrealengine.com/en-US/motion-warping-in-unreal-engine/">motion warping</a> internally to warp the selected participants to a specific Transform. This ensures that animations are played correctly at the intended time and position, allowing for perfectly synced animations during gameplay, consistent with the original vision from the animation software (Maya, Blender…).</p>

<p>In our case, the <code class="language-plaintext highlighter-rouge">Interactor</code> should move towards <code class="language-plaintext highlighter-rouge">Target</code> before starting our takedown animation, for that we’ll enable <a href="https://docs.unrealengine.com/en-US/root-motion-in-unreal-engine/">Root Motion</a> in the Animation Sequence of the Montage of our <code class="language-plaintext highlighter-rouge">Interactor</code> and then, we’ll add a <code class="language-plaintext highlighter-rouge">Motion Warping Anim Notify State</code> for the duration that we want the warp to last (the shorter, the faster our character will reach to the destination to play the Contextual Animation).</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/contextual-anim/montagewarping.jpg" alt="Override preview data" class="align-center" /></p>

<p>The <code class="language-plaintext highlighter-rouge">Warp Target Name</code> will be used later to define the synchronization transform in the CAS asset.</p>

<p class="notice--info"><strong>Note:</strong> Visit the <a href="https://docs.unrealengine.com/en-US/motion-warping-in-unreal-engine/">motion warping documentation</a> to know more about this feature and all the variables you’ll find in the <code class="language-plaintext highlighter-rouge">Anim Notify State</code>.</p>

<p>In our case, our <code class="language-plaintext highlighter-rouge">Target</code> Montage doesn’t need any additional configuration. However, if any character animation within the CAS Asset entails movement, Root Motion will be essential for their Montages, so that the character will move alongside the Root Bone’s movement.</p>

<p class="notice--info"><strong>Note:</strong> Each animation track in the CAS asset consists of the combination of animations for all of our participants.</p>

<h2 id="creating-a-new-track">Creating a new track</h2>

<p>To create a new animation track click on the button <code class="language-plaintext highlighter-rouge">New AnimSet</code> located at the top of the CAS Asset; it will promp you a window in which you can add a montage per participant (if applicable).</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/contextual-anim/newanimset.jpg" alt="New AnimSet" class="align-center" /></p>

<p>You can also tweak the Collision Behaviour between the participants and their attachment logic. For example, in the case of a takedown animation we would insert the attacker’s montage in the <code class="language-plaintext highlighter-rouge">Interactor</code> animation and the death montage in the <code class="language-plaintext highlighter-rouge">Target</code> animation.</p>

<p>After adding the montages, you can click on them in the animation track to adjust further parameters. Here we’ll find <code class="language-plaintext highlighter-rouge">Mesh to scene</code>, which will let you adjust the transform of your Actor to play the synced animation properly.</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/contextual-anim/animsetparams.jpg" alt="AnimSet parameters" class="align-center" /></p>

<p>If done properly you’ll have something like this:</p>

<p><img src="https://vorixo.github.io/devtricks/assets/images/per-post/contextual-anim/takedown.gif" alt="AnimSet in Action" class="align-center" /></p>

<p>Our preview looks great! But if we want this to work during gameplay we have to configure motion warping in our CAS Asset. For that, add a new member on the <code class="language-plaintext highlighter-rouge">Warp Point Definitions</code> array, then on its properties write the name you’ve wrote in <code class="language-plaintext highlighter-rouge">Warp Target Name</code> in the previous section using the <code class="language-plaintext highlighter-rouge">Primary Actor</code> (Target) as the Mode, and finally, click on <code class="language-plaintext highlighter-rouge">Update Warp Points</code>. This will add a warping point in our <code class="language-plaintext highlighter-rouge">Target</code> Actor which will make the <code class="language-plaintext highlighter-rouge">Motion Warping</code> configured in our montage warp towards it.</p>

<p><img src="https://vorixo.github.io/devtricks/assets/images/per-post/contextual-anim/warping.jpg" alt="Warping configuration" class="align-center" /></p>

<p>Once done, we can add more AnimSets if we’d like to add varied montages to this Contextual Animation (ie: different takedown animations), the <code class="language-plaintext highlighter-rouge">Selection Criteria</code> will ensure we play a proper one.</p>

<p><img src="https://vorixo.github.io/devtricks/assets/images/per-post/contextual-anim/multipleanimsets.jpg" alt="Multiple AnimSets" class="align-center" /></p>

<h2 id="contextual-animation-selection-criteria">Contextual Animation Selection Criteria</h2>

<p>As previously indicated, a CAS can have multiple AnimSets, where each can contain its own set of Selection Criterias. In Contextual Animation Assets (CAS), the Selection Criteria sets the rules for determining which Anim Set fits the bill.</p>

<p>In simple terms, these Selection Criterias help pick out the right AnimSet for the job. At the beginning of the execution, the AnimSets undergo evaluation in a random order. The first one that meets all the Selection Criteria is chosen. But if none of them meet the criteria, no one will get selected.</p>

<h3 id="adding-a-selection-criteria">Adding a Selection Criteria</h3>

<p>To add a selection criteria, navigate to the Montage added within your CAS. Within it, you’ll discover a Selection Criteria array.</p>

<p><img src="https://vorixo.github.io/devtricks/assets/images/per-post/contextual-anim/selectioncriteria.jpg" alt="Selection Criteria" class="align-center" /></p>

<p>Within this array, you can incorporate multiple selection criterias, which can be defined using both C++ and Blueprints. The plugin comes with a few:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">Contextual Anim Selection Criterion Cone</code>: Checks if the target is inside a user defined Cone. If it is, the Criteria <strong>passes</strong>.</li>
  <li><code class="language-plaintext highlighter-rouge">Contextual Anim Selection Criterion Distance</code>: Checks that the target is withing a distance range, can be evaluated in 3D or 2D.</li>
  <li><code class="language-plaintext highlighter-rouge">Contextual Anim Selection Criterion Trigger Area</code>: Checks if the target is in a user defined 3D area, if it is, the Criterion <strong>passes</strong>.</li>
</ul>

<p>Some of these criterias come with debug visualization.</p>

<h1 id="how-to-play-a-contextual-animation-during-gameplay">How to play a Contextual Animation during gameplay</h1>

<p>Playing Contextual Animations in Blueprints and C++ is very easy, all it takes is 2 functions:</p>

<p><img src="https://vorixo.github.io/devtricks/assets/images/per-post/contextual-anim/callingcontextual.jpg" alt="Playing a Contextual Animation" class="align-center" /></p>

<p>In the example above I’m playing the contextual animation inside an Ability instigated by the attacker, therefore <code class="language-plaintext highlighter-rouge">Primary</code> will be the Victim and <code class="language-plaintext highlighter-rouge">Secondary</code> the attacker, like in the CAS. Once our bindings are setup, we can start the Contextual Anim Scene.</p>

<h2 id="extra-functionalities">Extra functionalities</h2>

<p>The <code class="language-plaintext highlighter-rouge">Contextual Anim Scene Actor Component</code> defines a series of delegates we can use to react to different events occouring during the Anim Set.</p>

<p><img src="https://vorixo.github.io/devtricks/assets/images/per-post/contextual-anim/delegatescontextual.jpg" alt="Delegates" class="align-center" /></p>

<p>Contextual Animations can also be earlied out if desired by calling <code class="language-plaintext highlighter-rouge">EarlyOutContextualAnimScene</code> from <code class="language-plaintext highlighter-rouge">Contextual Anim Scene Actor Component</code>.</p>

<h2 id="multiplayer-details">Multiplayer details</h2>

<p>Contextual Animations implement multiplayer support for you, so you shouldn’t have to worry about anything when it comes to making them work in multiplayer.</p>

<p>At the time of writing Contextual Animations should be called <strong>only</strong> from the server <strong>or</strong> from an <a href="https://docs.unrealengine.com/en-US/actor-role-and-remoterole-in-unreal-engine/">autonomous proxy</a> (<code class="language-plaintext highlighter-rouge">ROLE_AutonomousProxy</code>) and they do not support prediction. To give an example of this, in the <a href="https://vorixo.github.io/devtricks/gas/">Ability</a> example from above I’m starting the Anim Scene <strong>only</strong> on Authority.</p>

<p>If you’ve done everything correctly, you’ll be able to do many cool things (courtesy of <a href="https://github.com/dyanikoglu">Doğa</a>):</p>

<iframe width="480" height="270" src="https://www.youtube.com/embed/kuv5OM3CiYw" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>

<iframe width="480" height="270" src="https://www.youtube.com/embed/r8DQkq6AOLk" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>

<h1 id="conclusion">Conclusion</h1>

<p>Hey!</p>

<p>I hope you found interesting this pseudo article/tutorial.</p>

<p>This was possible thanks to my friend Doğa, who not only proactively shared his knowledge with us by the means of collaborating in the writing of this article, but also shared two very cool examples he crafted. I think a very nice way to thank him would be to follow on his <a href="https://twitter.com/dcyanikoglu">social media</a>!</p>

<p>Now as a I always say, happy deving!</p>

<p>Enjoy, <a href="https://twitter.com/vorixo">vori</a>.</p>]]></content><author><name>Alvaro Jover-Alvarez</name></author><category term="Videogames Development" /><category term="Multiplayer" /><category term="UE5" /><category term="Animation" /><category term="Networking" /><summary type="html"><![CDATA[In this post we'll learn how to use the Contextual Animation Plugin.]]></summary></entry><entry><title type="html">Mover in Unreal Engine 5.4</title><link href="https://vorixo.github.io/devtricks/mover-show/" rel="alternate" type="text/html" title="Mover in Unreal Engine 5.4" /><published>2024-02-03T00:00:00+00:00</published><updated>2024-02-03T00:00:00+00:00</updated><id>https://vorixo.github.io/devtricks/mover-show</id><content type="html" xml:base="https://vorixo.github.io/devtricks/mover-show/"><![CDATA[<p>Unreal Engine introduced Mover 2.0 in 5.4, this post showcases my first (second) impressions and an overview of the system.</p>

<h1 id="introduction">Introduction</h1>

<p>Epic Games recently introduced a new and <strong>experimental</strong> Movement Component in 5.4, called Mover, so… let’s take a look.</p>

<p>In order to activate the feature, open <code class="language-plaintext highlighter-rouge">Edit</code> and within <code class="language-plaintext highlighter-rouge">Plugins</code> windows, turn on the following:</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/mover-show/mover-enable.jpg" alt="Mover Plugins" class="align-center" /></p>

<h2 id="how-does-it-work">How does it work?</h2>

<p>The following video I’ve recorded showcases the state of the system at the release date of the Plugin during UE 5.4:</p>

<iframe width="480" height="270" src="https://www.youtube.com/embed/1jD4WT6wkjw" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>

<p class="notice--info"><strong>Note:</strong> At the time of writing, Mover 2.0 is only obtainable through the git version of the engine, branch 5.4 or ue5-main.</p>

<h2 id="additional-resources">Additional resources</h2>

<p>In the video I mention two resources:</p>
<ul>
  <li><a href="https://github.com/daftsoftware/FGMovement?tab=readme-ov-file#important-notices">Configuration settings for Mover.</a></li>
  <li><a href="https://github.com/daftsoftware/FGMovement">An alternative Movement system implemented with Mover.</a></li>
</ul>

<h1 id="conclusion">Conclusion</h1>

<p>Thanks for watching!</p>

<p>Hopefully, and as I also said previously, I you are as hyped as I am with this new feature! :)</p>

<p>Enjoy, <a href="https://twitter.com/vorixo">vori</a>.</p>]]></content><author><name>Alvaro Jover-Alvarez</name></author><category term="Videogames Development" /><category term="Multiplayer" /><category term="UE5" /><category term="Physics Prediction" /><category term="Networking" /><summary type="html"><![CDATA[In this post we'll see the current state of the Mover Plugin in UE 5.4.]]></summary></entry><entry><title type="html">Convenient Multiplayer Logging in UE5</title><link href="https://vorixo.github.io/devtricks/convenient-pie-multiplayer-logging/" rel="alternate" type="text/html" title="Convenient Multiplayer Logging in UE5" /><published>2024-01-04T00:00:00+00:00</published><updated>2024-01-04T00:00:00+00:00</updated><id>https://vorixo.github.io/devtricks/convenient-pie-multiplayer-logging</id><content type="html" xml:base="https://vorixo.github.io/devtricks/convenient-pie-multiplayer-logging/"><![CDATA[<p>Hello and happy new year! Many of you asked me how I measured the accuracy in my <a href="https://vorixo.github.io/devtricks/non-destructive-synced-net-clock/">network clock article</a>.</p>

<p>This technique can be generalized to infinite use cases, so I decided to generalize it and write about it in this small article. So I hope you like it and find it useful.</p>

<h1 id="overview">Overview</h1>

<p>This article explains how to access the server instance from a client in PIE (Play in Editor), thus, easing logging of value discrepancy between server and client. This technique has served me in the past to debug synced variables. In essence, expect something as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LogEntry: Client Stamina: 29.25 | Server Stamina: 29.23
</code></pre></div></div>

<p>As you can see, the same log entry contains the value of the stamina in the client and in the server captured in the same frame. The trick here is accessing the server instance from the client instance, this trick will only work in the editor, so its strictly useful for editor debugging/logging.</p>

<h1 id="the-implementation">The implementation</h1>

<p>To obtain a Server Player Controller from a client, first we have to find the server world, only existing within the editor context:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">UWorld</span><span class="o">*</span> <span class="n">UMyDevelopmentStatics</span><span class="o">::</span><span class="n">FindPlayInEditorAuthorityWorld</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">check</span><span class="p">(</span><span class="n">GEngine</span><span class="p">);</span>

	<span class="c1">// Find the server world (any PIE world will do, in case they are running without a dedicated server, but the ded. server world is ideal)</span>
	<span class="n">UWorld</span><span class="o">*</span> <span class="n">ServerWorld</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="cp">#if WITH_EDITOR
</span>	<span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">FWorldContext</span><span class="o">&amp;</span> <span class="n">WorldContext</span> <span class="o">:</span> <span class="n">GEngine</span><span class="o">-&gt;</span><span class="n">GetWorldContexts</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">WorldContext</span><span class="p">.</span><span class="n">WorldType</span> <span class="o">==</span> <span class="n">EWorldType</span><span class="o">::</span><span class="n">PIE</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="k">if</span> <span class="p">(</span><span class="n">UWorld</span><span class="o">*</span> <span class="n">TestWorld</span> <span class="o">=</span> <span class="n">WorldContext</span><span class="p">.</span><span class="n">World</span><span class="p">())</span>
			<span class="p">{</span>
				<span class="k">if</span> <span class="p">(</span><span class="n">WorldContext</span><span class="p">.</span><span class="n">RunAsDedicated</span><span class="p">)</span>
				<span class="p">{</span>
					<span class="c1">// Ideal case</span>
					<span class="n">ServerWorld</span> <span class="o">=</span> <span class="n">TestWorld</span><span class="p">;</span>
					<span class="k">break</span><span class="p">;</span>
				<span class="p">}</span>
				<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">ServerWorld</span> <span class="o">==</span> <span class="nb">nullptr</span><span class="p">)</span>
				<span class="p">{</span>
					<span class="n">ServerWorld</span> <span class="o">=</span> <span class="n">TestWorld</span><span class="p">;</span>
				<span class="p">}</span>
				<span class="k">else</span>
				<span class="p">{</span>
					<span class="c1">// We already have a candidate, see if this one is 'better'</span>
					<span class="k">if</span> <span class="p">(</span><span class="n">TestWorld</span><span class="o">-&gt;</span><span class="n">GetNetMode</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">ServerWorld</span><span class="o">-&gt;</span><span class="n">GetNetMode</span><span class="p">())</span>
					<span class="p">{</span>
						<span class="k">return</span> <span class="n">ServerWorld</span><span class="p">;</span>
					<span class="p">}</span>
				<span class="p">}</span>
			<span class="p">}</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="cp">#endif
</span>	<span class="k">return</span> <span class="n">ServerWorld</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>The function above was conveniently implemented in Lyra, so we can make use of it. The next function will use the previous function to get the Server Player Controller:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">APlayerController</span><span class="o">*</span> <span class="n">UMyDevelopmentStatics</span><span class="o">::</span><span class="n">FindPlayInEditorAuthorityPlayerController</span><span class="p">(</span><span class="n">APlayerController</span><span class="o">*</span> <span class="n">ClientController</span><span class="p">)</span>
<span class="p">{</span>
<span class="cp">#if WITH_EDITOR
</span>	<span class="n">UWorld</span><span class="o">*</span> <span class="n">ServerWorld</span> <span class="o">=</span> <span class="n">UMyDevelopmentStatics</span><span class="o">::</span><span class="n">FindPlayInEditorAuthorityWorld</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">ServerWorld</span> <span class="o">!=</span> <span class="nb">nullptr</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">for</span> <span class="p">(</span><span class="n">FConstPlayerControllerIterator</span> <span class="n">Iterator</span> <span class="o">=</span> <span class="n">ServerWorld</span><span class="o">-&gt;</span><span class="n">GetPlayerControllerIterator</span><span class="p">();</span> <span class="n">Iterator</span><span class="p">;</span> <span class="o">++</span><span class="n">Iterator</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">APlayerController</span><span class="o">*</span> <span class="n">ServerController</span> <span class="o">=</span> <span class="n">Iterator</span><span class="o">-&gt;</span><span class="n">Get</span><span class="p">();</span>

			<span class="k">const</span> <span class="n">FUniqueNetIdRepl</span><span class="o">&amp;</span> <span class="n">ServerPlayerUniqueId</span> <span class="o">=</span> <span class="n">ServerController</span><span class="o">-&gt;</span><span class="n">PlayerState</span><span class="o">-&gt;</span><span class="n">GetUniqueId</span><span class="p">();</span>
			<span class="k">const</span> <span class="n">FUniqueNetIdRepl</span><span class="o">&amp;</span> <span class="n">ClientPlayerUniqueId</span> <span class="o">=</span> <span class="n">ClientController</span><span class="o">-&gt;</span><span class="n">PlayerState</span><span class="o">-&gt;</span><span class="n">GetUniqueId</span><span class="p">();</span>

			<span class="k">if</span> <span class="p">(</span><span class="n">ServerPlayerUniqueId</span> <span class="o">==</span> <span class="n">ClientPlayerUniqueId</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="k">return</span> <span class="n">ServerController</span><span class="p">;</span>
			<span class="p">}</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="cp">#endif
</span>	<span class="k">return</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>As we can see, we simply iterate through the player controllers of the server world until we find the one matching (so that the Player UniqueId matches). Thus, if we call the function from our client, the function returns the server Player Controller related to our client Player Controller.</p>

<h1 id="a-simple-example">A simple example</h1>

<p>In the example below, we want to print the server value alongside the client value of Stamina from our CMC.</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="c1">// From a client...</span>

<span class="kt">float</span> <span class="n">ServerStamina</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">APlayerController</span><span class="o">*</span> <span class="n">ServerPlayerController</span> <span class="o">=</span> <span class="n">UMyDevelopmentStatics</span><span class="o">::</span><span class="n">FindPlayInEditorAuthorityPlayerController</span><span class="p">(</span><span class="n">GetPlayerController</span><span class="p">()))</span>
<span class="p">{</span>
	<span class="k">if</span><span class="p">(</span> <span class="n">AMyCharacter</span><span class="o">*</span> <span class="n">ServerPawn</span> <span class="o">=</span> <span class="n">Cast</span><span class="o">&lt;</span><span class="n">AMyCharacter</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ServerPlayerController</span><span class="o">-&gt;</span><span class="n">GetPawn</span><span class="p">()))</span>
	<span class="p">{</span>
		<span class="n">ServerStamina</span> <span class="o">=</span> <span class="n">ServerPawn</span><span class="o">-&gt;</span><span class="n">GetMyCharacterMovementComponent</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">Stamina</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="n">Print</span><span class="p">(</span><span class="s">"LogEntry: Client Stamina: %f | Server Stamina: %f"</span><span class="p">,</span> <span class="n">Stamina</span><span class="p">,</span> <span class="n">ServerStamina</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<h1 id="conclusion">Conclusion</h1>

<p>Thanks for reading!</p>

<p>I hope this clarifies the magic I made back then when I measured the clock. Now! Feel free to use it wherever you like! Trust me, you’ll find usecases :D</p>

<p>Enjoy, <a href="https://twitter.com/vorixo">vori</a>.</p>]]></content><author><name>Alvaro Jover-Alvarez</name></author><category term="Videogames Development" /><category term="Multiplayer" /><category term="UE5" /><category term="Networking" /><category term="Debugging" /><category term="Logging" /><summary type="html"><![CDATA[In this post we'll learn a very cool trick to enhance your multiplayer logs. It consists on accessing the server instance from the client in the editor. But how? Find out inside!]]></summary></entry><entry><title type="html">Lazy loading the Ability System Component (GAS) in Multiplayer games</title><link href="https://vorixo.github.io/devtricks/lazy-loading-asc/" rel="alternate" type="text/html" title="Lazy loading the Ability System Component (GAS) in Multiplayer games" /><published>2023-11-10T00:00:00+00:00</published><updated>2023-11-10T00:00:00+00:00</updated><id>https://vorixo.github.io/devtricks/lazy-loading-asc</id><content type="html" xml:base="https://vorixo.github.io/devtricks/lazy-loading-asc/"><![CDATA[<p>Hello! In this post we’ll learn together how to Lazy Load the <a href="https://vorixo.github.io/devtricks/gas/">Ability System Component</a> in our multiplayer games to reduce memory usage!</p>

<h1 id="introduction">Introduction</h1>

<p>The Ability System Component (ASC) can be a bit resource-intensive, especially when dealing with World Actors that require it, like Damageables. If our world is populated with such Actors, their memory footprint can become noticeable.</p>

<p>Taking a cue from Epic and the insights shared by Tranek in the excellent <a href="https://github.com/tranek/GASDocumentation#optimizations-asclazyloading">GASDocumentation</a>, Fortnite adopts a smart approach. They lazily load their ASCs in their World Actors just when they’re needed, dodging the memory hit until it’s absolutely necessary. This memory optimization is a significant boost for scalability; since ASC World Actors won’t carry that footprint unless explicitly interacted with.</p>

<p>Think of it like this: buildings, trees, and damageable props on standby with a lower cost, only racking up the memory bill when they step into the gameplay spotlight.</p>

<p>But before we start, I would love to thank my dear friend <a href="https://www.thegames.dev/">KaosSpectrum</a> for his significant research and collaboration on the topic; without him, this wouldn’t have been possible.</p>

<h1 id="the-implementation">The implementation</h1>

<p>So… let’s take a look on how we can implement this. Bear with me, as Epic hasn’t posted this anywhere online, so if you spot any error, feel free to report it!</p>

<p>The first step is to create an Actor type that will hold this behaviour, in my case I called it <code class="language-plaintext highlighter-rouge">AMyGameplayActor</code>. It is critical that your actor implements the <code class="language-plaintext highlighter-rouge">IAbilitySystemInterface</code> interface, as we’ll require to override the <code class="language-plaintext highlighter-rouge">GetAbilitySystemComponent</code> function.</p>

<p>In this Actor, I’ve defined an Enum <code class="language-plaintext highlighter-rouge">EAbilitySystemCreationPolicy</code> that determines the ASC loading behaviour:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="cm">/**
 *	Defines how a the Ability System is loaded (if ever).
 */</span>
<span class="n">UENUM</span><span class="p">(</span><span class="n">BlueprintType</span><span class="p">)</span>
<span class="k">enum</span> <span class="k">class</span> <span class="nc">EAbilitySystemCreationPolicy</span> <span class="o">:</span> <span class="n">uint8</span>
<span class="p">{</span>
	<span class="n">Never</span> <span class="n">UMETA</span><span class="p">(</span><span class="n">ToolTip</span> <span class="o">=</span> <span class="s">"The Ability System will be always null"</span><span class="p">),</span>
	<span class="n">Lazy</span> <span class="n">UMETA</span><span class="p">(</span><span class="n">ToolTip</span> <span class="o">=</span> <span class="s">"The Ability System will be null in the client until it is used in the server"</span><span class="p">),</span>
	<span class="n">Always</span> <span class="n">UMETA</span><span class="p">(</span><span class="n">ToolTip</span> <span class="o">=</span> <span class="s">"The Ability System is loaded when the Actor loads"</span><span class="p">),</span>
<span class="p">};</span></code></pre></figure>

<p>I’ve also defined other relevant properties for the implementation, such as the Ability System Component class, the Attribute mutation buffer, or the runtime ASC transient property:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="nl">protected:</span>
	<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditDefaultsOnly</span><span class="p">,</span> <span class="n">Category</span><span class="o">=</span><span class="s">"Abilities"</span><span class="p">)</span>
	<span class="n">TSubclassOf</span><span class="o">&lt;</span><span class="n">UMyAbilitySystemComponent</span><span class="o">&gt;</span> <span class="n">AbilitySystemComponentClass</span><span class="p">;</span>

	<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">Transient</span><span class="p">)</span>
	<span class="n">TObjectPtr</span><span class="o">&lt;</span><span class="n">UMyAbilitySystemComponent</span><span class="o">&gt;</span> <span class="n">AbilitySystemComponent</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>

<span class="k">private</span><span class="o">:</span>

	<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">Transient</span><span class="p">,</span> <span class="n">ReplicatedUsing</span><span class="o">=</span><span class="n">OnRep_ReplicatedAbilitySystemComponent</span><span class="p">)</span>
	<span class="n">TObjectPtr</span><span class="o">&lt;</span><span class="n">UMyAbilitySystemComponent</span><span class="o">&gt;</span> <span class="n">ReplicatedAbilitySystemComponent</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>

	<span class="k">struct</span> <span class="nc">FPendingAttributeReplication</span>
	<span class="p">{</span>
		<span class="n">FPendingAttributeReplication</span><span class="p">()</span>
		<span class="p">{</span>
		<span class="p">}</span>

		<span class="n">FPendingAttributeReplication</span><span class="p">(</span><span class="k">const</span> <span class="n">FGameplayAttribute</span><span class="o">&amp;</span> <span class="n">InAttribute</span><span class="p">,</span> <span class="k">const</span> <span class="n">FGameplayAttributeData</span><span class="o">&amp;</span> <span class="n">InNewValue</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">Attribute</span> <span class="o">=</span> <span class="n">InAttribute</span><span class="p">;</span>
			<span class="n">NewValue</span> <span class="o">=</span> <span class="n">InNewValue</span><span class="p">;</span>
		<span class="p">}</span>

		<span class="n">FGameplayAttribute</span> <span class="n">Attribute</span><span class="p">;</span>
		<span class="n">FGameplayAttributeData</span> <span class="n">NewValue</span><span class="p">;</span>
	<span class="p">};</span>

	<span class="n">TArray</span><span class="o">&lt;</span><span class="k">struct</span> <span class="nc">FPendingAttributeReplication</span><span class="o">&gt;</span> <span class="n">PendingAttributeReplications</span><span class="p">;</span></code></pre></figure>

<p>Gameplay Actors need to replicate by default, so we setup their constructor appropriately:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">AMyGameplayActor</span><span class="o">::</span><span class="n">AMyGameplayActor</span><span class="p">(</span><span class="k">const</span> <span class="n">FObjectInitializer</span><span class="o">&amp;</span> <span class="n">ObjectInitializer</span><span class="p">)</span> <span class="o">:</span> <span class="n">Super</span><span class="p">(</span><span class="n">ObjectInitializer</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">PrimaryActorTick</span><span class="p">.</span><span class="n">bCanEverTick</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
	<span class="n">PrimaryActorTick</span><span class="p">.</span><span class="n">bStartWithTickEnabled</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
	<span class="n">AbilitySystemComponentClass</span> <span class="o">=</span> <span class="n">UMyAbilitySystemComponent</span><span class="o">::</span><span class="n">StaticClass</span><span class="p">();</span>

	<span class="n">bReplicates</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>Then, we create a the relevant functions to create the ASC and to Initialize it:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="n">AMyGameplayActor</span><span class="o">::</span><span class="n">CreateAbilitySystem</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">AbilitySystemComponent</span> <span class="o">=</span> <span class="n">NewObject</span><span class="o">&lt;</span><span class="n">UMyAbilitySystemComponent</span><span class="o">&gt;</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">AbilitySystemComponentClass</span><span class="p">);</span>
	<span class="n">AbilitySystemComponent</span><span class="o">-&gt;</span><span class="n">SetIsReplicated</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
	<span class="n">AbilitySystemComponent</span><span class="o">-&gt;</span><span class="n">SetReplicationMode</span><span class="p">(</span><span class="n">EGameplayEffectReplicationMode</span><span class="o">::</span><span class="n">Minimal</span><span class="p">);</span>
	<span class="n">AbilitySystemComponent</span><span class="o">-&gt;</span><span class="n">RegisterComponent</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">AMyGameplayActor</span><span class="o">::</span><span class="n">InitializeAbilitySystem</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">check</span><span class="p">(</span><span class="n">AbilitySystemComponent</span><span class="p">);</span>
	<span class="n">AbilitySystemComponent</span><span class="o">-&gt;</span><span class="n">InitAbilityActorInfo</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">HasAuthority</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="c1">// @TODO: Grant Abilities</span>

		<span class="c1">// @TODO: Grant and initialize Attributes</span>
	<span class="p">}</span>

	<span class="c1">// @TODO: Add static gameplay tags server/client</span>
<span class="p">}</span></code></pre></figure>

<p>Following next, we override and implement the functions below to accomodate to the ASC creation policies we defined:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="n">AMyGameplayActor</span><span class="o">::</span><span class="n">PreInitializeComponents</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">PreInitializeComponents</span><span class="p">();</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">AbilitySystemComponentCreationPolicy</span> <span class="o">==</span> <span class="n">EAbilitySystemCreationPolicy</span><span class="o">::</span><span class="n">Always</span> <span class="o">&amp;&amp;</span> <span class="n">GetNetMode</span><span class="p">()</span> <span class="o">!=</span> <span class="n">NM_Client</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">check</span><span class="p">(</span><span class="o">!</span><span class="n">AbilitySystemComponent</span><span class="p">);</span>
		<span class="n">CreateAbilitySystem</span><span class="p">();</span>
		<span class="n">InitializeAbilitySystem</span><span class="p">();</span>
		<span class="n">ForceNetUpdate</span><span class="p">();</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">AMyGameplayActor</span><span class="o">::</span><span class="n">PreReplication</span><span class="p">(</span><span class="n">IRepChangedPropertyTracker</span><span class="o">&amp;</span> <span class="n">ChangedPropertyTracker</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">PreReplication</span><span class="p">(</span><span class="n">ChangedPropertyTracker</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReplicatedAbilitySystemComponent</span> <span class="o">&amp;&amp;</span> <span class="n">AbilitySystemComponent</span> <span class="o">&amp;&amp;</span> <span class="n">AbilitySystemComponentCreationPolicy</span> <span class="o">!=</span> <span class="n">EAbilitySystemCreationPolicy</span><span class="o">::</span><span class="n">Never</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">MARK_PROPERTY_DIRTY_FROM_NAME</span><span class="p">(</span><span class="n">ThisClass</span><span class="p">,</span> <span class="n">ReplicatedAbilitySystemComponent</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
		<span class="n">ReplicatedAbilitySystemComponent</span> <span class="o">=</span> <span class="n">AbilitySystemComponent</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">AMyGameplayActor</span><span class="o">::</span><span class="n">GetLifetimeReplicatedProps</span><span class="p">(</span><span class="n">TArray</span><span class="o">&lt;</span> <span class="n">FLifetimeProperty</span> <span class="o">&gt;&amp;</span> <span class="n">OutLifetimeProps</span><span class="p">)</span> <span class="k">const</span>
<span class="p">{</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">GetLifetimeReplicatedProps</span><span class="p">(</span><span class="n">OutLifetimeProps</span><span class="p">);</span>

	<span class="n">FDoRepLifetimeParams</span> <span class="n">Params</span><span class="p">;</span>
	<span class="n">Params</span><span class="p">.</span><span class="n">bIsPushBased</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>

	<span class="n">DOREPLIFETIME_WITH_PARAMS_FAST</span><span class="p">(</span><span class="n">ThisClass</span><span class="p">,</span> <span class="n">ReplicatedAbilitySystemComponent</span><span class="p">,</span> <span class="n">Params</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">UAbilitySystemComponent</span><span class="o">*</span> <span class="n">AMyGameplayActor</span><span class="o">::</span><span class="n">GetAbilitySystemComponent</span><span class="p">()</span> <span class="k">const</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">AbilitySystemComponent</span> <span class="o">&amp;&amp;</span> <span class="n">HasAuthority</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">AbilitySystemComponentCreationPolicy</span> <span class="o">==</span> <span class="n">EAbilitySystemCreationPolicy</span><span class="o">::</span><span class="n">Lazy</span> <span class="o">&amp;&amp;</span> <span class="n">GetWorld</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">IsUnreachable</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="n">AMyGameplayActor</span><span class="o">*</span> <span class="n">MutableActor</span> <span class="o">=</span> <span class="k">const_cast</span><span class="o">&lt;</span><span class="n">AMyGameplayActor</span><span class="o">*&gt;</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
		<span class="n">MutableActor</span><span class="o">-&gt;</span><span class="n">CreateAbilitySystem</span><span class="p">();</span>
		<span class="n">MutableActor</span><span class="o">-&gt;</span><span class="n">InitializeAbilitySystem</span><span class="p">();</span>
		<span class="n">MutableActor</span><span class="o">-&gt;</span><span class="n">ForceNetUpdate</span><span class="p">();</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">AbilitySystemComponent</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">AMyGameplayActor</span><span class="o">::</span><span class="n">ApplyPendingAttributesFromReplication</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">check</span><span class="p">(</span><span class="n">AbilitySystemComponent</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">PendingAttributeReplications</span><span class="p">.</span><span class="n">Num</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">FPendingAttributeReplication</span><span class="o">&amp;</span> <span class="n">Pending</span> <span class="o">:</span> <span class="n">PendingAttributeReplications</span><span class="p">)</span> 
		<span class="p">{</span>
			<span class="n">AbilitySystemComponent</span><span class="o">-&gt;</span><span class="n">DeferredSetBaseAttributeValueFromReplication</span><span class="p">(</span><span class="n">Pending</span><span class="p">.</span><span class="n">Attribute</span><span class="p">,</span> <span class="n">Pending</span><span class="p">.</span><span class="n">NewValue</span><span class="p">);</span>
		<span class="p">}</span>
		<span class="n">PendingAttributeReplications</span><span class="p">.</span><span class="n">Empty</span><span class="p">();</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">AMyGameplayActor</span><span class="o">::</span><span class="n">OnRep_ReplicatedAbilitySystemComponent</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">AbilitySystemComponent</span> <span class="o">=</span> <span class="n">ReplicatedAbilitySystemComponent</span><span class="p">;</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">AbilitySystemComponent</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">InitializeAbilitySystem</span><span class="p">();</span>
		<span class="n">ApplyPendingAttributesFromReplication</span><span class="p">();</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">AMyGameplayActor</span><span class="o">::</span><span class="n">SetPendingAttributeFromReplication</span><span class="p">(</span><span class="k">const</span> <span class="n">FGameplayAttribute</span><span class="o">&amp;</span> <span class="n">Attribute</span><span class="p">,</span> <span class="k">const</span> <span class="n">FGameplayAttributeData</span><span class="o">&amp;</span> <span class="n">NewValue</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">PendingAttributeReplications</span><span class="p">.</span><span class="n">Emplace</span><span class="p">(</span><span class="n">FPendingAttributeReplication</span><span class="p">(</span><span class="n">Attribute</span><span class="p">,</span> <span class="n">NewValue</span><span class="p">));</span>
<span class="p">}</span></code></pre></figure>

<p>As we can see, if the policy is <code class="language-plaintext highlighter-rouge">Lazy</code>, the first time we call <code class="language-plaintext highlighter-rouge">GetAbilitySystemComponent()</code> through any of the ASC API functions, the ASC will get created and initialized. The <code class="language-plaintext highlighter-rouge">PreReplication</code> overriden function and our proxy <code class="language-plaintext highlighter-rouge">ReplicatedAbilitySystemComponent</code> variable will make sure <code class="language-plaintext highlighter-rouge">AbilitySystemComponent</code> will get to the client and Server properly.</p>

<h2 id="pending-attribute-replication">Pending Attribute Replication</h2>

<p>When the Ability System Component gets created in the server, it takes latency time before it reaches the client, and during that time, Attributes might need to replicate (accounting for Net Priority and Frequency on the owning Actors).</p>

<p>During this time, we need to hold the attribute changes somewhere until the ASC is available in the Client, for that, we use the <code class="language-plaintext highlighter-rouge">PendingAttributeReplications</code> Array defined in our Gameplay Actor.</p>

<p>To buffer the Attribute mutations, we need to route Attribute replication through our Gameplay Actor. In our base Attribute class we have in our game define the following macro:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="cp">#define MYGAMEPLAYATTRIBUTE_REPNOTIFY(ClassName, PropertyName, OldValue) \
{ \
	static FProperty* ThisProperty = FindFieldChecked&lt;FProperty&gt;(ClassName::StaticClass(), GET_MEMBER_NAME_CHECKED(ClassName, PropertyName)); \
	if (!GetOwningAbilitySystemComponent()) \
	{ \
		if (AMyGameplayActor* GameplayActor = Cast&lt;AMyGameplayActor&gt;(GetOwningActor())) \
		{ \
			GameplayActor-&gt;SetPendingAttributeFromReplication(FGameplayAttribute(ThisProperty), PropertyName); \
		} \
		return; \
	} \
	else \
	{ \
		GetOwningAbilitySystemComponent()-&gt;SetBaseAttributeValueFromReplication(FGameplayAttribute(ThisProperty), PropertyName, OldValue); \
	} \
}</span></code></pre></figure>

<p>Then, in our child Attribute Sets, we have to implement our <code class="language-plaintext highlighter-rouge">OnRep</code> as follows:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="n">UMyAttributeSet_Health</span><span class="o">::</span><span class="n">OnRep_Health</span><span class="p">(</span><span class="k">const</span> <span class="n">FGameplayAttributeData</span><span class="o">&amp;</span> <span class="n">OldHealth</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">MYGAMEPLAYATTRIBUTE_REPNOTIFY</span><span class="p">(</span><span class="n">UMyAttributeSet_Health</span><span class="p">,</span> <span class="n">Health</span><span class="p">,</span> <span class="n">OldHealth</span><span class="p">);</span> 
<span class="p">}</span>

<span class="kt">void</span> <span class="n">UMyAttributeSet_Health</span><span class="o">::</span><span class="n">OnRep_Shield</span><span class="p">(</span><span class="k">const</span> <span class="n">FGameplayAttributeData</span><span class="o">&amp;</span> <span class="n">OldShield</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">MYGAMEPLAYATTRIBUTE_REPNOTIFY</span><span class="p">(</span><span class="n">UMyAttributeSet_Health</span><span class="p">,</span> <span class="n">Shield</span><span class="p">,</span> <span class="n">OldShield</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>As we can see, the macro we created calls <code class="language-plaintext highlighter-rouge">SetPendingAttributeFromReplication</code> we defined in our <code class="language-plaintext highlighter-rouge">AMyGameplayActor</code> from the Attribute Set <code class="language-plaintext highlighter-rouge">OnRep</code> functions, which fill our buffer Array <code class="language-plaintext highlighter-rouge">PendingAttributeReplications</code> which gets consumed in <code class="language-plaintext highlighter-rouge">ApplyPendingAttributesFromReplication</code>, when the ASC reaches the client in <code class="language-plaintext highlighter-rouge">AMyGameplayActor</code>.</p>

<p><code class="language-plaintext highlighter-rouge">ApplyPendingAttributesFromReplication</code> calls <code class="language-plaintext highlighter-rouge">DeferredSetBaseAttributeValueFromReplication</code> which will ensure we have the most up to date attribute values on the client:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="n">UMyAbilitySystemComponent</span><span class="o">::</span><span class="n">DeferredSetBaseAttributeValueFromReplication</span><span class="p">(</span><span class="k">const</span> <span class="n">FGameplayAttribute</span><span class="o">&amp;</span> <span class="n">Attribute</span><span class="p">,</span> <span class="kt">float</span> <span class="n">NewValue</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="kt">float</span> <span class="n">OldValue</span> <span class="o">=</span> <span class="n">ActiveGameplayEffects</span><span class="p">.</span><span class="n">GetAttributeBaseValue</span><span class="p">(</span><span class="n">Attribute</span><span class="p">);</span>
	<span class="n">ActiveGameplayEffects</span><span class="p">.</span><span class="n">SetAttributeBaseValue</span><span class="p">(</span><span class="n">Attribute</span><span class="p">,</span> <span class="n">NewValue</span><span class="p">);</span>
	<span class="n">SetBaseAttributeValueFromReplication</span><span class="p">(</span><span class="n">Attribute</span><span class="p">,</span> <span class="n">NewValue</span><span class="p">,</span> <span class="n">OldValue</span><span class="p">);</span>
	<span class="c1">// TODO: You can process deferred delegates here</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">UMyAbilitySystemComponent</span><span class="o">::</span><span class="n">DeferredSetBaseAttributeValueFromReplication</span><span class="p">(</span><span class="k">const</span> <span class="n">FGameplayAttribute</span><span class="o">&amp;</span> <span class="n">Attribute</span><span class="p">,</span> <span class="k">const</span> <span class="n">FGameplayAttributeData</span><span class="o">&amp;</span> <span class="n">NewValue</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="kt">float</span> <span class="n">OldValue</span> <span class="o">=</span> <span class="n">ActiveGameplayEffects</span><span class="p">.</span><span class="n">GetAttributeBaseValue</span><span class="p">(</span><span class="n">Attribute</span><span class="p">);</span>
	<span class="n">ActiveGameplayEffects</span><span class="p">.</span><span class="n">SetAttributeBaseValue</span><span class="p">(</span><span class="n">Attribute</span><span class="p">,</span> <span class="n">NewValue</span><span class="p">.</span><span class="n">GetBaseValue</span><span class="p">());</span>
	<span class="n">SetBaseAttributeValueFromReplication</span><span class="p">(</span><span class="n">Attribute</span><span class="p">,</span> <span class="n">NewValue</span><span class="p">.</span><span class="n">GetBaseValue</span><span class="p">(),</span> <span class="n">OldValue</span><span class="p">);</span>
	<span class="c1">// TODO: You can process deferred delegates here</span>
<span class="p">}</span></code></pre></figure>

<h1 id="conclusion">Conclusion</h1>

<p>Thanks for reading!</p>

<p>Now you’ll have a way to save some memory from your ASC Gameplay Actors! I hope you found this article helpful!</p>

<p>I’m going to leave a remaining challenge to the curious reader: Deferred delegates! These deferred delegates should happen when the ASC reaches the client in our Gameplay Actor, and it would give us the possibility to react to the buffered changes. If you get to implement this, feel free to share it!</p>

<p>Enjoy, <a href="https://twitter.com/vorixo">vori</a>.</p>]]></content><author><name>Alvaro Jover-Alvarez</name></author><category term="Videogames Development" /><category term="Multiplayer" /><category term="UE5" /><category term="GAS" /><category term="Ability System" /><category term="Networking" /><summary type="html"><![CDATA[In this post we'll learn how to lazy load the ability system component and why this can be benefitial for our games.]]></summary></entry><entry><title type="html">Replicating stateful sounds and animations</title><link href="https://vorixo.github.io/devtricks/replicating-tracked-events/" rel="alternate" type="text/html" title="Replicating stateful sounds and animations" /><published>2023-09-17T00:00:00+00:00</published><updated>2023-09-17T00:00:00+00:00</updated><id>https://vorixo.github.io/devtricks/replicating-tracked-events</id><content type="html" xml:base="https://vorixo.github.io/devtricks/replicating-tracked-events/"><![CDATA[<p>Hello! In this post we’ll expose a very simple pattern you can use to replicate any long <strong>stateful</strong> sound or animation. In addition, we’ll learn about some caveats when it comes to <strong>replication order guarantees</strong>.</p>

<h1 id="introduction">Introduction</h1>

<p>There are times when we want to replicate a long animation and still have it playing in the correct timeframe when a client joins. While the <a href="https://docs.unrealengine.com/5.1/en-US/gameplay-ability-system-for-unreal-engine/">Gameplay Ability System</a> does this with the <a href="https://docs.unrealengine.com/4.27/en-US/BlueprintAPI/Ability/Tasks/PlayMontageAndWait/"><code class="language-plaintext highlighter-rouge">PlayMontageAndWait</code></a> node, GAS is not always appropriate for all of our use cases.</p>

<p>The same problem also occurs with music or other animated elements, such as an elevator. In all the cases, we want <a href="https://vorixo.github.io/devtricks/stateful-events-multiplayer/#towards-relevancy-resilient-code">late joiners</a> to see our ingredients in the correct state by the time they join.</p>

<h1 id="the-solution">The solution</h1>

<p>So… how can we solve this? The solution relies on using time! Yes, that simple!</p>

<p>If we store and replicate in a variable the time at which an action took place, we can figure out by the time someone joins to our game, where the animation/sound should be at for that client. Let’s picture it this way:</p>

<ol>
  <li>Player 1 starts playing an animation at t0 = 34 s</li>
  <li>Player 2 joins the game at t1 = 38 s</li>
  <li>By the time Player 2 joined, the animation has already advanced (t1 - t0) 4 seconds</li>
  <li>Player 2 should start playing the animation of Player 1 at t = 4 s.</li>
</ol>

<p>Let’s see how we can translate this to code.</p>

<h2 id="a-practical-example">A practical example</h2>

<p>In our imaginary game, characters can play the flute, and we want the melody to be synchronised between them, but that isnt complicated, since a Multicast can do the job. However, the melody is long, and we want incoming connections to hear the melody properly even if the player started playing it at an earlier time. These requirements made already the feature stateful; so… we have to discard Multicasts.</p>

<h2 id="coding-our-solution">Coding our solution</h2>

<p>One of the first concepts we should understand before coding this solution is that the <a href="https://wizardcell.com/unreal/multiplayer-tips-and-tricks/#1522-gamestate-replication-timing-guarantee"><code class="language-plaintext highlighter-rouge">GameState</code> is guaranteed to be valid when any Actor on client calls <code class="language-plaintext highlighter-rouge">BeginPlay</code></a>, this is important since we will rely on a <code class="language-plaintext highlighter-rouge">GameState</code> replicated clock to set the correct animation/sound state.</p>

<p>To replicate to our simulated proxies the time at which we started playing the melody we are going to use a replicated property <code class="language-plaintext highlighter-rouge">LastTimePlayedMelody</code> that replicates only to simulated proxies.</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="c1">// Represents the time at which the melody was played</span>
<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">ReplicatedUsing</span> <span class="o">=</span> <span class="n">OnRepLastTimePlayedMelody</span><span class="p">)</span>
<span class="kt">float</span> <span class="n">LastTimePlayedMelody</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>

<span class="kt">void</span> <span class="n">AMyPawn</span><span class="o">::</span><span class="n">GetLifetimeReplicatedProps</span><span class="p">(</span><span class="n">TArray</span><span class="o">&lt;</span><span class="n">FLifetimeProperty</span><span class="o">&gt;&amp;</span> <span class="n">OutLifetimeProps</span><span class="p">)</span> <span class="k">const</span>
<span class="p">{</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">GetLifetimeReplicatedProps</span><span class="p">(</span><span class="n">OutLifetimeProps</span><span class="p">);</span>

	<span class="c1">// We make sure only simulated proxies get this repli property</span>
	<span class="n">DOREPLIFETIME_CONDITION</span><span class="p">(</span><span class="n">AMyPawn</span><span class="p">,</span> <span class="n">LastTimePlayedMelody</span><span class="p">,</span> <span class="n">COND_SimulatedOnly</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>To play the melody at a specific time we are going to use <code class="language-plaintext highlighter-rouge">SpawnSoundAttached</code>, since it contains a parameter <code class="language-plaintext highlighter-rouge">StartTime</code> that we can input to start the sound at a time. For that, we created the following function that we should call <strong>locally</strong>, which starts playing a melody inmediately. As we can see, the function also notifies the server so that the rest of the clients can also play it.</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="n">AMyPawn</span><span class="o">::</span><span class="n">PlayMelody</span><span class="p">(</span><span class="kt">float</span> <span class="n">StartTime</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">UGameplayStatics</span><span class="o">::</span><span class="n">SpawnSoundAttached</span><span class="p">(</span><span class="n">FluteSong</span><span class="p">,</span> <span class="n">GetMesh</span><span class="p">(),</span> <span class="n">NAME_None</span><span class="p">,</span> 
		<span class="n">FVector</span><span class="o">::</span><span class="n">ZeroVector</span><span class="p">,</span> <span class="n">EAttachLocation</span><span class="o">::</span><span class="n">KeepRelativeOffset</span><span class="p">,</span> 
		<span class="nb">false</span><span class="p">,</span> <span class="mf">1.</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.</span><span class="n">f</span><span class="p">,</span> <span class="n">StartTime</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">IsLocallyControlled</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">HasAuthority</span><span class="p">())</span>
		<span class="p">{</span>
			<span class="n">LastTimePlayedMelody</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetTimeSeconds</span><span class="p">();</span>
		<span class="p">}</span>
		<span class="k">else</span>
		<span class="p">{</span>
			<span class="n">ServerPlayMelody</span><span class="p">();</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">AMyPawn</span><span class="o">::</span><span class="n">ServerPlayMelody</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">LastTimePlayedMelody</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetTimeSeconds</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetNetMode</span><span class="p">()</span> <span class="o">!=</span> <span class="n">ENetMode</span><span class="o">::</span><span class="n">NM_DedicatedServer</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">PlayMelody</span><span class="p">(</span><span class="mf">0.</span><span class="n">f</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>In the server we should set <code class="language-plaintext highlighter-rouge">LastTimePlayedMelody</code>, which will replicate through the OnRep. In the case we are in a Listen Server, we should also play the melody.</p>

<p>Then, in the OnRep we do as follows:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="n">AMyPawn</span><span class="o">::</span><span class="n">OnRepLastTimePlayedMelody</span><span class="p">()</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">TimeSeconds</span> <span class="o">==</span> <span class="n">CreationTime</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">AGameStateBase</span><span class="o">*</span> <span class="n">GameState</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetGameState</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">GameState</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">const</span> <span class="kt">float</span> <span class="n">SoundTime</span> <span class="o">=</span> <span class="n">GameState</span><span class="o">-&gt;</span><span class="n">GetServerWorldTimeSeconds</span><span class="p">()</span> <span class="o">-</span> <span class="n">LastTimePlayedMelody</span><span class="p">;</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">SoundTime</span> <span class="o">&lt;</span> <span class="n">FluteSong</span><span class="o">-&gt;</span><span class="n">GetDuration</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="n">PlayMelody</span><span class="p">(</span><span class="n">SoundTime</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>We calculate the time at which the sound should be at by substracting the current server time <code class="language-plaintext highlighter-rouge">GameState-&gt;GetServerWorldTimeSeconds()</code> with the OnRep variable. If the SoundTime is lesser than the total duration of the Sound, we can start playing the sound at the exact point!</p>

<p class="notice--info">If we were to use the same technique with animations, we can use <code class="language-plaintext highlighter-rouge">UAnimInstance::Montage_Play</code> which also has the analogous <code class="language-plaintext highlighter-rouge">InTimeToStartMontageAt</code>.</p>

<h2 id="problems-late-joiners">Problems: Late joiners</h2>

<p>However, we are not done. If we execute the code from above we will notice that our late joiners dont execute anything, this is because in the OnRep code from above, we are doing two very specific conditions:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">if</span> <span class="p">(</span><span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">TimeSeconds</span> <span class="o">==</span> <span class="n">CreationTime</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">AGameStateBase</span><span class="o">*</span> <span class="n">GameState</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetGameState</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">GameState</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">return</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>These two conditions basically skip the OnRep, as we dont want to use it on the first spawn packet. But why? We do this because the <code class="language-plaintext highlighter-rouge">GameState</code> isnt guaranteed to be replicated at the time our OnRep gets called. Then, how do we solve this?</p>

<p>As we mentioned previously:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GameState is guaranteed to be valid when any Actor on client calls BeginPlay.
</code></pre></div></div>

<p>So… let’s code our late join support in <code class="language-plaintext highlighter-rouge">BeginPlay</code>!</p>

<h3 id="beginplay-guarantees">BeginPlay guarantees</h3>

<p>As I said above, <code class="language-plaintext highlighter-rouge">GameState</code> will be valid when client-side <code class="language-plaintext highlighter-rouge">BeginPlay</code> is called.</p>

<p>However, having a valid <code class="language-plaintext highlighter-rouge">GameState</code> isn’t enough, since we also need to have up-to-date properties by the time <code class="language-plaintext highlighter-rouge">BeginPlay</code> is called on the client. Now, we can only guarantee that our properties have replicated by then in runtime spawned replicated Actors. The Actors that are pre-placed in the level (also called <a href="https://vorixo.github.io/devtricks/procgen/#net-startup-actor-and-net-addressable"><em>Net Startup</em> Actors</a>) don’t guarantee correct replicated state in <code class="language-plaintext highlighter-rouge">BeginPlay</code>.</p>

<p>So, in this case, we can safely assume that, in our Character, since it isn’t a <em>Net Startup</em> Actor, the variables will be up to date for the client in <code class="language-plaintext highlighter-rouge">BeginPlay</code>:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="n">AMyPawn</span><span class="o">::</span><span class="n">BeginPlay</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">BeginPlay</span><span class="p">();</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">GetLocalRole</span><span class="p">()</span> <span class="o">==</span> <span class="n">ENetRole</span><span class="o">::</span><span class="n">ROLE_SimulatedProxy</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">AGameStateBase</span><span class="o">*</span> <span class="n">GameState</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetGameState</span><span class="p">();</span>
		<span class="k">const</span> <span class="kt">float</span> <span class="n">SoundTime</span> <span class="o">=</span> <span class="n">GameState</span><span class="o">-&gt;</span><span class="n">GetServerWorldTimeSeconds</span><span class="p">()</span> <span class="o">-</span> <span class="n">LastTimePlayedMelody</span><span class="p">;</span>

		<span class="k">if</span> <span class="p">(</span><span class="n">SoundTime</span> <span class="o">&lt;</span> <span class="n">FluteSong</span><span class="o">-&gt;</span><span class="n">GetDuration</span><span class="p">())</span>
		<span class="p">{</span>
			<span class="n">PlayMelody</span><span class="p">(</span><span class="n">SoundTime</span><span class="p">);</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>When we execute the <code class="language-plaintext highlighter-rouge">BeginPlay</code> of other Characters (simulated proxies) in our client, we can access the <code class="language-plaintext highlighter-rouge">GameState</code> (which will be valid) to retrieve <code class="language-plaintext highlighter-rouge">GetServerWorldTimeSeconds</code>, and then using the same technique we saw in the OnRep, we can play the melody at the specified time.</p>

<h3 id="what-about-net-startup-actors">What about <em>Net Startup</em> Actors?</h3>

<p>In the case of <em>Net Startup</em> Actors we can’t rely on <code class="language-plaintext highlighter-rouge">BeginPlay</code>, so we wouldn’t put any initialization specific code on it.</p>

<p>In this case, we can develop a more generic solution… however… it is very ugly:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="n">AMyNetStartupActor</span><span class="o">::</span><span class="n">OnRepLastTimePlayedMelody</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">AGameStateBase</span><span class="o">*</span> <span class="n">GameState</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetGameState</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">GameState</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="c1">// Silly hack to prevent early initialization, if the GameState isn't valid we wait until it is to be able to get our clock</span>
		<span class="n">FTimerHandle</span> <span class="n">DummyTimer</span><span class="p">;</span>
		<span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetTimerManager</span><span class="p">().</span><span class="n">SetTimer</span><span class="p">(</span><span class="n">DummyTimer</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ThisClass</span><span class="o">::</span><span class="n">OnRepLastTimePlayedMelody</span><span class="p">,</span> <span class="mf">0.1</span><span class="n">f</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">const</span> <span class="kt">float</span> <span class="n">SoundTime</span> <span class="o">=</span> <span class="n">GameState</span><span class="o">-&gt;</span><span class="n">GetServerWorldTimeSeconds</span><span class="p">()</span> <span class="o">-</span> <span class="n">LastTimePlayedMelody</span><span class="p">;</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">SoundTime</span> <span class="o">&lt;</span> <span class="n">FluteSong</span><span class="o">-&gt;</span><span class="n">GetDuration</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="n">PlayMelody</span><span class="p">(</span><span class="n">SoundTime</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Unfortuntely, waiting for the <code class="language-plaintext highlighter-rouge">GameState</code> is the only solution. We can implement this in a more elegant manner orchestrating our initialization with a subsystem or other methods, but this inconsistency between <em>Net Startup</em> and non-<em>Net Startup</em> Actors makes networking more difficult, by default.</p>

<p>I really wish this gets changed in the future, but to date, this is our safest bet.</p>

<h1 id="conclusion">Conclusion</h1>

<p>Thanks for reading!</p>

<p>This article covers a very simplistic pattern you can use to replicate stateful tracked ingredients, such as sounds or animations, and also covers the problem between <em>Net Startup</em> and non-<em>Net Startup</em> Actors.</p>

<p>There are many things that could be improved about the techniques presented in the article, such as using a <a href="https://vorixo.github.io/devtricks/non-destructive-synced-net-clock/">better synced clock</a>, improving the waiting <code class="language-plaintext highlighter-rouge">GameState</code> method, or even employing another techniques to replicate tracks without depending on time.</p>

<p>However you got a chance to reason about different initialization techniques, and I hope that this article made you understand how important order is in multiplayer.</p>

<p>Enjoy, <a href="https://twitter.com/vorixo">vori</a>.</p>]]></content><author><name>Alvaro Jover-Alvarez</name></author><category term="Videogames Development" /><category term="Multiplayer" /><category term="UE5" /><category term="Animations" /><category term="Sound" /><category term="Networking" /><summary type="html"><![CDATA[In this post we'll learn a pattern to replicate long stateful songs and animations that depend on time.]]></summary></entry><entry><title type="html">Physics Network Prediction in Unreal Engine 5.3 Preview</title><link href="https://vorixo.github.io/devtricks/phys-prediction-show/" rel="alternate" type="text/html" title="Physics Network Prediction in Unreal Engine 5.3 Preview" /><published>2023-07-30T00:00:00+00:00</published><updated>2023-07-30T00:00:00+00:00</updated><id>https://vorixo.github.io/devtricks/phys-prediction-show</id><content type="html" xml:base="https://vorixo.github.io/devtricks/phys-prediction-show/"><![CDATA[<p>Unreal Engine introduced Physics prediction in 5.3, this post showcases a little demo project using it.</p>

<h1 id="introduction">Introduction</h1>

<p>Wow! I didn’t see that coming! Epic Games introduced an <strong>experimental</strong> Physics prediction feature in 5.3, so… let’s take a look.</p>

<p>In order to activate the feature, open <code class="language-plaintext highlighter-rouge">Project Settings</code> and within the <code class="language-plaintext highlighter-rouge">Physics</code> tab, turn on <code class="language-plaintext highlighter-rouge">Enable Physics Prediction</code>.</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/phys-prediction-show/enableit.jpg" alt="Network Physics Prediction" class="align-center" /></p>

<p>There are other settings you might want to explore!</p>

<h2 id="how-does-it-work">How does it work?</h2>

<p>The following video I’ve recorded showcases the state of the system at the release date of UE 5.3 Preview.</p>

<iframe width="480" height="270" src="https://www.youtube.com/embed/py2WbMj1afw" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>

<h1 id="conclusion">Conclusion</h1>

<p>Thanks for watching!</p>

<p>Hopefully you are as hyped as I am with this new feature! :)</p>

<p>Enjoy, <a href="https://twitter.com/vorixo">vori</a>.</p>]]></content><author><name>Alvaro Jover-Alvarez</name></author><category term="Videogames Development" /><category term="Multiplayer" /><category term="UE5" /><category term="Physics Prediction" /><category term="Networking" /><summary type="html"><![CDATA[In this post we'll see the current state of Physics Network Prediction in UE 5.3 Preview.]]></summary></entry><entry><title type="html">Understanding Unreal Engine’s DORM_Initial dormancy setting</title><link href="https://vorixo.github.io/devtricks/initial-dormancy/" rel="alternate" type="text/html" title="Understanding Unreal Engine’s DORM_Initial dormancy setting" /><published>2023-07-02T00:00:00+00:00</published><updated>2023-07-02T00:00:00+00:00</updated><id>https://vorixo.github.io/devtricks/initial-dormancy</id><content type="html" xml:base="https://vorixo.github.io/devtricks/initial-dormancy/"><![CDATA[<p>This article is a collaboration with <a href="https://twitter.com/wizardcells">WizardCell</a> and on it, you’ll learn how <code class="language-plaintext highlighter-rouge">DORM_Initial</code> works and why you should be careful with it.</p>

<h1 id="introduction">Introduction</h1>

<p>If we read the description of the <code class="language-plaintext highlighter-rouge">DORM_Initial</code> dormancy setting, we see what follows:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>This actor is initially dormant for all connection if it was placed in map.
</code></pre></div></div>

<p>Which made me understand that this setting would work like <code class="language-plaintext highlighter-rouge">DORM_DormantAll</code> just with a couple extra optimizations. But it isn’t the case, let’s see what are the implications of using this setting.</p>

<h1 id="dorm_dormantall-versus-dorm_initial"><code class="language-plaintext highlighter-rouge">DORM_DormantAll</code> versus <code class="language-plaintext highlighter-rouge">DORM_Initial</code></h1>

<p>When an Actor becomes relevant to a connection, all the properties of said Actor are sent to the connection, no matter whether the Actor is <code class="language-plaintext highlighter-rouge">DORM_DormantAll</code> or <code class="language-plaintext highlighter-rouge">DORM_Awake</code>. That means that the <a href="https://vorixo.github.io/devtricks/stateful-events-multiplayer/">state</a> the client receives from the Actor will be consistent of what the server sees about it.</p>

<p>However, that is not the case of <code class="language-plaintext highlighter-rouge">DORM_Initial</code> Actors, as they will not send any C++ replicated property if a connection becomes relevant:</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/initial-dormancy/dormancygif.gif" alt="Dormancy Initial issue" class="align-center" /></p>

<p class="notice--danger"><strong>Watch out!</strong> Blueprint replicated properties ignore Actor dormancy settings.</p>

<h2 id="how-to-use-dorm_initial">How to use <code class="language-plaintext highlighter-rouge">DORM_Initial</code></h2>

<p>So shall I simply not use <code class="language-plaintext highlighter-rouge">DORM_Initial</code>? No, you should! <code class="language-plaintext highlighter-rouge">DORM_Initial</code> Actors won’t be considered for replication at all even under relevancy, and sometimes that can be a huge optimization.</p>

<p>To ensure incoming connections get the new values over <code class="language-plaintext highlighter-rouge">DORM_Initial</code> Actors, you should call <code class="language-plaintext highlighter-rouge">FlushNetDormancy</code> on them <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-network-dormancy-in-unreal-engine#whentousewakemethods">before updating their properties</a>. This will make the Actor to become <code class="language-plaintext highlighter-rouge">DORM_DormantAll</code> but only when strictly needed.</p>

<p><img src="https://vorixo.github.io/devtricks//assets/images/per-post/initial-dormancy/flushdormancynode.jpg" alt="Flush Dormancy node" class="align-center" /></p>

<h3 id="what-about-calling-forcenetupdate-to-also-flush-the-dormancy">What about calling <code class="language-plaintext highlighter-rouge">ForceNetUpdate</code> to also flush the dormancy?</h3>

<p>Be very careful with this, as <code class="language-plaintext highlighter-rouge">ForceNetUpdate</code> also calls <code class="language-plaintext highlighter-rouge">FlushNetDormancy</code> but its guarded with a <code class="language-plaintext highlighter-rouge">NetDriver</code> check:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="n">AActor</span><span class="o">::</span><span class="n">ForceNetUpdate</span><span class="p">()</span>
<span class="p">{</span>
	<span class="p">...</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">GetLocalRole</span><span class="p">()</span> <span class="o">==</span> <span class="n">ROLE_Authority</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="c1">// ForceNetUpdate on the game net driver only if we are the authority...</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">NetDriver</span> <span class="o">&amp;&amp;</span> <span class="n">NetDriver</span><span class="o">-&gt;</span><span class="n">GetNetMode</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">ENetMode</span><span class="o">::</span><span class="n">NM_Client</span><span class="p">)</span> <span class="c1">// ... and not a client</span>
		<span class="p">{</span>
			<span class="n">NetDriver</span><span class="o">-&gt;</span><span class="n">ForceNetUpdate</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>

			<span class="k">if</span> <span class="p">(</span><span class="n">NetDormancy</span> <span class="o">&gt;</span> <span class="n">DORM_Awake</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="n">FlushNetDormancy</span><span class="p">();</span>
			<span class="p">}</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="p">...</span>
<span class="p">}</span></code></pre></figure>

<p>So, if the <code class="language-plaintext highlighter-rouge">NetDriver</code> is Null at the moment you call <code class="language-plaintext highlighter-rouge">ForceNetUpdate</code>*, the Actor will never call <code class="language-plaintext highlighter-rouge">FlushNetDormancy</code>, causing it to stay in a <code class="language-plaintext highlighter-rouge">DORM_Initial</code> state. For that reason it is always recommended to <strong>explicitly</strong> call <code class="language-plaintext highlighter-rouge">FlushNetDormancy</code> when dealing with <code class="language-plaintext highlighter-rouge">DORM_Initial</code> Actors.</p>

<p class="notice--info">*Worlds don’t get a <code class="language-plaintext highlighter-rouge">NetDriver</code> until <a href="https://docs.unrealengine.com/4.26/en-US/API/Runtime/Engine/Engine/UWorld/Listen/"><code class="language-plaintext highlighter-rouge">UWorld::Listen</code></a> is called.</p>

<h1 id="conclusion">Conclusion</h1>

<p>Thanks for reading!</p>

<p>Let’s hope everything is clear! Feel free to reach us out if there are questions! 
And I encourage every reader to check <a href="https://wizardcell.com/">WizardCell’s articles</a>.</p>

<p>If you want to know more about Dormancy, visit <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-network-dormancy-in-unreal-engine">Epic’s official documentation</a>!</p>

<p>Enjoy, <a href="https://twitter.com/wizardcells">WizardCell</a> and <a href="https://twitter.com/vorixo">vori</a>.</p>]]></content><author><name>Alvaro Jover-Alvarez</name></author><category term="Videogames Development" /><category term="Multiplayer" /><category term="UE5" /><category term="Dormancy" /><category term="Networking" /><summary type="html"><![CDATA[In this post we'll explain how DORM_Initial works and why you should be careful with it.]]></summary></entry></feed>