<?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-02-14T20:52:35+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.4</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.4" /><published>2024-05-04T00:00:00+00:00</published><updated>2024-05-04T00: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 a <a href="https://vorixo.github.io/devtricks/phys-prediction-show/">previous article</a> we saw a very premature version of the physics prediction system that now bundles with Unreal Engine. Now, in Unreal Engine 5.4 the system has matured a lot and in todays article we will 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 lately in a solution for all these problems bundled in their Networked Physics solution. Before digging onto the matter, I strongly recommend everyone to take a look to <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/networked-physics-overview">this article</a>, where Epic digs further on the different physics replication modes they brought to the engine with this new system.</p>

<p class="notice--info"><strong>Note:</strong> This code is deprecated in modern Unreal Engine versions (UE 5.7 +). Please refer to <a href="https://dev.epicgames.com/community/learning/tutorials/MoBq/unreal-engine-networked-physics-pawn-tutorial">this tutorial</a> by MBobbo (a key contributor to the system) for the updated implementation.</p>

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

<p>I decided to make this post as I couldnt find any simple example around the engine on how to use properly the new Networked Physics system. The ‘rolling boulder’ we are going to create may serve as a “hello world” for those that would like to get introduced in the matter, as this new system can be used to make any type of vehicle or physics controllable object. Because… what’s easier than a rolling sphere?</p>

<p>However, this tutorial comes with a big disclaimer, as we are in a very early r&amp;d phase, and we dont have yet any official technical documentation on the matter, other than the engine itself, and… my own assumptions!</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… lets get into it :D!</p>

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

<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>

<p>Then, lets not forget to enable the following settings in the same tab:</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>One of the core components of the new physics system is the <code class="language-plaintext highlighter-rouge">UNetworkPhysicsComponent</code>, and to date, this component enforces us to wrap our movement code in another component, so we’ll have a physics movement component and a controllable pawn that holds said component, let’s create the pawn:</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>Pay close attention to the replication settings, as they will play a relevant role in our implementation. Worth noting that the physics replication mode we are choosing for our boulder is <strong>Resimulation</strong>; in addition im leveraging a really low <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/detailed-actor-replication-flow-in-unreal-engine">Net Update Frequency</a> (not using Iris yet!), simply to show how great this system is.</p>

<p>And to finish up the setup, let me share with you my root static mesh settings that we use for the physics simulation:</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>Main important points from the image above is that, we need to turn on physics simulation, set the collision channel to something sensitive, and finally, ensure that the <code class="language-plaintext highlighter-rouge">Component Replicates</code> flag is off, we don’t need it, we are replicating the transform of our boulder by other means.</p>

<p class="notice--info"><strong>Note:</strong> It is highly recommended to raise the replicated movement quantization levels so that the physics replication system will have more data to work on, you can find these settings under Replication -&gt; Advanced.</p>

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

<p>The way I’ve found other systems in the engine forward their physics input to the server is by means of the <code class="language-plaintext highlighter-rouge">UNetworkPhysicsComponent</code>. This component not only takes care of that, it also mantains historical records of both inputs and states, enabling the rewind and resimulation of physics simulations, crucial for maintaining consistency and accuracy in multiplayer environments. So, let’s make use of these features!</p>

