<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://markjg.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://markjg.com/" rel="alternate" type="text/html" /><updated>2024-03-20T00:01:49+02:00</updated><id>https://markjg.com/feed.xml</id><title type="html">Mark J. G’s programming blog</title><subtitle>mjg's programming and general life blog</subtitle><author><name>MarkJGx</name></author><entry><title type="html">How to inspect Nanite rasterization stats</title><link href="https://markjg.com/blog/nanite-debugging-stats/" rel="alternate" type="text/html" title="How to inspect Nanite rasterization stats" /><published>2024-03-19T00:00:00+02:00</published><updated>2024-03-19T00:00:00+02:00</updated><id>https://markjg.com/blog/nanite-debugging-stats</id><content type="html" xml:base="https://markjg.com/blog/nanite-debugging-stats/"><![CDATA[<h1 id="tldr">tldr;</h1>

<p>To get this Nanite stat visualization on the screen, type <code class="language-plaintext highlighter-rouge">NaniteStats</code> into the nearest Unreal viewport console.
<img src="/assets/images/nanitestatsdebugepicnanitelivestream.png" alt="https://www.youtube.com/watch?v=TMorJX3Nj6U&amp;t=542s" /></p>

<p>This uses a cool piece of technology that Epic built, letting them raster imgui-esque graphics straight from a shader. The system is called <code class="language-plaintext highlighter-rouge">ShaderPrint</code>. This totally makes sense, ever since Epic shifted its internal renderer to a more modern GPU driven system, compute shaders and its brethren dominate rendering logic. Shaders aren’t relegated to just vertex transposition and pixel shading. Welcome to the future!.. Welcome to <em>cough</em> ~2015. Grumpy jokes aside, <code class="language-plaintext highlighter-rouge">NaniteStats</code> is a console command, and the last time I checked those types of viewport console inputs don’t auto complete (last time I checked being 5.3).</p>

<p>This console command accepts a single argument, that lowercase argument can be one of four thing:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">list</code> - list available stat filters, will only list filters that are being used. For example, if you have VSM disabled then the <code class="language-plaintext highlighter-rouge">VirtualShadowMaps</code> won’t be listed. Listed through UE_LOG, not to viewport console.</li>
  <li><code class="language-plaintext highlighter-rouge">primary</code> - specifies that we only care about Nanite stats for the <code class="language-plaintext highlighter-rouge">primary</code> view.</li>
  <li><code class="language-plaintext highlighter-rouge">*</code> - specify a custom Nanite stat filter. Like <code class="language-plaintext highlighter-rouge">VirtualShadowMaps</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">off</code></li>
</ul>

<p><img src="/assets/images/vlcsnap-2024-03-19-23h59m47s443.png" alt="NaniteStats" /></p>

<p>Scene is <a href="https://x.com/DylserX/status/1685927605701406721?s=20">“Corridor” by Dylan Browne</a>.</p>

<p>That’s it. P.S: when I started writing this article I wanted to make it into a bigger thing exploring ShaderPrint and its implementation, however that would have taken an order of magnitude time longer than just this initial piece. Hope this helps.</p>]]></content><author><name>MarkJGx</name></author><category term="Blog" /><category term="unreal" /><category term="ue5" /><summary type="html"><![CDATA[tldr;]]></summary></entry><entry><title type="html">The quest for double precision time in Unreal Engine 5</title><link href="https://markjg.com/blog/the-quest-for-double-precision-time-in-unreal-engine-5/" rel="alternate" type="text/html" title="The quest for double precision time in Unreal Engine 5" /><published>2022-10-14T00:00:00+03:00</published><updated>2022-10-14T00:00:00+03:00</updated><id>https://markjg.com/blog/the-quest-for-double-precision-time-in-unreal-engine-5</id><content type="html" xml:base="https://markjg.com/blog/the-quest-for-double-precision-time-in-unreal-engine-5/"><![CDATA[<h1 id="the-preface">The Preface</h1>

<p>A long while ago I submitted a pull request to EpicGames/UnrealEngine’s main branch implementing a new reflected type called FTime.</p>

<h1 id="the-why">The Why</h1>

<p>Okay, cool… Why wouldn’t I just use a float instead?</p>

<p>What is FTime?</p>
<blockquote>
  <p>FTime is essentially your normal floating point number, but with a catch! It’s specifically intended to be used with timestamps and any game related time functionality. Because sometimes single precision floating point isn’t enough for your grand massively multiplayer duck game, you need your server to up for a longer period of time without time related calculations going haywire because you’ve hit the limit of your precision. However, sometimes it IS enough and the cost double precision doesn’t really make sense. FTime can switch between double floating point precision and single floating point precision with a simple compile time toggle, assuming you’re using a source engine build.</p>
</blockquote>

<h1 id="the-pull-request">The Pull Request</h1>

<p>The following is the description of the pull request I submitted <a href="https://github.com/EpicGames/UnrealEngine/pull/9125">here</a>.</p>

<h2 id="objective">Objective</h2>

<p>This pull request introduces a new UHT compatible type called <code class="language-plaintext highlighter-rouge">FTime</code>, with the aim of solving the game time precision on a case by case basis in a similar fashion to LWC’s <code class="language-plaintext highlighter-rouge">FReal</code>.</p>

<h2 id="motivation">Motivation</h2>

<h3 id="underlying-precision-motivations">Underlying precision motivations</h3>
<p>Single precision floating point does not offer enough precision for a long running dedicated server. The default server tick rate of 30 ticks per second (<code class="language-plaintext highlighter-rouge">NetServerMaxTickRate</code>’s default, precision: 1.0/30.0) would start experiencing time related issues after <code class="language-plaintext highlighter-rouge">6.1</code> days.</p>

<p><img src="https://s0.wp.com/latex.php?latex=value+%3D+pow%282%2C+ceil%28log2%28mantissa+%2A+precision%29%29%29&amp;bg=ffffff&amp;fg=666666&amp;s=0&amp;c=20201002" alt="PrecisionFormula" /> <a href="https://web.archive.org/web/20210116060658/https://blog.demofox.org/2017/11/21/floating-point-precision/">Source</a>
With double precision floating point, the issues would go from appearing after <code class="language-plaintext highlighter-rouge">6.1</code> days to <code class="language-plaintext highlighter-rouge">8,925,512</code> years.</p>

