<?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>cdo on stillcooking.dev</title>
    <link>https://stillcooking.dev/en/tags/cdo/</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/cdo/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>
    
  </channel>
</rss>