<p>The <code class="language-plaintext highlighter-rouge">UNetworkPhysicsComponent</code> requires us to create two structs, which hold the input and the state of our physics simulation:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="cm">/** Ball inputs from the player controller */</span>
<span class="n">USTRUCT</span><span class="p">()</span>
<span class="k">struct</span> <span class="nc">FBallInputs</span>
<span class="p">{</span>
	<span class="n">GENERATED_BODY</span><span class="p">()</span>

	<span class="n">FBallInputs</span><span class="p">()</span>
		<span class="o">:</span> <span class="n">SteeringInput</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="n">ThrottleInput</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="n">TravelDirection</span><span class="p">(</span><span class="n">FRotator</span><span class="o">::</span><span class="n">ZeroRotator</span><span class="p">)</span>
		<span class="p">,</span> <span class="n">JumpCount</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
	<span class="p">{}</span>

	<span class="c1">// Steering output to physics system. Range -1...1</span>
	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="kt">float</span> <span class="n">SteeringInput</span><span class="p">;</span>

	<span class="c1">// Accelerator output to physics system. Range -1...1</span>
	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="kt">float</span> <span class="n">ThrottleInput</span><span class="p">;</span>

	<span class="c1">// Desired direction</span>
	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="n">FRotator</span> <span class="n">TravelDirection</span><span class="p">;</span>

	<span class="cm">/** Counter for user jumps */</span>
	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="n">int32</span> <span class="n">JumpCount</span><span class="p">;</span>
<span class="p">};</span>

<span class="cm">/** Ball state data that will be used in the state history to rewind the simulation at some point in time */</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">FNetworkPhysicsData</span>
<span class="p">{</span>
	<span class="n">GENERATED_BODY</span><span class="p">()</span>

	<span class="cm">/**  Serialize data function that will be used to transfer the struct across the network */</span>
	<span class="kt">bool</span> <span class="n">NetSerialize</span><span class="p">(</span><span class="n">FArchive</span><span class="o">&amp;</span> <span class="n">Ar</span><span class="p">,</span> <span class="k">class</span> <span class="nc">UPackageMap</span><span class="o">*</span> <span class="n">Map</span><span class="p">,</span> <span class="kt">bool</span><span class="o">&amp;</span> <span class="n">bOutSuccess</span><span class="p">);</span>
<span class="p">};</span>

<span class="k">template</span><span class="o">&lt;</span><span class="p">&gt;</span>
<span class="k">struct</span> <span class="nc">TStructOpsTypeTraits</span><span class="o">&lt;</span><span class="n">FNetworkBallStates</span><span class="o">&gt;</span> <span class="o">:</span> <span class="k">public</span> <span class="n">TStructOpsTypeTraitsBase2</span><span class="o">&lt;</span><span class="n">FNetworkBallStates</span><span class="o">&gt;</span>
<span class="p">{</span>
	<span class="k">enum</span>
	<span class="p">{</span>
		<span class="n">WithNetSerializer</span> <span class="o">=</span> <span class="nb">true</span><span class="p">,</span>
	<span class="p">};</span>
<span class="p">};</span>