<h3 id="case-by-case-motivations">Case by case motivations</h3>
<p>Nevertheless, outright converting every single usage of game time to a double wouldn’t be ideal. Not all games require double precision floating point time and the benefit from using single precision floating point could be more than marginal. Games that use short-lived worlds and replicate heavily could benefit from single precision. Or single-player mobile games,  why increase the underlying memory footprint just because dedicated servers can’t run for more than 6 days? How is that relevant to the target platform?</p>

<h2 id="design-notes">Design notes</h2>
<p><code class="language-plaintext highlighter-rouge">FTime</code> is a simple double/float alias that lives inside of <code class="language-plaintext highlighter-rouge">Math/Time.h</code>, it’s included in <code class="language-plaintext highlighter-rouge">CoreMinimal.h</code> which covers both programs and games. A new UnrealHeaderTool property called <code class="language-plaintext highlighter-rouge">FTimeProperty</code> has been introduced for the corresponding <code class="language-plaintext highlighter-rouge">FTime</code> type alias ensuring blueprint/reflection compatibility.</p>

<h3 id="uhtcode-gen">UHT/Code-gen</h3>
<p><code class="language-plaintext highlighter-rouge">FTimeProperty</code> traits are generated based on what the current <code class="language-plaintext highlighter-rouge">FTime</code> type is, the traits are chosen by a compile time inheritance conditional, picking either the double property trait or the float one. (Sidebar: I opted to the use UE type traits in this case instead of std’s, not sure what official usage stance is anymore.) When it comes to actually generating the underlying property params, I opt to fake a double/float EPropertyGenFlags based on the PropertyDef’s cpp type.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="c1">// Switch between EPropertyGenFlags Double and Float depending on FPropertyTypeTraitsTime parent, which in itself is driven by the udnerlying type of FTime.</span>
      <span class="k">const</span> <span class="n">TCHAR</span><span class="o">*</span> <span class="n">PropertyGenFlag</span> <span class="o">=</span> <span class="n">PropertyDef</span><span class="p">.</span><span class="n">GetCPPType</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">==</span> <span class="n">TEXT</span><span class="p">(</span><span class="s">"double"</span><span class="p">)</span>
                                       <span class="o">?</span> <span class="n">TEXT</span><span class="p">(</span><span class="s">"UECodeGen_Private::EPropertyGenFlags::Double"</span><span class="p">)</span>
                                       <span class="o">:</span> <span class="n">TEXT</span><span class="p">(</span><span class="s">"UECodeGen_Private::EPropertyGenFlags::Float"</span><span class="p">);</span>
</code></pre></div></div>
<p>From my testing, works well.</p>

<p>I originally wrote this on top <code class="language-plaintext highlighter-rouge">5.0.1</code> which doesn’t have the C# UHT version so that has not been implemented at the time of writing. Only old UHT is aware of <code class="language-plaintext highlighter-rouge">FTimeProperty</code>.</p>

<h2 id="engineering-impact">Engineering impact</h2>
<p>Depending on what happens with this pull request, the ability to change the underlying FTime type would require a custom engine fork, which wouldn’t be an issue for most developers interested in this type of change.</p>

<p>Thanks for reading.</p>

<p>P.S: looks like the cherry picks I made from my engine branch have brought back some 5.0.1 code, will address in the morning.
EDIT: needs a bit of additional with regards to 0.0f and 0.0 consistency, backwards compat in the DemoNetDriver serializer, a few invalid uses of FTime, and C# UHT.</p>]]></content><author><name>MarkJGx</name></author><category term="Blog" /><category term="ue5" /><category term="unreal" /><category term="pull-request" /><summary type="html"><![CDATA[The Preface]]></summary></entry><entry><title type="html">WebAssembly in Unreal, not the other way around!</title><link href="https://markjg.com/blog/web-assembly-in-unreal-not-the-way-around/" rel="alternate" type="text/html" title="WebAssembly in Unreal, not the other way around!" /><published>2022-10-14T00:00:00+03:00</published><updated>2022-10-14T00:00:00+03:00</updated><id>https://markjg.com/blog/web-assembly-in-unreal-not-the-way-around</id><content type="html" xml:base="https://markjg.com/blog/web-assembly-in-unreal-not-the-way-around/"><![CDATA[<h1 id="the-postface">The Postface:</h1>
<p>A long while ago I posted a video demonstrating how useful something like WebAssembly can be for Unreal Engine scripting.
If you’re interested: look forward to a future blog post that I will [[link]] back to here when I write a piece exploring said demo.</p>]]></content><author><name>MarkJGx</name></author><category term="Blog" /><category term="unreal" /><category term="webassembly" /><summary type="html"><![CDATA[The Postface: A long while ago I posted a video demonstrating how useful something like WebAssembly can be for Unreal Engine scripting. If you’re interested: look forward to a future blog post that I will [[link]] back to here when I write a piece exploring said demo.]]></summary></entry><entry><title type="html">PhysX/APEX in Unreal Engine 5</title><link href="https://markjg.com/blog/physx-in-ue5/" rel="alternate" type="text/html" title="PhysX/APEX in Unreal Engine 5" /><published>2022-05-21T00:00:00+03:00</published><updated>2022-05-21T00:00:00+03:00</updated><id>https://markjg.com/blog/physx-in-ue5</id><content type="html" xml:base="https://markjg.com/blog/physx-in-ue5/"><![CDATA[<p><img src="/assets/videos/physxue5.mp4" alt="APEX which relies on PhysX working in Unreal Engine 5" /></p>

