<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>subsystem on stillcooking.dev</title>
    <link>https://stillcooking.dev/en/tags/subsystem/</link>
    <description>Personal portfolio and technical notes</description>
    <generator>Hugo</generator>
    <language>en-US</language>
    
    <copyright>© 2026 stillcooking.dev — CC BY 4.0, https://creativecommons.org/licenses/by/4.0/</copyright>
    <lastBuildDate>Thu, 13 Aug 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://stillcooking.dev/en/tags/subsystem/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>ShouldCreateSubsystem and the subsystem class hierarchy</title>
      <link>https://stillcooking.dev/en/topics/unreal-engine/gameplay-framework/should-create-subsystem-picks-the-class/</link>
      <pubDate>Thu, 13 Aug 2026 00:00:00 +0000</pubDate>
      
      <guid isPermaLink="true">https://stillcooking.dev/en/topics/unreal-engine/gameplay-framework/should-create-subsystem-picks-the-class/</guid>
      <description>The subsystem collection asks each non-abstract class&amp;rsquo;s CDO whether to create an instance. A subclass does not automatically replace its parent; both can exist side by side. This explains how to move the implementation into Blueprint, how to keep a C++ fallback when the child is missing, and why GetSubsystem&lt;T&gt;() can still find the derived instance.</description>
      <content:encoded><![CDATA[<p><code>ShouldCreateSubsystem</code> reads like an on/off switch: return <code>false</code>, and the subsystem is not created. That is how it behaves in its most common use, but that is only one use of a broader mechanism. The method decides whether an instance of each candidate class is created. Across a hierarchy, those individual decisions determine which implementations end up in the collection.</p>
<p>The difference only shows up once the hierarchy has more than one level, which happens as soon as someone creates a Blueprint child of a subsystem written in C++.</p>
<p>I covered the lifecycle contract and where each collection starts in a separate post: <a href="/en/topics/unreal-engine/gameplay-framework/subsystem-lifecycle-init-order">Subsystem and manager actor lifecycles</a>. Here, I&rsquo;m focusing on one specific part: the loop that picks the classes.</p>
<h2 id="a-subclass-does-not-replace-its-parent">A subclass does not replace its parent</h2>
<p>The collection gathers derived classes in a single call and runs <strong>every one</strong> of them through the same procedure (<code>Engine/Private/Subsystems/SubsystemCollection.cpp:209</code>):</p>
<div class="highlight" data-lang="cpp">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">TArray</span><span class="o">&lt;</span><span class="n">UClass</span><span class="o">*&gt;</span> <span class="n">SubsystemClasses</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">GetDerivedClasses</span><span class="p">(</span><span class="n">BaseType</span><span class="p">,</span> <span class="n">SubsystemClasses</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="n">UClass</span><span class="o">*</span> <span class="nl">SubsystemClass</span> <span class="p">:</span> <span class="n">SubsystemClasses</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">AddAndInitializeSubsystem</span><span class="p">(</span><span class="n">SubsystemClass</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>There is no selection of the most-derived class here, no collapsing of the hierarchy, no rule that the child wins over the parent. The question is asked separately of each entry’s own CDO (<code>SubsystemCollection.cpp:331</code>):</p>
<div class="highlight" data-lang="cpp">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">const</span> <span class="n">USubsystem</span><span class="o">*</span> <span class="n">CDO</span> <span class="o">=</span> <span class="n">SubsystemClass</span><span class="o">-&gt;</span><span class="n">GetDefaultObject</span><span class="o">&lt;</span><span class="n">USubsystem</span><span class="o">&gt;</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">CDO</span><span class="o">-&gt;</span><span class="n">ShouldCreateSubsystem</span><span class="p">(</span><span class="n">Outer</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">USubsystem</span><span class="o">*</span> <span class="n">Subsystem</span> <span class="o">=</span> <span class="n">NewObject</span><span class="o">&lt;</span><span class="n">USubsystem</span><span class="o">&gt;</span><span class="p">(</span><span class="n">Outer</span><span class="p">,</span> <span class="n">SubsystemClass</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">SubsystemMap</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">SubsystemClass</span><span class="p">,</span> <span class="n">Subsystem</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div></div>
<p>The consequence is straightforward and easy to miss: a Blueprint child of a C++ class joins the parent as a <strong>second, independent instance</strong>. The base implementation returns <code>true</code> (<code>Subsystem.h:61</code>), so if nobody has overridden the method, two instances live in the collection. One of the C++ class, one of the Blueprint class. Both have run <code>Initialize</code>, both carry their own state, and neither knows about the other.</p>
<div class="callout callout--warning">
  <div class="callout__title">Warning</div>
  Creating a Blueprint child of a subsystem takes two clicks and no C++ at all. From that point on, every counter, cache, and handler in that subsystem exists in duplicate. The engine reports nothing, because as far as the collection is concerned these are two different classes and two valid entries in <code>SubsystemMap</code>.
</div>

<p>Two filters run before that question is asked. The first rejects abstract classes (<code>:318</code>), the second rejects non-authoritative ones (<code>:324</code>):</p>
<div class="highlight" data-lang="cpp">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Do not create instances of classes that aren&#39;t authoritative.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="p">(</span><span class="n">SubsystemClass</span><span class="o">-&gt;</span><span class="n">GetAuthoritativeClass</span><span class="p">()</span> <span class="o">!=</span> <span class="n">SubsystemClass</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">nullptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>The second one says something about intent. <code>GetAuthoritativeClass</code> filters out the intermediate classes produced when Blueprints are recompiled, which means Epic hardened this path for Blueprint classes. A Blueprint subsystem is a scenario the engine anticipates.</p>
<h2 id="the-switch-false-in-c-true-in-blueprint">The switch: <code>false</code> in C++, <code>true</code> in Blueprint</h2>
<p>The question is asked separately of every CDO, and Blueprint defaults <strong>live on the CDO</strong> of their class. The same flag can therefore hold a different value at each level of the hierarchy, which turns it into a switch between implementation layers:</p>
<div class="highlight" data-lang="cpp">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">UCLASS</span><span class="p">(</span><span class="n">Blueprintable</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">MYGAME_API</span> <span class="nl">UMySubsystem</span> <span class="p">:</span> <span class="k">public</span> <span class="n">UGameInstanceSubsystem</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">GENERATED_BODY</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">protected</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/** Read from the CDO only - set to true in the Blueprint child that implements this. */</span>
</span></span><span class="line"><span class="cl">    <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">&#34;Config&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">bShouldCreateSubsystem</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">ShouldCreateSubsystem</span><span class="p">(</span><span class="n">UObject</span><span class="o">*</span> <span class="n">Outer</span><span class="p">)</span> <span class="k">const</span> <span class="k">override</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Super</span><span class="o">::</span><span class="n">ShouldCreateSubsystem</span><span class="p">(</span><span class="n">Outer</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">bShouldCreateSubsystem</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div></div>
<p>With <code>false</code> on the C++ side and <code>true</code> in the Blueprint class defaults, exactly one instance ends up in the collection. The C++ class becomes a skeleton: it defines the interface, the <code>BlueprintImplementableEvent</code> declarations, and everything Blueprint cannot declare on its own, while the implementation lives one level below.</p>
<p>This works because <code>ShouldCreateSubsystem</code> runs on the CDO before an instance exists, which means it runs on the object that holds the default values stored in the Blueprint asset. That is also why the flag is marked <code>EditDefaultsOnly</code>. The value is read from the defaults and nowhere else, so per-instance editing changes nothing.</p>
<div class="callout callout--note">
  <div class="callout__title">Note</div>
  The result of <code>Super::ShouldCreateSubsystem(Outer)</code> has to feed <strong>into the condition</strong>. The base implementation returns <code>true</code> (<code>Subsystem.h:61</code>), so calling it and discarding the result looks harmless. In classes derived from <code>UWorldSubsystem</code> the override checks <code>DoesSupportWorldType</code>, and skipping it allows the subsystem to be created in editor worlds.
</div>

<h2 id="getsubsystemt-finds-the-child-anyway"><code>GetSubsystem&lt;T&gt;()</code> finds the child anyway</h2>
<p>Instances live in a <code>TMap&lt;UClass*, USubsystem*&gt;</code> keyed by the <strong>concrete</strong> class. The natural conclusion is that querying for the base class will not find an instance of a derived one, and that conclusion is wrong. <code>GetSubsystemInternal</code> has a fallback (<code>SubsystemCollection.cpp:66</code>):</p>
<div class="highlight" data-lang="cpp">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">USubsystem</span><span class="o">*</span> <span class="n">SystemPtr</span> <span class="o">=</span> <span class="n">SubsystemMap</span><span class="p">.</span><span class="n">FindRef</span><span class="p">(</span><span class="n">SubsystemClass</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">SystemPtr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">SystemPtr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="n">FSubsystemArray</span><span class="o">&amp;</span> <span class="n">SystemPtrs</span> <span class="o">=</span> <span class="n">FindAndPopulateSubsystemArrayInternal</span><span class="p">(</span><span class="n">SubsystemClass</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">SystemPtrs</span><span class="p">.</span><span class="n">Subsystems</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></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">SystemPtrs</span><span class="p">.</span><span class="n">Subsystems</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>A miss in the map triggers an <code>IsChildOf</code> sweep and returns the first hit. So <code>GetSubsystem&lt;UMySubsystem&gt;()</code> with the base class as the parameter returns the Blueprint child instance, even though nothing is stored under the base class key.</p>
<p>Without that fallback the switch would make no sense. Every piece of C++ that queries the subsystem by its own base class would stop seeing it the moment the implementation moved into Blueprint.</p>
<div class="callout callout--insight">
  <div class="callout__title">Insight</div>
  The fallback returns <code>Subsystems[0]</code>, the first hit in the array. With two instances alive (a parent that never overrode the method, plus a child), the order in that array decides the result. That is one more reason for the hierarchy to have exactly one winner.
</div>

<p>Listing every instance at once takes a separate accessor. In 5.7 it is called <code>GetSubsystemArrayCopy</code> and returns by value, both on the collection itself (<code>Public/Subsystems/SubsystemCollection.h:139</code>) and on the <code>UGameInstance</code> wrapper (<code>Classes/Engine/GameInstance.h:463</code>).</p>
<h2 id="the-flagless-variant-yielding-to-subclasses">The flagless variant: yielding to subclasses</h2>
<p>The flag has one unpleasant property. When the Blueprint child is gone, whether it was deleted, moved, or simply never loaded in time, <code>false</code> on the C++ side means <strong>nothing</strong> is created. The subsystem disappears entirely, including the part that was written in C++ and had nothing to do with Blueprint.</p>
<p>The condition can be phrased differently: let the class step aside, provided there is someone to step aside for.</p>
<div class="highlight" data-lang="cpp">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">UMySubsystem</span><span class="o">::</span><span class="n">ShouldCreateSubsystem</span><span class="p">(</span><span class="n">UObject</span><span class="o">*</span> <span class="n">Outer</span><span class="p">)</span> <span class="k">const</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">Super</span><span class="o">::</span><span class="n">ShouldCreateSubsystem</span><span class="p">(</span><span class="n">Outer</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">/// @Note: The collection instantiates every non-abstract subclass, so a Blueprint child
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">///        would live alongside this one. Yield so that only the most-derived class is created.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">TArray</span><span class="o">&lt;</span><span class="n">UClass</span><span class="o">*&gt;</span> <span class="n">DerivedClasses</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">GetDerivedClasses</span><span class="p">(</span><span class="n">GetClass</span><span class="p">(),</span> <span class="n">DerivedClasses</span><span class="p">,</span> <span class="cm">/*bRecursive*/</span> <span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">DerivedClasses</span><span class="p">.</span><span class="n">Num</span><span class="p">()</span> <span class="o">==</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>In the normal case the outcome is the same, since a child exists and the parent steps aside. The failure looks different though: a missing child leaves a working C++ instance instead of an empty slot. There is also no flag to forget to set in a new Blueprint&rsquo;s defaults.</p>
<p>I use this variant in a production subsystem, and I have tested it in PIE. With the Blueprint child present, exactly one instance is alive; with the child removed, the C++ instance is. I have not tested it in a packaged build, so that behavior remains an inference from the mechanism rather than an observed result.</p>
<p>There are two limits, and both follow directly from this implementation.</p>
<p><strong>Two children mean two instances.</strong> Each of them sees zero descendants and each concludes that it is the most-derived one. This case cannot be resolved inside the method, which leaves a convention: exactly one Blueprint child per class.</p>
<p><strong>A single permanently loaded C++ subclass disables the base class for good.</strong> A test class in an Editor module is enough. It is compiled, so it sits in memory from startup, so the parent yields to it in every editor session. In practice this means a test module must not derive from such a subsystem, and the test double has to be built on a plain <code>UObject</code> acting as a listener. This trap is worse than two Blueprint children, because nothing about it is visible in the assets. It lives in test code that nobody associates with runtime.</p>
<h2 id="common-mistakes">Common mistakes</h2>
<div class="table-wrap">
  <table>
    <thead>
      <tr>
        <th>Mistake</th>
        <th>Effect</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><code>Super::ShouldCreateSubsystem(Outer);</code> on its own line, result discarded</td>
        <td>The parent condition does nothing; in <code>UWorldSubsystem</code> the subsystem enters editor worlds</td>
      </tr>
      <tr>
        <td><code>EditAnywhere</code> instead of <code>EditDefaultsOnly</code> on the flag</td>
        <td>Implies per-instance editing, while the value is read from the CDO and nowhere else</td>
      </tr>
      <tr>
        <td>A Blueprint child with no overridden condition on the C++ side</td>
        <td>Two live instances, split state, and <code>GetSubsystem&lt;T&gt;()</code> returning the first hit in the array</td>
      </tr>
      <tr>
        <td>The flag set to <code>false</code> and a child that never loads in time</td>
        <td>Zero instances — <a href="/en/topics/unreal-engine/gameplay-framework/blueprint-subsystem-missing-in-packaged-build">a separate case, covered here</a></td>
      </tr>
    </tbody>
  </table>
</div>
<p>The last of those cannot be reproduced in the editor. It only surfaces in a packaged build.</p>
<h2 id="verification">Verification</h2>
<p>The number and classes of live instances, in one console command:</p>
<div class="highlight" data-lang="text">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">obj list class=MySubsystem</span></span></code></pre></div></div>
<p>A query for the base class covers derived classes, so it shows the parent and the children at once, broken down by class. Two rows instead of one reveal the duplication described in the table above; a row for a <code>_C</code> class means the switch worked and the Blueprint implementation is the one running.</p>
<p>Each CDO decision is in the log once the category’s log level is raised:</p>
<div class="highlight" data-lang="text">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">-LogCmds=&#34;LogSubsystemCollection VeryVerbose&#34;</span></span></code></pre></div></div>
<div class="highlight" data-lang="log">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><pre tabindex="0" class="ue-log"><code><span class="ue-log__line ue-log__line--verbose"><span class="ue-log__cat">LogSubsystemCollection</span>: <span class="ue-log__sev ue-log__sev--veryverbose">VeryVerbose</span>: <span class="ue-log__msg">Subsystem does not exist, but CDO choose to not create (MySubsystem)</span>
</span></code></pre>
</div>
<p>That line means the class was on the list and refused. Its <strong>absence</strong>, together with a missing instance, means something entirely different: the class was not on the list, so nobody asked it. Telling those two states apart is the only way to distinguish a working switch from a class that never loaded. At the default log level both of them look like silence, which is why the category’s log level has to be raised first.</p>
]]></content:encoded>
      
    </item>
    
    <item>
      <title>A Blueprint subsystem does not get created in a packaged build</title>
      <link>https://stillcooking.dev/en/topics/unreal-engine/gameplay-framework/blueprint-subsystem-missing-in-packaged-build/</link>
      <pubDate>Tue, 11 Aug 2026 00:00:00 +0000</pubDate>
      
      <guid isPermaLink="true">https://stillcooking.dev/en/topics/unreal-engine/gameplay-framework/blueprint-subsystem-missing-in-packaged-build/</guid>
      <description>The subsystem collection scans for subclasses once, when it is initialized, and sees only classes already in memory. In a packaged build, a Blueprint class that has not been loaded by then is missed entirely: its CDO is never asked whether to create an instance. The Blueprint logic never runs, and the missing class produces no error or log entry. In the editor, the Content Browser can keep the class loaded and hide the problem.</description>
      <content:encoded><![CDATA[<p>A Blueprint child of a subsystem can go an entire session in a packaged build without ever being created. Nothing is wrong with the code. It is the same code that worked in PIE a minute earlier. The problem lies earlier in the sequence: by the time the engine looks for subclasses, the Blueprint class is not in memory, so it never makes the list and its CDO is never queried.</p>
<h2 id="the-symptom-nothing-to-look-for">The symptom: nothing to look for</h2>
<p>No compile errors. No warnings. No lines in the log. <code>GetSubsystem&lt;T&gt;()</code> does not return <code>nullptr</code> either, because as long as the parent C++ class does not refuse to be created, an instance is created normally. What gets created is the parent. The Blueprint implementation that was supposed to replace it never runs, and the pointer looks perfectly valid.</p>
<p>There is a harsher variant, and it happens when the parent refuses on purpose. That is the case in <a href="/en/topics/unreal-engine/gameplay-framework/should-create-subsystem-picks-the-class">the pattern where the C++ class returns <code>false</code> to make room for a Blueprint implementation</a>. Then <strong>nothing</strong> is created at all: the parent refused, and nobody asked the child.</p>
<h2 id="two-runs-that-settle-it">Two runs that settle it</h2>
<p>The test project uses UE 5.7. It contains <code>UMyTestSubsystem : UGameInstanceSubsystem</code>, marked <code>UCLASS(Blueprintable)</code>, which logs a single line in <code>Initialize</code>. It also contains a Blueprint child, <code>BP_MyTestSubsystem</code>, with a <code>PrintString</code> in its graph. The build is packaged in the Development configuration.</p>
<p>First run, with nothing in the project referencing the Blueprint class:</p>
<div class="highlight" data-lang="log">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><pre tabindex="0" class="ue-log"><code><span class="ue-log__line ue-log__line--verbose"><span class="ue-log__cat">LogSubsystemCollection</span>: <span class="ue-log__sev ue-log__sev--verbose">Verbose</span>: <span class="ue-log__msg">Initializing subsystem collection for BP_GameInstance_Test_C_2147482547 with type GameInstanceSubsystem</span>
</span><span class="ue-log__line ue-log__line--verbose"><span class="ue-log__cat">LogSubsystemCollection</span>: <span class="ue-log__sev ue-log__sev--veryverbose">VeryVerbose</span>: <span class="ue-log__msg">Subsystem does not exist, but CDO choose to not create (MyTestSubsystem)</span>
</span></code></pre>
</div>
<p>The parent made it onto the list and refused. The child is not on that list at all. There is no second <code>choose to not create</code> line, and no line from <code>Initialize</code> either, which the child inherits from C++ and would have run had it been created. Counting objects confirms this independently:</p>
<div class="highlight" data-lang="log">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><pre tabindex="0" class="ue-log"><code><span class="ue-log__line">Obj List: class=MyTestSubsystem
</span><span class="ue-log__line">Objects:
</span><span class="ue-log__line">
</span><span class="ue-log__line">0 Objects (Total: 0.000M / Max: 0.000M / Res: 0.000M | ...)
</span></code></pre>
</div>
<p>Second run, after adding a single variable of type <code>MyTestSubsystem Class Reference</code> to the GameInstance class, with its default value pointing at the Blueprint child. The variable is never used anywhere. It exists for one reason: so that the asset holds a reference.</p>
<div class="highlight" data-lang="log">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><pre tabindex="0" class="ue-log"><code><span class="ue-log__line">                    Class    Count
</span><span class="ue-log__line">     BP_MyTestSubsystem_C        1
</span><span class="ue-log__line">
</span><span class="ue-log__line">1 Objects
</span></code></pre>
</div>
<div class="highlight" data-lang="log">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><pre tabindex="0" class="ue-log"><code><span class="ue-log__line"><span class="ue-log__cat">LogTemp</span>: <span class="ue-log__msg">UMyTestSubsystem initialized.</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[None] PostInitialize(): Test Subsystem (BP_MyTestSubsystem)</span>
</span></code></pre>
</div>
<p>The instance exists, it is of the Blueprint class, and both the inherited C++ <code>Initialize</code> method and the Blueprint graph ran. The only difference between the two runs is an unused variable.</p>
<h2 id="where-it-disappears-the-scan-happens-once">Where it disappears: the scan happens once</h2>
<p><code>FSubsystemCollectionBase::Initialize</code> builds the class list in a single call, at the moment the collection is created (<code>Engine/Private/Subsystems/SubsystemCollection.cpp:209</code>):</p>
<div class="highlight" data-lang="cpp">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">TArray</span><span class="o">&lt;</span><span class="n">UClass</span><span class="o">*&gt;</span> <span class="n">SubsystemClasses</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">GetDerivedClasses</span><span class="p">(</span><span class="n">BaseType</span><span class="p">,</span> <span class="n">SubsystemClasses</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="n">UClass</span><span class="o">*</span> <span class="nl">SubsystemClass</span> <span class="p">:</span> <span class="n">SubsystemClasses</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">AddAndInitializeSubsystem</span><span class="p">(</span><span class="n">SubsystemClass</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><code>GetDerivedClasses</code> sweeps the class hierarchy <strong>in memory</strong>. A class that nobody has loaded exists only as a file on disk: the asset registry knows about it, the type system does not. Since it never lands in <code>SubsystemClasses</code>, <code>AddAndInitializeSubsystem</code> never receives it, and <code>ShouldCreateSubsystem</code> has nothing to run on. A class missing from the scan produces no CDO decision entry. A class that is found but refuses creation produces the <code>CDO choose to not create</code> line at <code>VeryVerbose</code> verbosity.</p>
<p>That loop is in fact the <code>else</code> branch. The first branch handles a completely different case (<code>SubsystemCollection.cpp:194</code>):</p>
<div class="highlight" data-lang="cpp">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">BaseType</span><span class="o">-&gt;</span><span class="n">IsChildOf</span><span class="p">(</span><span class="n">UDynamicSubsystem</span><span class="o">::</span><span class="n">StaticClass</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">TPair</span><span class="o">&lt;</span><span class="n">FName</span><span class="p">,</span> <span class="n">TArray</span><span class="o">&lt;</span><span class="n">TSubclassOf</span><span class="o">&lt;</span><span class="n">UDynamicSubsystem</span><span class="o">&gt;&gt;&gt;&amp;</span> <span class="nl">SubsystemClasses</span> <span class="p">:</span> <span class="n">GlobalDynamicSystemModuleMap</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// GetDerivedClasses, once
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div></div>
<p><code>UDynamicSubsystem</code> gets incremental registration. <code>FSubsystemModuleWatcher</code> listens for module loads and calls <code>AddAllInstances</code> for classes that have just appeared (<code>SubsystemCollection.cpp:524</code>). <code>UGameInstanceSubsystem</code> does not belong to that branch. For it, the scan runs once and nothing repeats it, so a class loaded one second after the collection is initialized is too late for that collection.</p>
<div class="callout callout--warning">
  <div class="callout__title">Warning</div>
  Timing is the only criterion here. One thing matters: whether the class is in memory <strong>before</strong> the subsystem collection is created. Having the asset in the pak does nothing on its own. An asset that is packaged but referenced by nothing is, as far as this scan is concerned, absent.
</div>

<h2 id="why-the-editor-can-hide-this">Why the editor can hide this</h2>
<p>In the editor, the Content Browser keeps the Blueprint class in memory. Opening the asset once is enough, and so is having it sit in a visible folder. By the time PIE starts, the class is already loaded, <code>GetDerivedClasses</code> sees it, and everything behaves exactly as designed.</p>
<p>In a real project the packaged build usually works too, but for a different reason: the reference exists by accident. A <code>Get Subsystem</code> node with the Blueprint class selected embeds a hard reference in the Blueprint that calls it, and that Blueprint ships with the level or with the GameInstance class. A reference from a Blueprint hides the problem if that Blueprint loads the subsystem class <strong>before</strong> the collection is initialized.</p>
<p>That is the whole trap. The mechanism breaks in exactly the place nobody tests it: when the subsystem is called from C++ and nowhere else.</p>
<h2 id="the-fix">The fix</h2>
<p>Anything that loads the class before the collection is initialized. The cheapest option, and the one that does not disappear when somebody rewires a graph, is a variable of type <code>&lt;Subsystem&gt; Class Reference</code> on an object that loads early, with its default value set to the Blueprint class. The GameInstance class is the natural place for the variable, because the GameInstance itself is created before its subsystem collection.</p>
<p>The variable does not have to be used for anything. What matters is that the GameInstance asset holds a hard reference to the Blueprint class, so loading one pulls in the other.</p>
<div class="callout callout--tip">
  <div class="callout__title">Tip</div>
  Leave a comment on the variable where it is declared. An unused <code>Class Reference</code> variable looks like a leftover from a refactor and is the first thing anyone deletes during cleanup, and deleting it breaks nothing in the editor.
</div>

<p>Getting the class loaded in time is the only real fix. How badly the failure hurts when nothing loads it is a separate question, and the answer depends on how the C++ class steps aside for the Blueprint. Overriding <code>ShouldCreateSubsystem</code> with a condition that checks for subclasses, rather than with a flag set in the Blueprint defaults, turns “zero instances” into “a C++ instance without the Blueprint logic”: a reduced system rather than a missing one. I wrote up the mechanism itself, and its limits, in <a href="/en/topics/unreal-engine/gameplay-framework/should-create-subsystem-picks-the-class">ShouldCreateSubsystem and the subsystem class hierarchy</a>.</p>
<h2 id="checking-this-in-your-own-project">Checking this in your own project</h2>
<div class="section-panel section-panel--checklist">
  <div class="section-panel__label">Checklist</div>
  
<p>What to check when a subsystem works in PIE and its behavior in a packaged build is unknown:</p>
<ul class="checklist">
  <li>Package in the <strong>Development</strong> configuration rather than Shipping. A Shipping build compiles <code>Verbose</code> and <code>VeryVerbose</code> out, so raising the log level cannot bring them back.</li>
  <li>Run it with <code>&lt;Game&gt;.exe -log "-ExecCmds=obj list class=&lt;Subsystem&gt;"</code>. A query for the base class covers derived classes too, which is visible in the second run above: the answer to a question about the C++ class is an instance of the Blueprint class.</li>
  <li>Check the <strong>class</strong>, not only the object count. <code>1 Objects</code> of the parent class while a child exists is the same failure, only quieter.</li>
  <li>For each CDO decision separately, use <code>-LogCmds="LogSubsystemCollection VeryVerbose"</code>. A <code>CDO choose to not create</code> line means “asked and refused.” Its absence, alongside a missing instance, means “never asked.”</li>
</ul>

</div>

<p>Check two things separately: whether the instance exists, and whether the log contains a CDO decision for that class. A missing instance together with a missing CDO decision points to loading as the source of the problem. Neither observation is enough on its own.</p>
<h2 id="when-this-matters">When this matters</h2>
<p>As long as a Blueprint reference gets the class loaded in time, the trap stays asleep. The reference appears on its own and nobody finds out it was ever needed. I reproduced it in two setups: one where the subsystem serves C++ code only, and one where a Blueprint child was added to override a few default values and nothing called it afterwards.</p>
<p>A third setup follows from the mechanism, but I did not reproduce it. Removing the last <code>Get Subsystem</code> call from Blueprints during a refactor can remove the only reference to the class. If that happens, this is the worst of the three cases, because nothing obviously connects the regression to the commit that triggered it. A node disappears from one Blueprint, the subsystem stops existing in the packaged game, and everything still works in the editor.</p>
]]></content:encoded>
      
    </item>
    
    <item>
      <title>Subsystem and manager actor lifecycles — who, when, and in what order</title>
      <link>https://stillcooking.dev/en/topics/unreal-engine/gameplay-framework/subsystem-lifecycle-init-order/</link>
      <pubDate>Tue, 21 Jul 2026 00:00:00 +0000</pubDate>
      
      <guid isPermaLink="true">https://stillcooking.dev/en/topics/unreal-engine/gameplay-framework/subsystem-lifecycle-init-order/</guid>
      <description>Five subsystem base classes, each with a different owner. Where the engine creates and tears down each collection, how world startup relates to actor BeginPlay, and where initialization order is no longer guaranteed.</description>
      <content:encoded><![CDATA[<h2 id="five-collections-five-owners">Five collections, five owners</h2>
<p>A subsystem derives from one of five base classes. Each subsystem type is associated with a different owner and inherits that owner’s lifetime: <code>UEngineSubsystem</code>, <code>UEditorSubsystem</code>, <code>UGameInstanceSubsystem</code>, <code>UWorldSubsystem</code> (plus <code>UTickableWorldSubsystem</code>), or <code>ULocalPlayerSubsystem</code> (<code>Runtime/Engine/Public/Subsystems/Subsystem.h:12-20</code>). Everything below comes from reading the UE 5.8 source.</p>
<p>Choosing the base class is how you declare the subsystem&rsquo;s lifetime. Everything else happens automatically.</p>
<p>All five follow the same contract: <code>ShouldCreateSubsystem(UObject* Outer)</code> → <code>Initialize</code> → <code>Deinitialize</code>. The first call is the exception. It runs on the CDO before an instance even exists, as the header states explicitly: <em>&ldquo;Note: This function is called on the CDO prior to instances being created!&rdquo;</em> (<code>Subsystem.h:49-56</code>).</p>
<p>Under the hood, the collection gathers every non-abstract derived class through <code>GetDerivedClasses(BaseType, …, true)</code> and asks each CDO whether that subsystem should be created (<code>Private/Subsystems/SubsystemCollection.cpp:256,373-391</code>). It stores the resulting instances in a <code>TMap&lt;UClass*, USubsystem*&gt;</code> (<code>Public/Subsystems/SubsystemCollection.h:116-143</code>). One instance per class per Outer isn’t a convention; it follows from the container type. The key is a concrete class, not a hierarchy. If another non-abstract class derives from that base class — a Blueprint child, for instance — its CDO gets asked the same question separately, and the collection ends up holding two independent instances. How this affects which class provides the implementation is covered separately in <a href="/en/topics/unreal-engine/gameplay-framework/should-create-subsystem-picks-the-class">ShouldCreateSubsystem and the subsystem class hierarchy</a>.</p>
<h2 id="where-each-collection-starts">Where each collection starts</h2>
<div class="table-wrap">
  <table>
    <thead>
      <tr>
        <th>Collection</th>
        <th>Created</th>
        <th>Torn down</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Engine (dynamic)</td>
        <td>collection: <code>UEngine::Init</code> — <code>Private/UnrealEngine.cpp:2403</code>; instances: module load — <code>Subsystem.h:72-83</code></td>
        <td><code>UEngine::PreExit</code> — <code>:2757</code>; instances: module unload</td>
      </tr>
      <tr>
        <td>Editor (dynamic)</td>
        <td>instances: module load — <code>Editor/EditorSubsystem/Public/EditorSubsystem.h:9-23</code></td>
        <td>module unload</td>
      </tr>
      <tr>
        <td>GameInstance</td>
        <td><code>UGameInstance::Init</code> — <code>Private/GameInstance.cpp:129</code></td>
        <td><code>UGameInstance::Shutdown</code> — <code>:167</code></td>
      </tr>
      <tr>
        <td>World</td>
        <td><code>UWorld::InitWorld</code> — <code>Private/World.cpp:2414</code></td>
        <td><code>UWorld::CleanupWorldInternal</code> — <code>:6476,6486</code></td>
      </tr>
      <tr>
        <td>LocalPlayer</td>
        <td><code>ULocalPlayer::PlayerAdded</code> — <code>Private/LocalPlayer.cpp:262,270</code></td>
        <td><code>ULocalPlayer::PlayerRemoved</code> — <code>:280</code></td>
      </tr>
    </tbody>
  </table>
</div>
<p>The first two rows are the easiest to get wrong. <code>UEditorSubsystem</code> and <code>UEngineSubsystem</code> derive from <code>UDynamicSubsystem</code>, which automatically populates the collection when a module loads and empties it when that module unloads. Your subsystem won’t exist until its module is explicitly loaded, and the engine won’t report an error if it isn’t.</p>
<div class="callout callout--warning">
  <div class="callout__title">Warning</div>
  The trap in the LocalPlayer row is hidden in the owner’s name. The collection doesn’t start when the <code>ULocalPlayer</code> object is created. It starts in <code>PlayerAdded</code>, after the player has been attached to a <code>UGameViewportClient</code>. Both overloads of that method call <code>SubsystemCollection.Initialize(this)</code> (<code>Private/LocalPlayer.cpp:257-271</code>), while <code>PlayerRemoved</code> deinitializes the collection (<code>:278-280</code>).
</div>

<div class="callout callout--insight">
  <div class="callout__title">Insight</div>
  In PIE, every client instance gets its own <code>UGameInstance</code>. This means the GameInstance, World, and LocalPlayer subsystems are created and destroyed with the PIE session, while Engine and Editor subsystems survive across many sessions in the same editor process. That difference is the most common source of state leaking between PIE runs.
</div>

<h2 id="the-world-startup-timeline">The world startup timeline</h2>
<p>By the time this timeline begins, the Engine and GameInstance collections have already been initialized. <code>UEngine::Init</code> runs during process startup, and <code>UGameInstance::Init</code> runs before the map load that creates the gameplay world.</p>
<div class="callout callout--note">
  <div class="callout__title">Note</div>
  One exception breaks this intuition: <code>UGameInstance::InitializeStandalone</code> creates a dummy world before calling its own <code>Init()</code> (<code>Private/GameInstance.cpp:190-202</code>). The World subsystems for that dummy world are therefore initialized before the GameInstance subsystems. The dummy world is destroyed during the first <code>LoadMap</code>.
</div>

<p>The relevant points in the timeline are all in <code>Runtime/Engine/Private/World.cpp</code>:</p>
<ol>
<li><strong><code>UWorld::InitWorld()</code></strong> calls <code>InitializeSubsystems</code> (<code>:2447</code>) and, near the end, <code>PostInitializeSubsystems</code> (<code>:2610</code>). World subsystems receive both <code>Initialize</code> and <code>PostInitialize</code>.</li>
<li><strong><code>UWorld::InitializeActorsForPlay()</code></strong> runs (<code>:5946</code>). Only then does the engine start handling actors placed in the level.</li>
<li><strong><code>UWorld::BeginPlay()</code></strong> calls <code>OnWorldBeginPlay</code> on every World subsystem and <strong>then</strong> calls <code>AGameModeBase::StartPlay()</code> (<code>:6165-6179</code>).</li>
</ol>
<p>The third point gives us a guarantee that no actor can provide: a World subsystem has been initialized and has already run <code>OnWorldBeginPlay</code> before GameMode starts gameplay. An actor placed in the level receives its <code>BeginPlay</code> during <code>InitializeActorsForPlay</code>/<code>StartPlay</code>, which puts it after the subsystems. That last part is my interpretation of the call order, not a guarantee stated on any single line of engine code, but it follows directly from that order.</p>
<p>An actor spawned during play is a different case. Its <code>BeginPlay</code> fires immediately after it is spawned, so the startup-order question doesn’t apply.</p>
<p>A manager actor comes close to providing the same guarantee, but only when three conditions are met: it derives from <code>AInfo</code> (or explicitly sets <code>bIsSpatiallyLoaded = false</code>), lives in the persistent level, and has no dependency on a streamed sublevel. The <code>AInfo</code> constructor sets <code>bIsSpatiallyLoaded = false</code> and <code>bReplicates = false</code> (<code>Private/Info.cpp:11-45</code>), which is exactly the kind of role Epic designed this class for.</p>
<p>A plain <code>AActor</code> placed in a World Partition level is spatially streamed by default and can be unloaded during gameplay. A subsystem is structurally immune to this class of bug because it doesn’t belong to any <code>ULevel</code>.</p>
<h2 id="where-the-ordering-guarantee-stops">Where the ordering guarantee stops</h2>
<p>The guarantee from the previous section applies only to this ordering. Everything below falls outside it, and that is where the assumption that “the subsystem is always there” breaks down.</p>
<p><strong>There is no declarative ordering between collections.</strong> <code>FSubsystemCollectionBase::InitializeDependency</code> enforces ordering within a single collection and nowhere else. The header states this plainly: <em>&ldquo;Dependencies only work within a collection&rdquo;</em> (<code>Public/Subsystems/SubsystemCollection.h:31-45</code>). A World subsystem that accesses a GameInstance subsystem from inside <code>Initialize</code> has no declarative safeguard. You must either enforce the order yourself or defer that access until <code>OnWorldBeginPlay</code>, when the world has already been assembled.</p>
<p><strong>A dynamic subsystem whose module hasn’t loaded never appears.</strong> A <code>UEditorSubsystem</code> placed in a module with the wrong <code>LoadingPhase</code> simply never reaches the collection (<code>Subsystem.h:72-83</code>, <code>EditorSubsystem.h:9-23</code>). The symptom is misleading: <code>GetEditorSubsystem&lt;T&gt;()</code> returns null even though the class compiles, loads, and looks perfectly fine in the editor.</p>
<p><strong>A class that hasn&rsquo;t been loaded never even makes the list.</strong> This is the same mechanism as the point above, seen from the other side. Non-dynamic collections run their <code>GetDerivedClasses</code> scan once, when the collection is created, and they only see the classes that are in memory at that moment. A Blueprint child of a subsystem with no hard references to that child isn&rsquo;t loaded yet in a packaged build, so its CDO is never asked — and at the default log level a refusal and an absence look identical: both are just a missing line. The editor never shows the problem, because the Content Browser keeps the class in memory. I covered the whole case in <a href="/en/topics/unreal-engine/gameplay-framework/blueprint-subsystem-missing-in-packaged-build">A Blueprint subsystem does not get created in a packaged build</a>.</p>
<p><strong>A conditional <code>ShouldCreateSubsystem</code> eliminates the non-null guarantee.</strong> Epic does this in its own code: <code>UInputDeviceSubsystem</code> returns <code>false</code> on a dedicated server, in a commandlet, and when Slate hasn’t been initialized (<code>Private/GameFramework/InputDeviceSubsystem.cpp:240-252</code>). The consequence is visible in the subsystem’s own accessor. <code>UInputDeviceSubsystem::Get()</code> returns <code>nullptr</code> (<code>:194-197</code>), so every call within the engine is wrapped in an <code>if</code>, three times in <code>ForceFeedbackEffect.cpp</code> alone (<code>:122</code>, <code>:190</code>, <code>:214</code>).</p>
<p>Overriding this hook gives up the very property that often makes a subsystem appealing in the first place. From that point on, every <code>GetSubsystem&lt;T&gt;()</code> requires a null check, and the call site has no way to know whether it is running on a path where the subsystem was created. Sometimes that tradeoff is intentional. The condition in this method doubles as a way of declaring which class in the hierarchy should be the subsystem, and the null check becomes the price of moving the implementation one level down, <a href="/en/topics/unreal-engine/gameplay-framework/should-create-subsystem-picks-the-class">into a Blueprint</a>.</p>
<p><strong><code>DoesSupportWorldType</code> includes editor worlds by default.</strong> A gameplay manager that doesn’t override this method gets an instance in every world opened in the editor, not just in PIE. This happens because <code>UWorldSubsystem</code> overrides <code>ShouldCreateSubsystem</code> through <code>DoesSupportWorldType</code>, whose default implementation allows game, PIE, <strong>and</strong> editor worlds (<code>Public/Subsystems/WorldSubsystem.h:33-66</code>, especially <code>:64-66</code>). If the subsystem also derives from <code>UTickableWorldSubsystem</code>, it ticks from <code>Initialize</code> to <code>Deinitialize</code> for as long as the map remains open in the editor (<code>WorldSubsystem.h:72-106</code>).</p>
<h2 id="confirming-this-in-your-own-project">Confirming this in your own project</h2>
<p>You can verify the entire timeline above in a single editor run. Log four points and include the world type on every line:</p>
<div class="highlight" data-lang="cpp">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">UMyWorldSubsystem</span><span class="o">::</span><span class="n">Initialize</span><span class="p">(</span><span class="n">FSubsystemCollectionBase</span><span class="o">&amp;</span> <span class="n">Collection</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Super</span><span class="o">::</span><span class="n">Initialize</span><span class="p">(</span><span class="n">Collection</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">UE_LOG</span><span class="p">(</span><span class="n">LogMyGame</span><span class="p">,</span> <span class="n">Log</span><span class="p">,</span> <span class="n">TEXT</span><span class="p">(</span><span class="s">&#34;[1] Subsystem::Initialize | World=%s Type=%d&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="o">*</span><span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetName</span><span class="p">(),</span> <span class="p">(</span><span class="n">int32</span><span class="p">)</span><span class="n">GetWorld</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">WorldType</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">UMyWorldSubsystem</span><span class="o">::</span><span class="n">OnWorldBeginPlay</span><span class="p">(</span><span class="n">UWorld</span><span class="o">&amp;</span> <span class="n">InWorld</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Super</span><span class="o">::</span><span class="n">OnWorldBeginPlay</span><span class="p">(</span><span class="n">InWorld</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">UE_LOG</span><span class="p">(</span><span class="n">LogMyGame</span><span class="p">,</span> <span class="n">Log</span><span class="p">,</span> <span class="n">TEXT</span><span class="p">(</span><span class="s">&#34;[2] Subsystem::OnWorldBeginPlay&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// AMyManagerActor::BeginPlay  → [3]
</span></span></span><span class="line"><span class="cl"><span class="c1">// AMyGameMode::StartPlay      → [4]
</span></span></span></code></pre></div></div>
<p>Seeing <code>1 → 2 → 3 → 4</code> in the log confirms the guarantee for one specific project and one specific streaming setup. If <code>[3]</code> appears before <code>[2]</code>, the actor is being spawned rather than placed in the level. Its <code>BeginPlay</code> fires immediately after the spawn and has nothing to do with the world startup timeline.</p>
<div class="section-panel section-panel--checklist">
  <div class="section-panel__label">Checklist</div>
  
<p>Additional checks, each answering a different question:</p>
<ul class="checklist">
  <li><strong>Who created me, and from where?</strong> — set a breakpoint on <code>FSubsystemCollectionBase::AddAndInitializeSubsystem</code>. The call stack shows both the collection and the point in the engine that created it (<code>UEngine::Init</code>, <code>UGameInstance::Init</code>, <code>UWorld::InitWorld</code>, or a module load).</li>
  <li><strong>Am I cluttering up the editor?</strong> — if line <code>[1]</code> appears when you simply open a map without starting PIE, then <code>DoesSupportWorldType</code> hasn’t been overridden. Filtering for <code>WorldType == EWorldType::Game || WorldType == EWorldType::PIE</code> fixes it.</li>
  <li><strong>Does my manager actor really survive?</strong> — check the actor’s <code>bIsSpatiallyLoaded</code> value in the Details panel under World Partition, and confirm that it belongs to the persistent level. Both conditions must be satisfied.</li>
  <li><strong>Was my class asked at all?</strong> — run with <code>-LogCmds="LogSubsystemCollection VeryVerbose"</code>. A <code>CDO choose to not create</code> line means the class was on the list and refused; the absence of that line alongside a missing instance means nobody ever asked it. In that second case the breakpoint from the first check never fires, so on its own it settles nothing.</li>
</ul>

</div>

<p>The ordering guarantee is real, and the engine code enforces it, but it covers exactly one thing: a World subsystem exists before actors in that same world receive <code>BeginPlay</code>. It extends no further than that single axis. Everything above also covers startup only; the order in which a world and a session shut down is a separate question, and this note leaves it open.</p>
<p>The game code has to answer every other question for itself, and the engine won’t even signal that the question came up.</p>
]]></content:encoded>
      
    </item>
    
  </channel>
</rss>