<span class="cm">/** Ball Inputs data that will be used in the inputs history to be applied while simulating */</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">FNetworkPhysicsData</span>
<span class="p">{</span>
	<span class="n">GENERATED_BODY</span><span class="p">()</span>

	<span class="cm">/** List of incoming control inputs coming from the local client */</span>
	<span class="n">UPROPERTY</span><span class="p">()</span>
	<span class="n">FBallInputs</span> <span class="n">BallInputs</span><span class="p">;</span>

	<span class="cm">/**  Apply the data onto the network physics component */</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">ApplyData</span><span class="p">(</span><span class="n">UActorComponent</span><span class="o">*</span> <span class="n">NetworkComponent</span><span class="p">)</span> <span class="k">const</span> <span class="k">override</span><span class="p">;</span>

	<span class="cm">/**  Build the data from the network physics component */</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">BuildData</span><span class="p">(</span><span class="k">const</span> <span class="n">UActorComponent</span><span class="o">*</span> <span class="n">NetworkComponent</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>

	<span class="cm">/**  Serialize data function that will be used to transfer the struct across the network */</span>
	<span class="kt">bool</span> <span class="n">NetSerialize</span><span class="p">(</span><span class="n">FArchive</span><span class="o">&amp;</span> <span class="n">Ar</span><span class="p">,</span> <span class="k">class</span> <span class="nc">UPackageMap</span><span class="o">*</span> <span class="n">Map</span><span class="p">,</span> <span class="kt">bool</span><span class="o">&amp;</span> <span class="n">bOutSuccess</span><span class="p">);</span>

	<span class="cm">/** Interpolate the data in between two inputs data */</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">FNetworkPhysicsData</span><span class="o">&amp;</span> <span class="n">MinData</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetworkPhysicsData</span><span class="o">&amp;</span> <span class="n">MaxData</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>

	<span class="cm">/** Merge data into this input */</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">FNetworkPhysicsData</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="p">};</span>

<span class="k">template</span><span class="o">&lt;</span><span class="p">&gt;</span>
<span class="k">struct</span> <span class="nc">TStructOpsTypeTraits</span><span class="o">&lt;</span><span class="n">FNetworkBallInputs</span><span class="o">&gt;</span> <span class="o">:</span> <span class="k">public</span> <span class="n">TStructOpsTypeTraitsBase2</span><span class="o">&lt;</span><span class="n">FNetworkBallInputs</span><span class="o">&gt;</span>
<span class="p">{</span>
	<span class="k">enum</span>
	<span class="p">{</span>
		<span class="n">WithNetSerializer</span> <span class="o">=</span> <span class="nb">true</span><span class="p">,</span>
	<span class="p">};</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="nc">FPhysicsBallTraits</span>
<span class="p">{</span>
	<span class="k">using</span> <span class="n">InputsType</span> <span class="o">=</span> <span class="n">FNetworkBallInputs</span><span class="p">;</span>
	<span class="k">using</span> <span class="n">StatesType</span> <span class="o">=</span> <span class="n">FNetworkBallStates</span><span class="p">;</span>
<span class="p">};</span></code></pre></figure>

<p>Note that, in my case I don’t require to keep track of any state of my boulder, but I still need to create the struct since the system requires it so.</p>

<p>Following next, the declaration of my movement class, I decided to use a <code class="language-plaintext highlighter-rouge">UPawnMovementComponent</code> in this case:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">CLASS</span><span class="p">(</span><span class="n">meta</span> <span class="o">=</span> <span class="p">(</span><span class="n">BlueprintSpawnableComponent</span><span class="p">))</span>
<span class="k">class</span> <span class="nc">MYPROJECT_API</span> <span class="n">URollingBallMovementComponent</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UPawnMovementComponent</span>
<span class="p">{</span>
	<span class="n">GENERATED_UCLASS_BODY</span><span class="p">()</span>
	
<span class="nl">public:</span>

	<span class="cm">/** Overridden to allow registration with components NOT owned by a Pawn. */</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">SetUpdatedComponent</span><span class="p">(</span><span class="n">USceneComponent</span><span class="o">*</span> <span class="n">NewUpdatedComponent</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>

	<span class="cm">/** Return true if it's suitable to create a physics representation of the Ball at this time */</span>
	<span class="k">virtual</span> <span class="kt">bool</span> <span class="n">ShouldCreatePhysicsState</span><span class="p">()</span> <span class="k">const</span> <span class="k">override</span><span class="p">;</span>

	<span class="cm">/** Used to create any physics engine information for this component */</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">OnCreatePhysicsState</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>

	<span class="cm">/** Used to shut down and physics engine structure for this component */</span>
	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">OnDestroyPhysicsState</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>

	<span class="kt">void</span> <span class="n">InitializeBall</span><span class="p">();</span>

	<span class="k">virtual</span> <span class="kt">void</span> <span class="n">AsyncPhysicsTickComponent</span><span class="p">(</span><span class="kt">float</span> <span class="n">DeltaTime</span><span class="p">,</span> <span class="kt">float</span> <span class="n">SimTime</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">InThrottle</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">InSteering</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">InTravelDirection</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">public:</span>

	<span class="c1">// Ball inputs</span>
	<span class="n">FBallInputs</span> <span class="n">BallInputs</span><span class="p">;</span>

	<span class="c1">// Ball state</span>
	<span class="n">FTransform</span> <span class="n">BallWorldTransform</span><span class="p">;</span>
	<span class="n">FVector</span> <span class="n">BallForwardAxis</span><span class="p">;</span>
	<span class="n">FVector</span> <span class="n">BallRightAxis</span><span class="p">;</span>

<span class="nl">private:</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="n">Chaos</span><span class="o">::</span><span class="n">FRigidBodyHandle_Internal</span><span class="o">*</span> <span class="n">RigidHandle</span><span class="p">;</span>
	<span class="n">FBodyInstance</span><span class="o">*</span> <span class="n">BodyInstance</span><span class="p">;</span>
	<span class="n">int32</span> <span class="n">PreviousJumpCount</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">};</span></code></pre></figure>

<p>And finally, the implementation:</p>

<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">URollingBallMovementComponent</span><span class="o">::</span><span class="n">URollingBallMovementComponent</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">SetIsReplicatedByDefault</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>

	<span class="k">static</span> <span class="k">const</span> <span class="n">FName</span> <span class="n">NetworkPhysicsComponentName</span><span class="p">(</span><span class="n">TEXT</span><span class="p">(</span><span class="s">"PC_NetworkPhysicsComponent"</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="p">,</span> <span class="n">UNetworkPhysicsComponent</span><span class="o">&gt;</span><span class="p">(</span><span class="n">NetworkPhysicsComponentName</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="c1">// Make DSO components net addressable</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="kt">void</span> <span class="n">URollingBallMovementComponent</span><span class="o">::</span><span class="n">SetUpdatedComponent</span><span class="p">(</span><span class="n">USceneComponent</span><span class="o">*</span> <span class="n">NewUpdatedComponent</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">UNavMovementComponent</span><span class="o">::</span><span class="n">SetUpdatedComponent</span><span class="p">(</span><span class="n">NewUpdatedComponent</span><span class="p">);</span>
	<span class="n">PawnOwner</span> <span class="o">=</span> <span class="n">NewUpdatedComponent</span> <span class="o">?</span> <span class="n">Cast</span><span class="o">&lt;</span><span class="n">APawn</span><span class="o">&gt;</span><span class="p">(</span><span class="n">NewUpdatedComponent</span><span class="o">-&gt;</span><span class="n">GetOwner</span><span class="p">())</span> <span class="o">:</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">URollingBallMovementComponent</span><span class="o">::</span><span class="n">ShouldCreatePhysicsState</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">IsRegistered</span><span class="p">()</span> <span class="o">||</span> <span class="n">IsBeingDestroyed</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="c1">// only create 'Physics' Ball in game</span>
	<span class="n">UWorld</span><span class="o">*</span> <span class="n">World</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">World</span><span class="o">-&gt;</span><span class="n">IsGameWorld</span><span class="p">())</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">World</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="n">PhysScene</span> <span class="o">&amp;&amp;</span> <span class="n">UpdatedComponent</span> <span class="o">&amp;&amp;</span> <span class="n">UpdatedPrimitive</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="k">return</span> <span class="nb">true</span><span class="p">;</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="kt">void</span> <span class="n">URollingBallMovementComponent</span><span class="o">::</span><span class="n">OnCreatePhysicsState</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">OnCreatePhysicsState</span><span class="p">();</span>

	<span class="c1">// only create Physics Ball in game</span>
	<span class="n">UWorld</span><span class="o">*</span> <span class="n">World</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">World</span><span class="o">-&gt;</span><span class="n">IsGameWorld</span><span class="p">())</span>
	<span class="p">{</span>
		<span class="n">InitializeBall</span><span class="p">();</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">CreateDataHistory</span><span class="o">&lt;</span><span class="n">FPhysicsBallTraits</span><span class="o">&gt;</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="c1">// Initializing phys handle</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">UPrimitiveComponent</span><span class="o">*</span> <span class="n">Mesh</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">UpdatedComponent</span><span class="p">))</span>
	<span class="p">{</span>
		<span class="n">BodyInstance</span> <span class="o">=</span> <span class="n">Mesh</span><span class="o">-&gt;</span><span class="n">GetBodyInstance</span><span class="p">();</span>
	<span class="p">}</span>	
<span class="p">}</span>

<span class="kt">void</span> <span class="n">URollingBallMovementComponent</span><span class="o">::</span><span class="n">OnDestroyPhysicsState</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">OnDestroyPhysicsState</span><span class="p">();</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">UpdatedComponent</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">UpdatedComponent</span><span class="o">-&gt;</span><span class="n">RecreatePhysicsState</span><span class="p">();</span>
	<span class="p">}</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">RemoveDataHistory</span><span class="p">();</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">URollingBallMovementComponent</span><span class="o">::</span><span class="n">InitializeBall</span><span class="p">()</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">UpdatedComponent</span> <span class="o">&amp;&amp;</span> <span class="n">UpdatedPrimitive</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">SetAsyncPhysicsTickEnabled</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">URollingBallMovementComponent</span><span class="o">::</span><span class="n">AsyncPhysicsTickComponent</span><span class="p">(</span><span class="kt">float</span> <span class="n">DeltaTime</span><span class="p">,</span> <span class="kt">float</span> <span class="n">SimTime</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">Super</span><span class="o">::</span><span class="n">AsyncPhysicsTickComponent</span><span class="p">(</span><span class="n">DeltaTime</span><span class="p">,</span> <span class="n">SimTime</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">BodyInstance</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">if</span> <span class="p">(</span><span class="k">const</span> <span class="k">auto</span> <span class="n">Handle</span> <span class="o">=</span> <span class="n">BodyInstance</span><span class="o">-&gt;</span><span class="n">ActorHandle</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">RigidHandle</span> <span class="o">=</span> <span class="n">Handle</span><span class="o">-&gt;</span><span class="n">GetPhysicsThreadAPI</span><span class="p">();</span>
	<span class="p">}</span>
	
	<span class="n">UWorld</span><span class="o">*</span> <span class="n">World</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">World</span> <span class="o">&amp;&amp;</span> <span class="n">RigidHandle</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">const</span> <span class="n">FTransform</span> <span class="n">WorldTM</span><span class="p">(</span><span class="n">RigidHandle</span><span class="o">-&gt;</span><span class="n">R</span><span class="p">(),</span> <span class="n">RigidHandle</span><span class="o">-&gt;</span><span class="n">X</span><span class="p">());</span>
		<span class="n">BallWorldTransform</span> <span class="o">=</span> <span class="n">WorldTM</span><span class="p">;</span>
		<span class="n">BallForwardAxis</span> <span class="o">=</span> <span class="n">BallWorldTransform</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">X</span><span class="p">);</span>
		<span class="n">BallRightAxis</span> <span class="o">=</span> <span class="n">BallWorldTransform</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">DesiredForwardVector</span> <span class="o">=</span> <span class="n">BallInputs</span><span class="p">.</span><span class="n">TravelDirection</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">DesiredRightVector</span> <span class="o">=</span> <span class="n">FRotationMatrix</span><span class="p">(</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">TravelDirection</span><span class="p">).</span><span class="n">GetScaledAxis</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="c1">// Update the simulation forces/impulses...</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">JumpCount</span> <span class="o">!=</span> <span class="n">PreviousJumpCount</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">RigidHandle</span><span class="o">-&gt;</span><span class="n">SetLinearImpulse</span><span class="p">(</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="mf">500.</span><span class="n">f</span><span class="p">),</span> <span class="nb">true</span><span class="p">);</span>
		<span class="p">}</span>

		<span class="n">RigidHandle</span><span class="o">-&gt;</span><span class="n">AddForce</span><span class="p">(</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">ThrottleInput</span> <span class="o">*</span> <span class="n">DesiredForwardVector</span> <span class="o">*</span> <span class="mf">80000.</span><span class="n">f</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
		<span class="n">RigidHandle</span><span class="o">-&gt;</span><span class="n">AddForce</span><span class="p">(</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">SteeringInput</span> <span class="o">*</span> <span class="n">DesiredRightVector</span> <span class="o">*</span> <span class="mf">80000.</span><span class="n">f</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>

		<span class="c1">// Set prev frame vars</span>
		<span class="n">PreviousJumpCount</span> <span class="o">=</span> <span class="n">BallInputs</span><span class="p">.</span><span class="n">JumpCount</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">URollingBallMovementComponent</span><span class="o">::</span><span class="n">SetThrottleInput</span><span class="p">(</span><span class="kt">float</span> <span class="n">InThrottle</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="kt">float</span> <span class="n">FinalThrottle</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">InThrottle</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">BallInputs</span><span class="p">.</span><span class="n">ThrottleInput</span> <span class="o">=</span> <span class="n">InThrottle</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">URollingBallMovementComponent</span><span class="o">::</span><span class="n">SetSteeringInput</span><span class="p">(</span><span class="kt">float</span> <span class="n">InSteering</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="kt">float</span> <span class="n">FinalSteering</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">InSteering</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">BallInputs</span><span class="p">.</span><span class="n">SteeringInput</span> <span class="o">=</span> <span class="n">InSteering</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">URollingBallMovementComponent</span><span class="o">::</span><span class="n">SetTravelDirectionInput</span><span class="p">(</span><span class="n">FRotator</span> <span class="n">InTravelDirection</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">BallInputs</span><span class="p">.</span><span class="n">TravelDirection</span> <span class="o">=</span> <span class="n">InTravelDirection</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">URollingBallMovementComponent</span><span class="o">::</span><span class="n">Jump</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">BallInputs</span><span class="p">.</span><span class="n">JumpCount</span><span class="o">++</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">NetSerialize</span><span class="p">(</span><span class="n">FArchive</span><span class="o">&amp;</span> <span class="n">Ar</span><span class="p">,</span> <span class="k">class</span> <span class="nc">UPackageMap</span><span class="o">*</span> <span class="n">Map</span><span class="p">,</span> <span class="kt">bool</span><span class="o">&amp;</span> <span class="n">bOutSuccess</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">FNetworkPhysicsData</span><span class="o">::</span><span class="n">SerializeFrames</span><span class="p">(</span><span class="n">Ar</span><span class="p">);</span>

	<span class="n">Ar</span> <span class="o">&lt;&lt;</span> <span class="n">BallInputs</span><span class="p">.</span><span class="n">SteeringInput</span><span class="p">;</span>
	<span class="n">Ar</span> <span class="o">&lt;&lt;</span> <span class="n">BallInputs</span><span class="p">.</span><span class="n">ThrottleInput</span><span class="p">;</span>
	<span class="n">Ar</span> <span class="o">&lt;&lt;</span> <span class="n">BallInputs</span><span class="p">.</span><span class="n">TravelDirection</span><span class="p">;</span>
	<span class="n">Ar</span> <span class="o">&lt;&lt;</span> <span class="n">BallInputs</span><span class="p">.</span><span class="n">JumpCount</span><span class="p">;</span>

	<span class="n">bOutSuccess</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
	<span class="k">return</span> <span class="n">bOutSuccess</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">ApplyData</span><span class="p">(</span><span class="n">UActorComponent</span><span class="o">*</span> <span class="n">NetworkComponent</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="n">URollingBallMovementComponent</span><span class="o">*</span> <span class="n">BallMover</span> <span class="o">=</span> <span class="n">Cast</span><span class="o">&lt;</span><span class="n">URollingBallMovementComponent</span><span class="o">&gt;</span><span class="p">(</span><span class="n">NetworkComponent</span><span class="p">))</span>
	<span class="p">{</span>
		<span class="n">BallMover</span><span class="o">-&gt;</span><span class="n">BallInputs</span> <span class="o">=</span> <span class="n">BallInputs</span><span class="p">;</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">BuildData</span><span class="p">(</span><span class="k">const</span> <span class="n">UActorComponent</span><span class="o">*</span> <span class="n">NetworkComponent</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">NetworkComponent</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="k">const</span> <span class="n">URollingBallMovementComponent</span><span class="o">*</span> <span class="n">BallMover</span> <span class="o">=</span> <span class="n">Cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">URollingBallMovementComponent</span><span class="o">&gt;</span><span class="p">(</span><span class="n">NetworkComponent</span><span class="p">))</span>
		<span class="p">{</span>
			<span class="n">BallInputs</span> <span class="o">=</span> <span class="n">BallMover</span><span class="o">-&gt;</span><span class="n">BallInputs</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">FNetworkBallInputs</span><span class="o">::</span><span class="n">InterpolateData</span><span class="p">(</span><span class="k">const</span> <span class="n">FNetworkPhysicsData</span><span class="o">&amp;</span> <span class="n">MinData</span><span class="p">,</span> <span class="k">const</span> <span class="n">FNetworkPhysicsData</span><span class="o">&amp;</span> <span class="n">MaxData</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">MinInput</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">MaxInput</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="k">const</span> <span class="kt">float</span> <span class="n">LerpFactor</span> <span class="o">=</span> <span class="n">MaxInput</span><span class="p">.</span><span class="n">LocalFrame</span> <span class="o">==</span> <span class="n">LocalFrame</span>
		<span class="o">?</span> <span class="mf">1.0</span><span class="n">f</span> <span class="o">/</span> <span class="p">(</span><span class="n">MaxInput</span><span class="p">.</span><span class="n">LocalFrame</span> <span class="o">-</span> <span class="n">MinInput</span><span class="p">.</span><span class="n">LocalFrame</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="c1">// Merge from min into max</span>
		<span class="o">:</span> <span class="p">(</span><span class="n">LocalFrame</span> <span class="o">-</span> <span class="n">MinInput</span><span class="p">.</span><span class="n">LocalFrame</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">MaxInput</span><span class="p">.</span><span class="n">LocalFrame</span> <span class="o">-</span> <span class="n">MinInput</span><span class="p">.</span><span class="n">LocalFrame</span><span class="p">);</span> <span class="c1">// Interpolate from min to max</span>


	<span class="n">BallInputs</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">MinInput</span><span class="p">.</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">ThrottleInput</span><span class="p">,</span> <span class="n">MaxInput</span><span class="p">.</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">ThrottleInput</span><span class="p">,</span> <span class="n">LerpFactor</span><span class="p">);</span>
	<span class="n">BallInputs</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">MinInput</span><span class="p">.</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">SteeringInput</span><span class="p">,</span> <span class="n">MaxInput</span><span class="p">.</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">SteeringInput</span><span class="p">,</span> <span class="n">LerpFactor</span><span class="p">);</span>
	<span class="n">BallInputs</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">MinInput</span><span class="p">.</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">TravelDirection</span><span class="p">,</span> <span class="n">MaxInput</span><span class="p">.</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">TravelDirection</span><span class="p">,</span> <span class="n">LerpFactor</span><span class="p">);</span>
	<span class="n">BallInputs</span><span class="p">.</span><span class="n">JumpCount</span> <span class="o">=</span> <span class="n">LerpFactor</span> <span class="o">&lt;</span> <span class="mf">0.5</span> <span class="o">?</span> <span class="n">MinInput</span><span class="p">.</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">JumpCount</span> <span class="o">:</span> <span class="n">MaxInput</span><span class="p">.</span><span class="n">BallInputs</span><span class="p">.</span><span class="n">JumpCount</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">FNetworkPhysicsData</span><span class="o">&amp;</span> <span class="n">FromData</span><span class="p">)</span>
<span class="p">{</span>
	<span class="c1">// Perform merge through InterpolateData</span>
	<span class="n">InterpolateData</span><span class="p">(</span><span class="n">FromData</span><span class="p">,</span> <span class="o">*</span><span class="k">this</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">NetSerialize</span><span class="p">(</span><span class="n">FArchive</span><span class="o">&amp;</span> <span class="n">Ar</span><span class="p">,</span> <span class="n">UPackageMap</span><span class="o">*</span> <span class="n">Map</span><span class="p">,</span> <span class="kt">bool</span><span class="o">&amp;</span> <span class="n">bOutSuccess</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">FNetworkPhysicsData</span><span class="o">::</span><span class="n">SerializeFrames</span><span class="p">(</span><span class="n">Ar</span><span class="p">);</span>
	<span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p class="notice--info">Note that the implementation above is just a “hello world”, it doesnt do any input sanitization in the server for simplicity.</p>

<p>If you did everything correctly, now you should have something as follows:</p>

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

<p>The above video was recorded with average latency everywhere (server and client). At the end of the video 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 later get corrected, making the local sphere snap back to the ground.</p>

<p class="notice--info"><strong>Note:</strong> The Physics Resimulation replication mode currently has some bugs in PIE, where if you try to add more than one client, it starts behaving erratically.</p>

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

<p>Now that we all are in the same page, I think its worth noting different details from the implementation from above.</p>

<p>At the start of our physics simulation (<code class="language-plaintext highlighter-rouge">OnCreatePhysicsState</code>), we let the <code class="language-plaintext highlighter-rouge">NetworkPhysicsComponent</code> know which data it should handle for our physics simulations. It’s like magic – super simple to use! We just need to specify the data’s type, the component holding it, and then implement overrides for input and state handling. <code class="language-plaintext highlighter-rouge">ApplyData</code> and <code class="language-plaintext highlighter-rouge">BuildData</code> are key here; they smoothly transfer data between our movement component and the <code class="language-plaintext highlighter-rouge">NetworkPhysicsComponent</code>, which takes care of all the nitty-gritty details of state and input management for us.</p>

<p>The input processing goes like this: during <code class="language-plaintext highlighter-rouge">AsyncPhysicsTickComponent</code>, inputs are first applied locally, then the server catches up. This allows for predictive autonomous proxy simulation followed by server validation</p>

<p>Now, we’re mostly dealing with continuous input streams, like movement directions or camera orientation (<code class="language-plaintext highlighter-rouge">TravelDirection</code>), which are straightforward. However, sending one-frame events, like a jump action, is still a puzzle. I’ve experimented with boolean input variables, such as <code class="language-plaintext highlighter-rouge">bJump</code>, but encountered issues – the server sometimes missed the update because the variable reset before the <code class="language-plaintext highlighter-rouge">NetworkPhysicsComponent</code> could transmit it. This led me to try alternative approaches, like using <code class="language-plaintext highlighter-rouge">JumpCount</code> to increment with each jump action. However, I’m not entirely certain if this method aligns with Epic’s recommended practices.</p>

<p>Another thing worth to note is that, if you look at the other physics examples around in the engine, they explicitly process the input in <code class="language-plaintext highlighter-rouge">OnPhysScenePreTick</code>, part of the Physics Scene. I’m however not sure of the extra-implications of this and I would love to know; but at least, in this minimal example we got something working in less than 400 lines of code.</p>

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

<p>Besides this post, you can also take a look at different systems from the engine that also make use of this technology:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">UChaosVehicleMovementComponent</code>: Chaos simple vehicle system.</li>
  <li><code class="language-plaintext highlighter-rouge">UMoverNetworkPhysicsLiaisonComponent</code>: Physics-based <a href="https://vorixo.github.io/devtricks/mover-show/">Mover</a>.</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 new 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 coded together a “hello world” physics boulder.</p>

<p>Note that this toy example uses a very simple setup for preview purposes. But I’d like to support it following the best practices, for that reason I’ll be mantaining it in my <a href="https://github.com/vorixo/ExperimentalArcadeVehicleSampleProject">experimental arcade vehicle sample</a> repo. Feel free to open issues or pull request to the sample, I’ll be vigilante, the main intention is to create a “hello world” example the community could learn from. And of course, along with that I’ll try to keep this article updated!</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 learn how to use the new UNetworkPhysicsComponent component used for networked physics introduced in UE5.4.]]></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>