<p><strong>Patch for 5.0.1 (branch #5.0)</strong>: <a href="https://github.com/MarkJGx/UnrealEngine/pull/1">https://github.com/MarkJGx/UnrealEngine/pull/1</a></p>

<h1 id="the-preface">The Preface:</h1>
<p>I wanted to see if Unreal 5.0 could use PhysX without too much blood
sweat and tears, and it could. This is more of a hacky proof of concept than an
actually solid PhysX integration, but it works.</p>

<h1 id="the-how">The How?</h1>

<p>This isn’t a perfect re-integration of PhysX specifically when it comes
to the inclusion of the headers in PhysXIncludes.h. I opted to go for
simply and dirty solution of using the relative include paths instead.
I am not a fan of this approach and would like to address when I have
some more time. I did verify that the PhysX module does indeed
specify a valid include directory and that’s where I left it.
I also took the liberty of stripping old references to VS2015 in the
physics modules (“PhysicsCore”) but I suspect that might have some
issues.</p>

<p>Certain collision primitives on SM’s won’t be setup and won’t work.
The underlying collision information is there it just isn’t loaded
properly, recreating the collision will fix this.
This is just something I haven’t gotten around to fixing due
to lack of time.</p>

<p><img src="https://open.spotify.com/track/4UQH52FfgGpzDShKDbBvEA?si=75eb0e5537c4402c" alt="https://open.spotify.com/track/4UQH52FfgGpzDShKDbBvEA?si=75eb0e5537c4402c" /></p>]]></content><author><name>MarkJGx</name></author><category term="Blog" /><category term="unreal" /><category term="pull-request" /><category term="physx" /><category term="apex" /><category term="ue5" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://markjg.com/%7B%22feature%22=%3E%22/assets/images/beforeport.png%22,%20%22thumb%22=%3E%22/assets/images/beforeport.png%22%7D" /><media:content medium="image" url="https://markjg.com/%7B%22feature%22=%3E%22/assets/images/beforeport.png%22,%20%22thumb%22=%3E%22/assets/images/beforeport.png%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Porting custom UE4 shading models to UE5 (or how to tackle the new GBuffer codegen)</title><link href="https://markjg.com/blog/porting-custom-shading-models-to-ue5/" rel="alternate" type="text/html" title="Porting custom UE4 shading models to UE5 (or how to tackle the new GBuffer codegen)" /><published>2022-04-18T00:00:00+03:00</published><updated>2022-04-18T00:00:00+03:00</updated><id>https://markjg.com/blog/porting-custom-shading-models-to-ue5</id><content type="html" xml:base="https://markjg.com/blog/porting-custom-shading-models-to-ue5/"><![CDATA[<p><img src="/assets/images/beforeport.png" alt="Oh no! My custom shading model isn't working anymore!" /></p>
<h1 id="the-preface">The Preface</h1>
<p>This is NOT a tutorial on how to add custom shading models in general, only a quick UE4 to UE5 transition helper.</p>

<h1 id="the-why">The Why?</h1>

<p><strong>Unreal Engine 5</strong> creates GBuffer encoding/decoding functions through code generation (<code class="language-plaintext highlighter-rouge">ShaderCompiler.h</code>/<code class="language-plaintext highlighter-rouge">ShaderGenerationUtil.cpp</code>) instead of hard coding (UE4) said functions. If you managed to port your custom shading model so far and get what you see above, then this blog is for you. Your custom shading model has be introduced into the new GBuffer code generation path.</p>

<h1 id="the-resolution-code">The Resolution (Code)</h1>

<p>Start out by adding your custom shading model to the a struct called <code class="language-plaintext highlighter-rouge">FShaderMaterialPropertyDefines</code> in <code class="language-plaintext highlighter-rouge">ShaderMaterial.h</code> (in the RenderCore module)</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// struct FShaderMaterialPropertyDefines</span>
  <span class="n">uint8</span> <span class="n">MATERIAL_SHADINGMODEL_SINGLELAYERWATER</span> <span class="o">:</span> <span class="mi">1</span><span class="p">;</span>
  <span class="n">uint8</span> <span class="n">MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT</span> <span class="o">:</span> <span class="mi">1</span><span class="p">;</span>

  <span class="c1">// Custom shading model</span>
  <span class="n">uint8</span> <span class="n">MATERIAL_SHADINGMODEL_CUSTOM_SHADING_MODEL</span> <span class="o">:</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>
<p>What you’re doing is adding a flag to the material property defines struct, indicating whether that shading model is currently active or not.</p>

<p>Next up, navigate to <code class="language-plaintext highlighter-rouge">FShaderCompileUtilities::ApplyFetchEnvironment</code> in <code class="language-plaintext highlighter-rouge">ShaderGenerationUtil.cpp</code> and the new custom shading model flag to the function.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// FShaderCompileUtilities::ApplyFetchEnvironment(FShaderMaterialPropertyDefines&amp; SrcDefines, FShaderCompilerEnvironment&amp; OutEnvironment)</span>
  <span class="n">FETCH_COMPILE_BOOL</span><span class="p">(</span><span class="n">MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT</span><span class="p">);</span>

    <span class="c1">// Custom shading model</span>
  <span class="n">FETCH_COMPILE_BOOL</span><span class="p">(</span><span class="n">MATERIAL_SHADINGMODEL_CUSTOM_SHADING_MODEL</span><span class="p">);</span>

</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">FETCH_COMPILE_BOOL()</code> is a macro that checks if the shading model flag is present in the current compilation environment, if so the shading model flag will be set in the defines struct.</p>

<p>Make sure to match the flag with the the underlying custom shading model pre-processor defines you added before, the macro will turn that flag into a string literal and check the compilation environment against said string.</p>

<blockquote>
  <p>The underlying flag is added to the environment by <code class="language-plaintext highlighter-rouge">GetMaterialEnvironment</code> in <code class="language-plaintext highlighter-rouge">MaterialHLSLEmitter.cpp</code>. Fortunately, we don’t have to worry about that, as that’s already done by past you.</p>
</blockquote>

<p>Last but not least, we have to mark our custom shading model as relevant to the GBuffer (and in doing so the GBuffer encoding/decoding codegen).</p>

<p><code class="language-plaintext highlighter-rouge">ShaderGenerationUtil.cpp</code></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="c1">// DetermineUsedMaterialSlots (global scope function)</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">Mat</span><span class="p">.</span><span class="n">MATERIAL_SHADINGMODEL_CUSTOM_SHADING_MODEL</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="n">SetStandardGBufferSlots</span><span class="p">(</span><span class="n">Slots</span><span class="p">,</span> <span class="n">bWriteEmissive</span><span class="p">,</span> <span class="n">bHasTangent</span><span class="p">,</span> <span class="n">bHasVelocity</span><span class="p">,</span> <span class="n">bHasStaticLighting</span><span class="p">,</span> <span class="n">bIsStrataMaterial</span><span class="p">);</span>
    <span class="c1">// Uncomment if your shading model has custom data.</span>
    <span class="c1">// Slots[GBS_CustomData] = bUseCustomData; </span>
  <span class="p">}</span>
</code></pre></div></div>

<p>That’s it, UE5 is aware of your custom shading model and will include it as a valid material slot in the GBuffer codegen. Thanks for reading.</p>

<h1 id="the-result">The Result</h1>

<p><img src="/assets/images/workingshadingmodel.png" alt="There we go, custom shading models in UE5" /></p>]]></content><author><name>MarkJGx</name></author><category term="Blog" /><category term="unreal" /><category term="tricks" /><category term="ue5" /><summary type="html"><![CDATA[The Preface This is NOT a tutorial on how to add custom shading models in general, only a quick UE4 to UE5 transition helper.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://markjg.com/%7B%22feature%22=%3E%22/assets/images/beforeport.png%22,%20%22thumb%22=%3E%22/assets/images/beforeport.png%22%7D" /><media:content medium="image" url="https://markjg.com/%7B%22feature%22=%3E%22/assets/images/beforeport.png%22,%20%22thumb%22=%3E%22/assets/images/beforeport.png%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Looking up values in a TMap/TSet with just the hash key.</title><link href="https://markjg.com/blog/hacking-tmap-and-tset/" rel="alternate" type="text/html" title="Looking up values in a TMap/TSet with just the hash key." /><published>2022-02-18T00:00:00+02:00</published><updated>2022-02-18T00:00:00+02:00</updated><id>https://markjg.com/blog/hacking-tmap-and-tset</id><content type="html" xml:base="https://markjg.com/blog/hacking-tmap-and-tset/"><![CDATA[<p><img src="/assets/images/hashlookupwithoutthewholekey.png" alt="The Goal" /></p>

<p>How do you look up a value in a TMap/TSet without the underlying key type?</p>

<p>Let’s say you just have the hash key (<code class="language-plaintext highlighter-rouge">uint32</code> that you can get from <code class="language-plaintext highlighter-rouge">GetTypeHash</code>) and nothing else. Do not fret!</p>

<h1 id="the-resolution-code">The Resolution (Code)</h1>

<p>I’ve crafted some helpers util’s to help you find a value inside of any TMap (easily translates to TSet). Here’s how you use <code class="language-plaintext highlighter-rouge">TTypeHashOnlyKeyFunc</code>:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">// DO NOT USE the TTypeHashOnlyKeyFunc as a actual KeyFunc on your map/set, it is only worth using for anything lookup related.</span>
<span class="n">TMap</span><span class="o">&lt;</span><span class="n">FExampleKey</span><span class="p">,</span> <span class="n">FExampleValue</span><span class="o">&gt;</span> <span class="n">KeyValueMap</span><span class="p">;</span>


<span class="n">uint32</span> <span class="n">HashKey</span> <span class="o">=</span> <span class="n">GetTypeHash</span><span class="p">(</span><span class="n">Key</span><span class="p">);</span>
<span class="n">TTypeHashOnlyKeyFunc</span><span class="o">&lt;</span><span class="n">FExampleKey</span><span class="p">,</span> <span class="n">FExampleValue</span><span class="o">&gt;::</span><span class="n">FHashHelper</span> <span class="nf">Comparable</span><span class="p">(</span><span class="n">HashKey</span><span class="p">);</span>
<span class="n">FExampleValue</span><span class="o">*</span> <span class="n">Value</span> <span class="o">=</span> <span class="n">Comparable</span><span class="p">.</span><span class="n">FindByHash</span><span class="p">(</span><span class="n">KeyValueMap</span><span class="p">);</span>

<span class="k">if</span><span class="p">(</span><span class="n">Value</span><span class="p">)</span>
<span class="p">{</span>
  <span class="c1">// Woohoo</span>
<span class="p">}</span>

</code></pre></div></div>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">TKeyType</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">TValueType</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">bInAllowDuplicateKeys</span> <span class="o">=</span> <span class="nb">false</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">TSetAllocator</span> <span class="o">=</span> <span class="n">FDefaultSetAllocator</span><span class="p">&gt;</span>
<span class="k">struct</span> <span class="nc">TTypeHashOnlyKeyFunc</span> <span class="o">:</span> <span class="k">public</span> <span class="n">TDefaultMapKeyFuncs</span><span class="o">&lt;</span><span class="n">TKeyType</span><span class="p">,</span> <span class="n">TValueType</span><span class="p">,</span> <span class="n">bInAllowDuplicateKeys</span><span class="o">&gt;</span>
<span class="p">{</span>
  <span class="c1">// Type that is the same size as the key type, the KeyFunc functions expect a key type, so let's just pretend to be one! Only tested with TMap's FindByHash.</span>
  <span class="k">struct</span> <span class="nc">FHashHelper</span>
  <span class="p">{</span>
    <span class="k">static</span> <span class="k">constexpr</span> <span class="n">uint32</span> <span class="n">ByteRemainder</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">max</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">uint32</span><span class="p">),</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">TKeyType</span><span class="p">))</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">uint32</span><span class="p">);</span>

    <span class="n">FHashHelper</span><span class="p">(</span><span class="k">const</span> <span class="n">uint32</span><span class="o">&amp;</span> <span class="n">Key</span><span class="p">)</span> <span class="o">:</span> <span class="n">Key</span><span class="p">(</span><span class="n">Key</span><span class="p">),</span> <span class="n">RemainderBytes</span><span class="p">{}</span>
    <span class="p">{</span>
    <span class="p">};</span>

    <span class="n">uint32</span> <span class="n">Key</span><span class="p">;</span>
    <span class="n">uint8</span>  <span class="n">RemainderBytes</span><span class="p">[</span><span class="n">ByteRemainder</span><span class="p">];</span>

    <span class="n">TKeyType</span> <span class="n">GetComparable</span><span class="p">()</span> <span class="k">const</span>
    <span class="p">{</span>
      <span class="c1">// We are the key! (not)</span>
      <span class="k">return</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">TKeyType</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">using</span> <span class="n">FTypeHashOnlyMap</span> <span class="o">=</span> <span class="n">TMap</span><span class="o">&lt;</span><span class="n">TKeyType</span><span class="p">,</span> <span class="n">TValueType</span><span class="p">,</span> <span class="n">TSetAllocator</span><span class="p">,</span> <span class="n">TTypeHashOnlyKeyFunc</span><span class="o">&lt;</span>
                                    <span class="n">TKeyType</span><span class="p">,</span> <span class="n">TValueType</span><span class="p">,</span> <span class="n">bInAllowDuplicateKeys</span><span class="p">,</span> <span class="n">TSetAllocator</span><span class="o">&gt;&gt;</span><span class="p">;</span>


    <span class="n">TValueType</span><span class="o">*</span> <span class="n">FindByHash</span><span class="p">(</span><span class="n">TMap</span><span class="o">&lt;</span><span class="n">TKeyType</span><span class="p">,</span> <span class="n">TValueType</span><span class="p">,</span> <span class="n">TSetAllocator</span><span class="o">&gt;&amp;</span> <span class="n">MapType</span><span class="p">)</span> <span class="k">const</span>
    <span class="p">{</span>
      <span class="c1">// let's pretend you have our KeyFunc handler instead!</span>
      <span class="k">return</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">FTypeHashOnlyMap</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">MapType</span><span class="p">).</span><span class="n">FindByHash</span><span class="p">(</span><span class="n">Key</span><span class="p">,</span> <span class="n">GetComparable</span><span class="p">());</span>
    <span class="p">}</span>
  <span class="p">};</span>

  <span class="k">static_assert</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">FHashHelper</span><span class="p">)</span> <span class="o">==</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">TKeyType</span><span class="p">),</span> <span class="s">"FHashKey must match the size of the key type!"</span><span class="p">);</span>


  <span class="n">FORCEINLINE</span> <span class="k">static</span> <span class="kt">bool</span> <span class="n">Matches</span><span class="p">(</span><span class="n">TKeyType</span> <span class="n">A</span><span class="p">,</span> <span class="n">TKeyType</span> <span class="n">B</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="k">const</span> <span class="n">uint32</span> <span class="n">KeyA</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">FHashHelper</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">A</span><span class="p">).</span><span class="n">Key</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">uint32</span> <span class="n">KeyB</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">FHashHelper</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">B</span><span class="p">).</span><span class="n">Key</span><span class="p">;</span>

    <span class="k">return</span> <span class="n">KeyA</span> <span class="o">==</span> <span class="n">KeyB</span><span class="p">;</span>
  <span class="p">}</span>


  <span class="n">FORCEINLINE</span> <span class="k">static</span> <span class="k">typename</span> <span class="n">TEnableIf</span><span class="o">&lt;</span><span class="k">sizeof</span><span class="p">(</span><span class="n">FHashHelper</span><span class="p">)</span> <span class="o">==</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">TKeyType</span><span class="p">),</span> <span class="n">TKeyType</span><span class="o">&gt;::</span><span class="n">Type</span> <span class="n">GetSetKey</span><span class="p">(</span>
    <span class="k">typename</span> <span class="n">TTypeHashOnlyKeyFunc</span><span class="o">::</span><span class="n">ElementInitType</span> <span class="n">Element</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="k">static_assert</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">TKeyType</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">4</span><span class="p">,</span> <span class="s">"The key type has to be bigger than 4 bytes!"</span><span class="p">);</span>

    <span class="n">FHashHelper</span> <span class="n">KeyHack</span><span class="p">(</span><span class="n">TTypeHashOnlyKeyFunc</span><span class="o">::</span><span class="n">GetKeyHash</span><span class="p">(</span><span class="n">Element</span><span class="p">.</span><span class="n">Key</span><span class="p">));</span>
    <span class="k">return</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">TKeyType</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">KeyHack</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">};</span>

</code></pre></div></div>

<h1 id="the-explanation">The Explanation</h1>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">KeyType</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">ValueType</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">SetAllocator</span> <span class="cm">/*= FDefaultSetAllocator*/</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">KeyFuncs</span> <span class="cm">/*= TDefaultMapHashableKeyFuncs&lt;KeyType,ValueType,false&gt;*/</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">TMap</span> <span class="o">:</span> <span class="k">public</span> <span class="n">TSortableMapBase</span><span class="o">&lt;</span><span class="n">KeyType</span><span class="p">,</span> <span class="n">ValueType</span><span class="p">,</span> <span class="n">SetAllocator</span><span class="p">,</span> <span class="n">KeyFuncs</span><span class="o">&gt;</span>
<span class="c1">//                                                                                              ^                                                                                  </span>
<span class="c1">// -------------------------------------------------------------------------&gt;                   |</span>
<span class="c1">//                                                                                              right here!</span>
</code></pre></div></div>

<p>Turns out Epic’s container types let you implement your own KeyFuncs handler(s)! By default TMap’s uses a <code class="language-plaintext highlighter-rouge">TDefaultMapHashableKeyFuncs&lt;KeyType,ValueType,false&gt;</code> KeyFunc, so what do the KeyFuncs handlers do? 
They act as middlemen for the underlying hash map and set containers, offering static compile time functions that containers use to check for:</p>
<ul>
  <li>key equality using <code class="language-plaintext highlighter-rouge">Matches(KeyLHS, KeyRHS)</code></li>
  <li>key type hash function <code class="language-plaintext highlighter-rouge">GetKeyHash(KeyType)</code>,</li>
  <li>getting the key from a the key/value pair <code class="language-plaintext highlighter-rouge">GetSetKey(Element/* A key value pair*/)</code></li>
</ul>

<p>and so on. It is really nice being able to override the default container behavior through a template type argument. Which is especially useful if you want to have different hashing behavior per container without changing the underlying key type.</p>

<h2 id="the-example">The Example</h2>

<p>For example, if you have a regular old string and didn’t want to pay for a costly string hash function operation, you could implement a more performant hashing function directly into the container type without ever having to make a new string type. Pretty neat, huh?</p>

<h2 id="the-underlying-logic">The Underlying logic</h2>

<p>With that out of the way, let me present you with a joke: A map container, a hash key and no one else walks into a bar…</p>

<p>Wait let me try that again: 
What if you only had a set/map, a hash key you got from wherever and nothing else. You know that the underlying hash key represents a key inside of that map, unfortunately there’s no easy way of looking it up.</p>

<p>You see by default TSet/TMap needs the whole type to look up the correct value, as the hash key simply points to the underlying hash bucket and from then on out it is a O(n) search, which compares each element in the hash bucket chain<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> through a equality operation, calling <code class="language-plaintext highlighter-rouge">Matches</code> in the KeyFuncs type. We can get around that limitation though, instead of just using the hash key for the hash bucket lookup, let’s use it for underlying equality check as well. Problem solved! Note: this method isn’t perfect, as you are the mercy of your hash function. If you can somehow manage a collision, the usual equality check will not save you. Although you potentially have 2^32 -1 possible hashes, so that’s alright.</p>

<p>Thanks for reading!</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Chain, I don’t mean a closed address linked list bucket. The actual chain’s memory is still contiguous (Open addressing) (TSparseArray: the memory is contiguous, but the elements are not necessarily next to each other in memory). <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>MarkJGx</name></author><category term="Blog" /><category term="unreal" /><category term="tricks" /><summary type="html"><![CDATA[How do you look up a value in a TMap/TSet without the underlying key type? Let’s say you just have the hash key (uint32 that you can get from GetTypeHash) and nothing else. Do not fret! The Resolution (Code) I’ve crafted some helpers util’s to help you find a value inside of any TMap (easily translates to TSet). Here’s how you use TTypeHashOnlyKeyFunc: // DO NOT USE the TTypeHashOnlyKeyFunc as a actual KeyFunc on your map/set, it is only worth using for anything lookup related. TMap&lt;FExampleKey, FExampleValue&gt; KeyValueMap; uint32 HashKey = GetTypeHash(Key); TTypeHashOnlyKeyFunc&lt;FExampleKey, FExampleValue&gt;::FHashHelper Comparable(HashKey); FExampleValue* Value = Comparable.FindByHash(KeyValueMap); if(Value) { // Woohoo } template &lt;typename TKeyType, typename TValueType, bool bInAllowDuplicateKeys = false, typename TSetAllocator = FDefaultSetAllocator&gt; struct TTypeHashOnlyKeyFunc : public TDefaultMapKeyFuncs&lt;TKeyType, TValueType, bInAllowDuplicateKeys&gt; { // Type that is the same size as the key type, the KeyFunc functions expect a key type, so let's just pretend to be one! Only tested with TMap's FindByHash. struct FHashHelper { static constexpr uint32 ByteRemainder = std::max(sizeof(uint32), sizeof(TKeyType)) - sizeof(uint32); FHashHelper(const uint32&amp; Key) : Key(Key), RemainderBytes{} { }; uint32 Key; uint8 RemainderBytes[ByteRemainder]; TKeyType GetComparable() const { // We are the key! (not) return reinterpret_cast&lt;const TKeyType&amp;&gt;(*this); } using FTypeHashOnlyMap = TMap&lt;TKeyType, TValueType, TSetAllocator, TTypeHashOnlyKeyFunc&lt; TKeyType, TValueType, bInAllowDuplicateKeys, TSetAllocator&gt;&gt;; TValueType* FindByHash(TMap&lt;TKeyType, TValueType, TSetAllocator&gt;&amp; MapType) const { // let's pretend you have our KeyFunc handler instead! return reinterpret_cast&lt;FTypeHashOnlyMap&amp;&gt;(MapType).FindByHash(Key, GetComparable()); } }; static_assert(sizeof(FHashHelper) == sizeof(TKeyType), "FHashKey must match the size of the key type!"); FORCEINLINE static bool Matches(TKeyType A, TKeyType B) { const uint32 KeyA = reinterpret_cast&lt;FHashHelper&amp;&gt;(A).Key; const uint32 KeyB = reinterpret_cast&lt;FHashHelper&amp;&gt;(B).Key; return KeyA == KeyB; } FORCEINLINE static typename TEnableIf&lt;sizeof(FHashHelper) == sizeof(TKeyType), TKeyType&gt;::Type GetSetKey( typename TTypeHashOnlyKeyFunc::ElementInitType Element) { static_assert(sizeof(TKeyType) &gt;= 4, "The key type has to be bigger than 4 bytes!"); FHashHelper KeyHack(TTypeHashOnlyKeyFunc::GetKeyHash(Element.Key)); return reinterpret_cast&lt;TKeyType&amp;&gt;(KeyHack); } }; The Explanation template&lt;typename KeyType, typename ValueType, typename SetAllocator /*= FDefaultSetAllocator*/, typename KeyFuncs /*= TDefaultMapHashableKeyFuncs&lt;KeyType,ValueType,false&gt;*/&gt; class TMap : public TSortableMapBase&lt;KeyType, ValueType, SetAllocator, KeyFuncs&gt; // ^ // -------------------------------------------------------------------------&gt; | // right here! Turns out Epic’s container types let you implement your own KeyFuncs handler(s)! By default TMap’s uses a TDefaultMapHashableKeyFuncs&lt;KeyType,ValueType,false&gt; KeyFunc, so what do the KeyFuncs handlers do? They act as middlemen for the underlying hash map and set containers, offering static compile time functions that containers use to check for: key equality using Matches(KeyLHS, KeyRHS) key type hash function GetKeyHash(KeyType), getting the key from a the key/value pair GetSetKey(Element/* A key value pair*/) and so on. It is really nice being able to override the default container behavior through a template type argument. Which is especially useful if you want to have different hashing behavior per container without changing the underlying key type. The Example For example, if you have a regular old string and didn’t want to pay for a costly string hash function operation, you could implement a more performant hashing function directly into the container type without ever having to make a new string type. Pretty neat, huh? The Underlying logic With that out of the way, let me present you with a joke: A map container, a hash key and no one else walks into a bar… Wait let me try that again: What if you only had a set/map, a hash key you got from wherever and nothing else. You know that the underlying hash key represents a key inside of that map, unfortunately there’s no easy way of looking it up. You see by default TSet/TMap needs the whole type to look up the correct value, as the hash key simply points to the underlying hash bucket and from then on out it is a O(n) search, which compares each element in the hash bucket chain1 through a equality operation, calling Matches in the KeyFuncs type. We can get around that limitation though, instead of just using the hash key for the hash bucket lookup, let’s use it for underlying equality check as well. Problem solved! Note: this method isn’t perfect, as you are the mercy of your hash function. If you can somehow manage a collision, the usual equality check will not save you. Although you potentially have 2^32 -1 possible hashes, so that’s alright. Thanks for reading! Chain, I don’t mean a closed address linked list bucket. The actual chain’s memory is still contiguous (Open addressing) (TSparseArray: the memory is contiguous, but the elements are not necessarily next to each other in memory). &#8617;]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://markjg.com/%7B%22feature%22=%3E%22/assets/images/hashlookupwithoutthewholekey.png%22,%20%22thumb%22=%3E%22/assets/images/hashlookupwithoutthewholekey.png%22%7D" /><media:content medium="image" url="https://markjg.com/%7B%22feature%22=%3E%22/assets/images/hashlookupwithoutthewholekey.png%22,%20%22thumb%22=%3E%22/assets/images/hashlookupwithoutthewholekey.png%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Debugging the Unreal Header Tool (UHT)</title><link href="https://markjg.com/blog/debugging-uht/" rel="alternate" type="text/html" title="Debugging the Unreal Header Tool (UHT)" /><published>2022-02-14T00:00:00+02:00</published><updated>2022-02-14T00:00:00+02:00</updated><id>https://markjg.com/blog/debugging-uht</id><content type="html" xml:base="https://markjg.com/blog/debugging-uht/"><![CDATA[<p>Here’s a quick excerpt from a forum post on how to debug Unreal Header Tool (UHT) in Unreal.</p>

<blockquote>
  <p>“Build UnrealHeaderTool in debug configuration (it needs to be just ‘Debug’, not ‘Debug Editor’ or anything else). You can just switch you active configuration to debug to do this and build UnrealHeaderTool. Do this after you build your target project and then go to <strong>Engine\Programs\UnrealHeaderTool\Saved\Logs\UnrealHeaderTool.log</strong> and copy the command line that’s in the log (note you only need the .uproject path and .manifest path) to UnrealHeaderTool’s command line in Visual Studio project properties <strong>(Configuration Properties -&gt; Debugging -&gt; Command Arguments)</strong>. The set UnrealHeaderTool as your startup project and launch it in Visual Studio. You will now be able to debug UHT as well as the plugin (if it’s loaded).” - robert.manuszewski (slightly modified)</p>
</blockquote>

<p>Source: <a href="https://forums.unrealengine.com/t/scripting-language-extensions-via-plugins/3542/181?u=markjg">https://forums.unrealengine.com/t/scripting-language-extensions-via-plugins/3542/181?u=markjg</a></p>]]></content><author><name>MarkJGx</name></author><category term="Blog" /><category term="unreal" /><category term="tricks" /><summary type="html"><![CDATA[Here’s a quick excerpt from a forum post on how to debug Unreal Header Tool (UHT) in Unreal. “Build UnrealHeaderTool in debug configuration (it needs to be just ‘Debug’, not ‘Debug Editor’ or anything else). You can just switch you active configuration to debug to do this and build UnrealHeaderTool. Do this after you build your target project and then go to Engine\Programs\UnrealHeaderTool\Saved\Logs\UnrealHeaderTool.log and copy the command line that’s in the log (note you only need the .uproject path and .manifest path) to UnrealHeaderTool’s command line in Visual Studio project properties (Configuration Properties -&gt; Debugging -&gt; Command Arguments). The set UnrealHeaderTool as your startup project and launch it in Visual Studio. You will now be able to debug UHT as well as the plugin (if it’s loaded).” - robert.manuszewski (slightly modified) Source: https://forums.unrealengine.com/t/scripting-language-extensions-via-plugins/3542/181?u=markjg]]></summary></entry><entry><title type="html">Speeding up Unreal Engine (Engine) development by caching your binary dependencies</title><link href="https://markjg.com/blog/speeding-unreal/" rel="alternate" type="text/html" title="Speeding up Unreal Engine (Engine) development by caching your binary dependencies" /><published>2022-01-09T00:00:00+02:00</published><updated>2022-01-09T00:00:00+02:00</updated><id>https://markjg.com/blog/speeding-unreal</id><content type="html" xml:base="https://markjg.com/blog/speeding-unreal/"><![CDATA[<p>You can significantly speed up your Unreal Engine, <strong>engine</strong> not project, workflow by opting to locally cache Unreal’s binary dependencies in a local directory. This is especially useful if you are constantly switching between engine versions, or want to have several local repos checked out.</p>

<p>Add a new global environment variable with the following name and value:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UE4_GITDEPS_ARGS</span><span class="o">=</span><span class="nt">--cache</span><span class="o">=</span><span class="n">C:\Users\markjg\clones\GitDepsCache</span><span class="w"> </span><span class="nt">--cache-days</span><span class="o">=</span><span class="mi">180</span><span class="w"> </span><span class="nt">--cache-size-multiplier</span><span class="o">=</span><span class="mi">5</span><span class="w">
</span></code></pre></div></div>

<p>The important line here is the <code class="language-plaintext highlighter-rouge">--cache=YourLocalCacheDirectoryPathHere</code>, that’s where your local cache will live. This directory can rapidly grow in size, at the time of writing my cache directory takes up around 26GB’s. You’re done!</p>

<p>How does this work? Every time you run the engine’s <code class="language-plaintext highlighter-rouge">./Setup</code> executable, that’ll will invoke the <code class="language-plaintext highlighter-rouge">GitDependency</code> tool. Which checks for any invalid (outdated, non-existent) resources and opts to download them. Dependency fetches also happen after certain git operations, specifically on post checkout and post merge (Invoked by git hooks <code class="language-plaintext highlighter-rouge">.git\hooks</code>, as <code class="language-plaintext highlighter-rouge">post-checkout</code>, <code class="language-plaintext highlighter-rouge">post-merge</code>).</p>

<p>This is really painful and tedious on slow connections, you can opt to reduce the redundant downloads by caching the binaries by setting up the <code class="language-plaintext highlighter-rouge">UE4_GITDEPS_ARGS</code> environment variable, which as you can guess, passes additional arguments the GitDependency tool. All we care about now is caching, but there’s a few other arguments you can play around with.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PS</span><span class="w"> </span><span class="nx">C:\Users\markjg\clones\UnrealEngineFive</span><span class="err">&gt;</span><span class="w"> </span><span class="o">.</span><span class="nx">\Engine\Binaries\DotNET\GitDependencies.exe</span><span class="w"> </span><span class="nf">?</span><span class="w">
</span><span class="nx">Invalid</span><span class="w"> </span><span class="nx">command</span><span class="w"> </span><span class="nx">line</span><span class="w"> </span><span class="nx">parameter:</span><span class="w"> </span><span class="nf">?</span><span class="w">

</span><span class="n">Usage:</span><span class="w">
   </span><span class="nx">GitDependencies</span><span class="w"> </span><span class="p">[</span><span class="n">options</span><span class="p">]</span><span class="w">

</span><span class="n">Options:</span><span class="w">
   </span><span class="nt">--all</span><span class="w">                         </span><span class="nx">Sync</span><span class="w"> </span><span class="nx">all</span><span class="w"> </span><span class="nx">folders</span><span class="w">
   </span><span class="nt">--include</span><span class="o">=</span><span class="err">&lt;</span><span class="n">X</span><span class="err">&gt;</span><span class="w">                 </span><span class="nx">Include</span><span class="w"> </span><span class="nx">binaries</span><span class="w"> </span><span class="nx">in</span><span class="w"> </span><span class="nx">folders</span><span class="w"> </span><span class="nx">called</span><span class="w"> </span><span class="err">&lt;</span><span class="nx">X</span><span class="err">&gt;</span><span class="w">
   </span><span class="nt">--exclude</span><span class="o">=</span><span class="err">&lt;</span><span class="n">X</span><span class="err">&gt;</span><span class="w">                 </span><span class="nx">Exclude</span><span class="w"> </span><span class="nx">binaries</span><span class="w"> </span><span class="nx">in</span><span class="w"> </span><span class="nx">folders</span><span class="w"> </span><span class="nx">called</span><span class="w"> </span><span class="err">&lt;</span><span class="nx">X</span><span class="err">&gt;</span><span class="w">
   </span><span class="nt">--prompt</span><span class="w">                      </span><span class="n">Prompt</span><span class="w"> </span><span class="nx">before</span><span class="w"> </span><span class="nx">overwriting</span><span class="w"> </span><span class="nx">modified</span><span class="w"> </span><span class="nx">files</span><span class="w">
   </span><span class="nt">--force</span><span class="w">                       </span><span class="n">Always</span><span class="w"> </span><span class="nx">overwrite</span><span class="w"> </span><span class="nx">modified</span><span class="w"> </span><span class="nx">files</span><span class="w">
   </span><span class="nt">--root</span><span class="o">=</span><span class="err">&lt;</span><span class="n">PATH</span><span class="err">&gt;</span><span class="w">                 </span><span class="nx">Set</span><span class="w"> </span><span class="nx">the</span><span class="w"> </span><span class="nx">repository</span><span class="w"> </span><span class="nx">directory</span><span class="w"> </span><span class="nx">to</span><span class="w"> </span><span class="nx">be</span><span class="w"> </span><span class="nx">sync</span><span class="w">
   </span><span class="nt">--threads</span><span class="o">=</span><span class="err">&lt;</span><span class="n">N</span><span class="err">&gt;</span><span class="w">                 </span><span class="nx">Use</span><span class="w"> </span><span class="nx">N</span><span class="w"> </span><span class="nx">threads</span><span class="w"> </span><span class="nx">when</span><span class="w"> </span><span class="nx">downloading</span><span class="w"> </span><span class="nx">new</span><span class="w"> </span><span class="nx">files</span><span class="w">
   </span><span class="nt">--dry-run</span><span class="w">                     </span><span class="n">Print</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="nx">list</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">outdated</span><span class="w"> </span><span class="nx">files</span><span class="w"> </span><span class="nx">and</span><span class="w"> </span><span class="nx">exit</span><span class="w">
   </span><span class="nt">--max-retries</span><span class="w">                 </span><span class="n">Override</span><span class="w"> </span><span class="nx">maximum</span><span class="w"> </span><span class="nx">number</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">retries</span><span class="w"> </span><span class="nx">per</span><span class="w"> </span><span class="nx">file</span><span class="w">
   </span><span class="nt">--proxy</span><span class="o">=</span><span class="err">&lt;</span><span class="n">user:password</span><span class="err">@</span><span class="nx">url</span><span class="err">&gt;</span><span class="w">   </span><span class="nx">Sets</span><span class="w"> </span><span class="nx">the</span><span class="w"> </span><span class="nx">HTTP</span><span class="w"> </span><span class="nx">proxy</span><span class="w"> </span><span class="nx">address</span><span class="w"> </span><span class="nx">and</span><span class="w"> </span><span class="nx">credentials</span><span class="w">
   </span><span class="nt">--cache</span><span class="o">=</span><span class="err">&lt;</span><span class="n">PATH</span><span class="err">&gt;</span><span class="w">                </span><span class="nx">Specifies</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="nx">custom</span><span class="w"> </span><span class="nx">path</span><span class="w"> </span><span class="nx">for</span><span class="w"> </span><span class="nx">the</span><span class="w"> </span><span class="nx">download</span><span class="w"> </span><span class="nx">cache</span><span class="w">
   </span><span class="nt">--cache-size-multiplier</span><span class="o">=</span><span class="err">&lt;</span><span class="n">N</span><span class="err">&gt;</span><span class="w">   </span><span class="nx">Cache</span><span class="w"> </span><span class="nx">size</span><span class="w"> </span><span class="nx">as</span><span class="w"> </span><span class="nx">multiplier</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">current</span><span class="w"> </span><span class="nx">download</span><span class="w">
   </span><span class="nt">--cache-days</span><span class="o">=</span><span class="err">&lt;</span><span class="n">N</span><span class="err">&gt;</span><span class="w">              </span><span class="nx">Number</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">days</span><span class="w"> </span><span class="nx">to</span><span class="w"> </span><span class="nx">keep</span><span class="w"> </span><span class="nx">entries</span><span class="w"> </span><span class="nx">in</span><span class="w"> </span><span class="nx">the</span><span class="w"> </span><span class="nx">cache</span><span class="w">
   </span><span class="nt">--no-cache</span><span class="w">                    </span><span class="n">Disable</span><span class="w"> </span><span class="nx">caching</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">downloaded</span><span class="w"> </span><span class="nx">files</span><span class="w">

</span><span class="n">Detected</span><span class="w"> </span><span class="nx">settings:</span><span class="w">
   </span><span class="n">Excluded</span><span class="w"> </span><span class="nx">folders:</span><span class="w"> </span><span class="nx">none</span><span class="w">
   </span><span class="n">Proxy</span><span class="w"> </span><span class="nx">server:</span><span class="w"> </span><span class="nx">none</span><span class="w">
   </span><span class="n">Download</span><span class="w"> </span><span class="nx">cache:</span><span class="w"> </span><span class="nx">C:\Users\markjg\clones\GitDepsCache</span><span class="w">

</span><span class="n">Default</span><span class="w"> </span><span class="nx">arguments</span><span class="w"> </span><span class="nx">can</span><span class="w"> </span><span class="nx">be</span><span class="w"> </span><span class="nx">set</span><span class="w"> </span><span class="nx">through</span><span class="w"> </span><span class="nx">the</span><span class="w"> </span><span class="nx">UE4_GITDEPS_ARGS</span><span class="w"> </span><span class="nx">environment</span><span class="w"> </span><span class="nx">variable.</span><span class="w">
</span></code></pre></div></div>

<p>That’s it.</p>]]></content><author><name>MarkJGx</name></author><category term="Blog" /><category term="unreal" /><category term="tips" /><summary type="html"><![CDATA[You can significantly speed up your Unreal Engine, engine not project, workflow by opting to locally cache Unreal’s binary dependencies in a local directory. This is especially useful if you are constantly switching between engine versions, or want to have several local repos checked out.]]></summary></entry></feed>