<?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>delegate on stillcooking.dev</title>
    <link>https://stillcooking.dev/en/tags/delegate/</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>Tue, 28 Jul 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://stillcooking.dev/en/tags/delegate/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>A dispatcher broadcast from inside a handler does not see later bindings</title>
      <link>https://stillcooking.dev/en/topics/unreal-engine/gameplay-framework/event-dispatcher-bind-order/</link>
      <pubDate>Tue, 28 Jul 2026 00:00:00 +0000</pubDate>
      
      <guid isPermaLink="true">https://stillcooking.dev/en/topics/unreal-engine/gameplay-framework/event-dispatcher-bind-order/</guid>
      <description>A Blueprint Event Dispatcher calls its bindings in registration order unless removals have reordered the list: ProcessMulticastDelegate iterates a copy from front to back. When one handler broadcasts another dispatcher, only bindings already registered with that dispatcher are included. Bindings added by later handlers do not exist yet. In this example, registration order follows from the lifecycle phases in which the Bind Event to&amp;hellip; nodes run across different Blueprints.</description>
      <content:encoded><![CDATA[<p>The dispatcher <code>OnDelegate_1</code> has two bindings. The first handler broadcasts <code>OnDelegate_2</code> while it runs. The second handler binds <code>CustomEvent_3</code> to <code>OnDelegate_2</code>, but by then the broadcast is already over.</p>
<p>The cause is not a race condition. A dispatcher broadcast is a synchronous loop over an array: one thread, one frame, the same result every time. The call order of the bindings is deterministic, and relying on it is legitimate, provided you know where that order comes from and what changes it. It comes from the lifecycle phase in which each <code>Bind Event to…</code> node ran.</p>
<h2 id="the-test-setup">The test setup</h2>
<p>UE 5.7, pure Blueprint, two separate Blueprints. <code>BP_GameInstance_Test</code> owns both dispatchers and <code>CustomEvent_1</code>, which is bound in <code>Init</code>:</p>
<figure class="bp-embed">
  <div class="bp-embed__canvas" data-bp-src="/blueprints/dispatcher-order-a-gameinstance.txt" data-bp-height="400"></div>
  <noscript>
    <p class="bp-embed__fallback">Viewing the graph requires JavaScript.</p>
    <a class="bp-embed__download" href="/blueprints/dispatcher-order-a-gameinstance.txt" download>Download Blueprint graph (.txt)</a>
  </noscript><figcaption class="bp-embed__caption">BP_GameInstance_Test: Init binds CustomEvent_1 to OnDelegate_1. CustomEvent_1 itself logs [1] and then broadcasts OnDelegate_2.</figcaption></figure>
<p>The actor <code>BP_DispatcherTest</code> binds to the same <code>OnDelegate_1</code> in <code>BeginPlay</code>, and its handler is what creates the binding on <code>OnDelegate_2</code>:</p>
<figure class="bp-embed">
  <div class="bp-embed__canvas" data-bp-src="/blueprints/dispatcher-order-a-actor.txt" data-bp-height="900"></div>
  <noscript>
    <p class="bp-embed__fallback">Viewing the graph requires JavaScript.</p>
    <a class="bp-embed__download" href="/blueprints/dispatcher-order-a-actor.txt" download>Download Blueprint graph (.txt)</a>
  </noscript><figcaption class="bp-embed__caption">BP_DispatcherTest: BeginPlay binds CustomEvent_2 to OnDelegate_1. CustomEvent_2 logs [2] and only then binds CustomEvent_3 (log [3]) to OnDelegate_2.</figcaption></figure>
<p>A key press in the Level Blueprint fires the broadcast. After calling <code>OnDelegate_1</code>, the graph logs <code>[END TEST]</code>. That marker marks the end of the synchronous execution triggered by the key press, and it makes the log comparisons further down readable:</p>
<figure class="bp-embed">
  <div class="bp-embed__canvas" data-bp-src="/blueprints/dispatcher-order-a-levelbp.txt" data-bp-height="420"></div>
  <noscript>
    <p class="bp-embed__fallback">Viewing the graph requires JavaScript.</p>
    <a class="bp-embed__download" href="/blueprints/dispatcher-order-a-levelbp.txt" download>Download Blueprint graph (.txt)</a>
  </noscript><figcaption class="bp-embed__caption">LVL_DispatcherTest: the key press broadcasts OnDelegate_1 on the GameInstance, then logs [END TEST].</figcaption></figure>
<p>One key press, and the Output Log shows:</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"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[1]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[2]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[END TEST]</span>
</span></code></pre>
</div>
<p><code>[3]</code> never appears. A breakpoint on <code>CustomEvent_3</code> is never hit. There is no compile error, no red node, and no warning. From the engine’s point of view nothing went wrong: the dispatcher called everything that was registered at the moment of the call, and <code>CustomEvent_3</code> was not registered then.</p>
<h2 id="this-is-not-a-race-condition">This is not a race condition</h2>
<p>The symptom suggests a race condition: the binding and the broadcast are competing, and the broadcast wins. That explanation is false and sends the diagnosis in the wrong direction.</p>
<p>Nothing here is racing anything. When <code>CustomEvent_1</code> broadcasts <code>OnDelegate_2</code>, <code>CustomEvent_2</code> has not started running yet. Not because it was too slow, but because it comes later in program order. Run the same project a hundred times and it behaves identically a hundred times.</p>
<p>The distinction has practical consequences. A race condition is something you look for in timing and try to fix with a delay added just in case. A delay does work here, but not for the reason that hypothesis suggests: it doesn&rsquo;t give another thread time to catch up, it moves the <code>OnDelegate_2</code> broadcast past the end of the entire <code>OnDelegate_1</code> handler list. It is worth knowing which of the two you are buying.</p>
<h2 id="proving-the-order-is-deterministic">Proving the order is deterministic</h2>
<p>The setup above shows where the invisible order comes from, but it mixes two variables at once. One Blueprint is enough to isolate it: all three custom events on <code>BP_GameInstance_Test</code>, both bindings to <code>OnDelegate_1</code> on a single exec wire in <code>Init</code>, no actor and no <code>BeginPlay</code>.</p>
<figure class="bp-embed">
  <div class="bp-embed__canvas" data-bp-src="/blueprints/dispatcher-order-b-first.txt" data-bp-height="1080"></div>
  <noscript>
    <p class="bp-embed__fallback">Viewing the graph requires JavaScript.</p>
    <a class="bp-embed__download" href="/blueprints/dispatcher-order-b-first.txt" download>Download Blueprint graph (.txt)</a>
  </noscript><figcaption class="bp-embed__caption">Arrangement one: in Init, CustomEvent_1 is bound first, then CustomEvent_2.</figcaption></figure>
<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">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[1]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[2]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[END TEST]</span>
</span></code></pre>
</div>
<p>Now the only change in the entire project: the exec wire between the two <code>Bind Event to OnDelegate_1</code> nodes is swapped. The nodes, the events, their contents, and the Level Blueprint stay untouched.</p>
<figure class="bp-embed">
  <div class="bp-embed__canvas" data-bp-src="/blueprints/dispatcher-order-b-swapped.txt" data-bp-height="1080"></div>
  <noscript>
    <p class="bp-embed__fallback">Viewing the graph requires JavaScript.</p>
    <a class="bp-embed__download" href="/blueprints/dispatcher-order-b-swapped.txt" download>Download Blueprint graph (.txt)</a>
  </noscript><figcaption class="bp-embed__caption">Arrangement two: identical contents, the two Bind Event nodes in reverse order.</figcaption></figure>
<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">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[2]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[1]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[3]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[END TEST]</span>
</span></code></pre>
</div>
<p><code>CustomEvent_2</code> ran first, bound <code>CustomEvent_3</code> to <code>OnDelegate_2</code>, and only then did <code>CustomEvent_1</code> broadcast that dispatcher. Reordering two nodes on an exec wire changed the result predictably and repeatably. The result follows directly from the order of execution: in one arrangement, registration happens after the broadcast; in the other, it happens before it.</p>
<div class="callout callout--warning">
  <div class="callout__title">Warning</div>
  When you repeat this test, press the key <strong>once per run</strong> and restart PIE between arrangements. A second press in arrangement one does log <code>[3]</code>, because the binding that <code>CustomEvent_2</code> created during the first broadcast is still there. That looks like instability, but it is leftover state from the previous call.
</div>

<h2 id="where-the-order-comes-from">Where the order comes from</h2>
<p>A Blueprint dispatcher is an <code>FMulticastScriptDelegate</code>, and its broadcast lives in <code>ScriptDelegates.h:917</code> (5.7):</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">InvocationList</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="c1">// Create a copy of the invocation list, just in case the list is modified by one of the callbacks during the broadcast
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">typedef</span> <span class="n">TArray</span><span class="o">&lt;</span> <span class="n">UnicastDelegateType</span><span class="p">,</span> <span class="n">TInlineAllocator</span><span class="o">&lt;</span> <span class="mi">4</span> <span class="o">&gt;</span> <span class="o">&gt;</span> <span class="n">FInlineInvocationList</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">FInlineInvocationList</span> <span class="n">InvocationListCopy</span> <span class="o">=</span> <span class="n">FInlineInvocationList</span><span class="p">(</span><span class="n">InvocationList</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Invoke each bound function
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span><span class="p">(</span> <span class="k">typename</span> <span class="n">FInlineInvocationList</span><span class="o">::</span><span class="n">TConstIterator</span> <span class="n">FunctionIt</span><span class="p">(</span> <span class="n">InvocationListCopy</span> <span class="p">);</span> <span class="n">FunctionIt</span><span class="p">;</span> <span class="o">++</span><span class="n">FunctionIt</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">if</span><span class="p">(</span> <span class="n">FunctionIt</span><span class="o">-&gt;</span><span class="n">IsBound</span><span class="p">()</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">FunctionIt</span><span class="o">-&gt;</span><span class="k">template</span> <span class="n">ProcessDelegate</span><span class="o">&lt;</span><span class="n">UObjectTemplate</span><span class="o">&gt;</span><span class="p">(</span><span class="n">Parameters</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><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><code>TConstIterator</code> runs front to back, and new bindings are appended to the end of the array. Hence the rule: <strong>in a Blueprint dispatcher, first bound is first called.</strong></p>
<p>That leaves the question of why <code>CustomEvent_1</code> runs first. Not the graph: neither of the two Blueprints contains a node that sets the call order. The lifecycle decides. <code>UGameInstance::Init</code> (<code>Private/GameInstance.cpp:129</code>) runs before the map is loaded, and therefore before <code>BeginPlay</code> on any actor in the world. The world startup timeline is laid out separately in <a href="/en/topics/unreal-engine/gameplay-framework/subsystem-lifecycle-init-order">Subsystem and manager actor lifecycles</a>.</p>
<div class="callout callout--insight">
  <div class="callout__title">Insight</div>
  In the original two-Blueprint setup, lifecycle phases determine the handler order: the binding in <code>Init</code> runs before the binding in <code>BeginPlay</code>. To trace the order in your own project, find every <code>Bind Event to…</code> node targeting that dispatcher and establish when each one executes, including the execution order within a single phase. That information may be spread across several graphs.
</div>

<h2 id="same-name-opposite-order">Same name, opposite order</h2>
<p>The C++ equivalent iterates the other way, with a comment that says why (<code>MulticastDelegateBase.h:292-293</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">// call bound functions in reverse order, so we ignore any instances that may be added by callees
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">for</span> <span class="p">(</span><span class="n">int32</span> <span class="n">InvocationListIndex</span> <span class="o">=</span> <span class="n">LocalInvocationList</span><span class="p">.</span><span class="n">Num</span><span class="p">()</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="n">InvocationListIndex</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="o">--</span><span class="n">InvocationListIndex</span><span class="p">)</span></span></span></code></pre></div></div>
<div class="table-wrap">
  <table>
    <thead>
      <tr>
        <th></th>
        <th>Call order</th>
        <th>Binding added during the broadcast</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Blueprint dispatcher (<code>FMulticastScriptDelegate</code>)</td>
        <td>registration order — first bound is called first</td>
        <td>skipped; the list is copied before the loop</td>
      </tr>
      <tr>
        <td>C++ delegate (<code>TMulticastDelegate</code>)</td>
        <td>reverse of registration — last bound is called first</td>
        <td>skipped; the loop runs backwards</td>
      </tr>
    </tbody>
  </table>
</div>
<p>So one name, “multicast delegate,” covers two implementations with opposite iteration order. The order a graph relies on is a property of the specific implementation, not of the concept.</p>
<h2 id="same-symptom-different-cause">Same symptom, different cause</h2>
<p>The comment above the copy, <em>“just in case the list is modified by one of the callbacks during the broadcast,”</em> describes a different case with an identical symptom. The copy means that a binding added to a dispatcher <strong>that is currently broadcasting</strong> will not be called in that broadcast. If <code>CustomEvent_2</code> bound <code>CustomEvent_3</code> to <code>OnDelegate_1</code>, the same dispatcher that is calling it right now, <code>CustomEvent_3</code> would stay silent as well. The cause would be the frozen copy taken before the first handler, not the registration order.</p>
<div class="callout callout--note">
  <div class="callout__title">Note</div>
  Both traps belong to the same family: <strong>the state of the binding list at the moment of the call is the only thing that counts</strong>. Anything bound after that does not exist for that call.
</div>

<h2 id="what-the-order-does-not-guarantee">What the order does not guarantee</h2>
<p>Moving a binding earlier in the lifecycle fixes the symptom, and it is a legitimate move. It is worth knowing what it assumes.</p>
<p>Epic does not promise this order. The multicast class documentation says so directly (<code>DelegateSignatureImpl.inl:1024-1025</code>):</p>
<blockquote>
<p>Multicast delegates offer no guarantees for the calling order of bound functions. As bindings get added and removed over time, the calling order may change.</p>
</blockquote>
<p>Removal from the list does not preserve order. <code>RemoveInternal</code> goes through <code>RemoveAtSwap</code> (<code>ScriptDelegates.h:1069</code>), so the last entry jumps into the freed index. The header warns about this on every removal method (<code>ScriptDelegates.h:731</code>, <code>771</code>, <code>1045</code>, <code>1056</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="o">*</span> <span class="n">Removes</span> <span class="n">a</span> <span class="n">function</span> <span class="n">from</span> <span class="k">this</span> <span class="n">multi</span><span class="o">-</span><span class="n">cast</span> <span class="n">delegate</span><span class="err">&#39;</span><span class="n">s</span> <span class="n">invocation</span> <span class="n">list</span> <span class="p">(</span><span class="n">performance</span> <span class="n">is</span> <span class="n">O</span><span class="p">(</span><span class="n">N</span><span class="p">)).</span>  <span class="n">Note</span> <span class="n">that</span> <span class="n">the</span>
</span></span><span class="line"><span class="cl"><span class="o">*</span> <span class="n">order</span> <span class="n">of</span> <span class="n">the</span> <span class="n">delegates</span> <span class="n">may</span> <span class="n">not</span> <span class="n">be</span> <span class="n">preserved</span><span class="o">!</span></span></span></code></pre></div></div>
<p>The practical takeaway: the order is stable as long as nothing unbinds from the dispatcher. Unbinding from this dispatcher anywhere in the project can reorder its remaining listeners.</p>
<div class="callout callout--note">
  <div class="callout__title">Note</div>
  Binding again does not move the event to the end of the list. The <code>Bind Event to…</code> node compiles to <code>EX_AddMulticastDelegate</code> (<code>ScriptCore.cpp:3342</code>), which calls <code>FMulticastInlineDelegateProperty::AddDelegate</code> (<code>PropertyMulticastDelegate.cpp:497</code>), which calls <code>InvocationList.AddUnique</code> (<code>ScriptDelegates.h:1040</code>). <code>AddUnique</code> leaves an existing entry at its current position, so binding a second time does nothing.
</div>

<div class="callout callout--warning">
  <div class="callout__title">Warning</div>
  Relying on the order is fine as long as you can answer two questions: <strong>what sets it</strong> and <strong>what can change it</strong>. If the answer to both is “I do not know,” the graph works by accident.
</div>

<h2 id="what-to-do-about-it">What to do about it</h2>
<p>One pattern forces a decision: <strong>a dispatcher handler that broadcasts another dispatcher while it runs.</strong> It opens a window in which some listeners have not registered yet. Five ways out, each with its own condition. The first three change the graph and have results shown below. The last two are a project convention rather than a change in the nodes.</p>
<h3 id="defer-the-broadcast-by-one-tick">Defer the broadcast by one tick</h3>
<p>Instead of broadcasting <code>OnDelegate_2</code> straight from <code>CustomEvent_1</code>, route it through <code>Set Timer for Next Tick by Event</code> and a separate <code>BroadcastDelegate2</code> event. The <code>OnDelegate_1</code> broadcast then runs through every handler, each one gets to bind, and <code>OnDelegate_2</code> starts with a complete list.</p>
<figure class="bp-embed">
  <div class="bp-embed__canvas" data-bp-src="/blueprints/dispatcher-fix-next-tick.txt" data-bp-height="1340"></div>
  <noscript>
    <p class="bp-embed__fallback">Viewing the graph requires JavaScript.</p>
    <a class="bp-embed__download" href="/blueprints/dispatcher-fix-next-tick.txt" download>Download Blueprint graph (.txt)</a>
  </noscript><figcaption class="bp-embed__caption">CustomEvent_1 does not broadcast OnDelegate_2 directly. It hands that off to BroadcastDelegate2 through Set Timer for Next Tick by Event.</figcaption></figure>
<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">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[1]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[2]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[END TEST]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[3]</span>
</span></code></pre>
</div>
<p><code>[3]</code> landing <strong>after</strong> the <code>[END TEST]</code> marker shows exactly what this solution does: the broadcast moved out of the synchronous execution triggered by the key press and into the next frame.</p>
<p>This applies when no <code>OnDelegate_2</code> listener binds later than the next frame. An actor spawned two frames later, or a widget created on demand, still misses out. It widens the window; it does not remove the dependency.</p>
<div class="callout callout--tip">
  <div class="callout__title">Tip</div>
  <code>Set Timer for Next Tick by Event</code> takes no time input, so it cannot accidentally be set to zero. <code>Set Timer by Event</code> can, and there <a href="/en/topics/unreal-engine/gameplay-framework/set-timer-by-event-time-zero"><code>Time = 0.0</code> clears the timer instead of firing it immediately</a>.
</div>

<h3 id="move-the-binding-to-a-dispatcher-that-is-guaranteed-to-fire-later">Move the binding to a dispatcher that is guaranteed to fire later</h3>
<p><code>CustomEvent_3</code> goes on <code>OnDelegate_3</code>, which is known to fire later. Here the same key press broadcasts it, right after <code>OnDelegate_1</code>.</p>
<figure class="bp-embed">
  <div class="bp-embed__canvas" data-bp-src="/blueprints/dispatcher-fix-later-dispatcher.txt" data-bp-height="1080"></div>
  <noscript>
    <p class="bp-embed__fallback">Viewing the graph requires JavaScript.</p>
    <a class="bp-embed__download" href="/blueprints/dispatcher-fix-later-dispatcher.txt" download>Download Blueprint graph (.txt)</a>
  </noscript><figcaption class="bp-embed__caption">CustomEvent_2 binds CustomEvent_3 to OnDelegate_3 instead of OnDelegate_2.</figcaption></figure>
<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">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[1]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[2]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[3]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[END TEST]</span>
</span></code></pre>
</div>
<p>Cheap and effective, but it still depends on ordering: the order of separate broadcasts rather than the order of handlers within one broadcast. The condition is that nobody moves that call.</p>
<h3 id="turn-the-event-into-state">Turn the event into state</h3>
<p>A dispatcher carries information only at the moment of the call. Whoever arrives late never gets it. A <code>bDelegate2Broadcast</code> bool on the sender side changes that rule: <code>CustomEvent_1</code> sets it before the broadcast, and <code>CustomEvent_2</code>, once its binding is in place, checks it with a <code>Branch</code> and calls <code>CustomEvent_3</code> directly if needed.</p>
<figure class="bp-embed">
  <div class="bp-embed__canvas" data-bp-src="/blueprints/dispatcher-fix-event-to-state.txt" data-bp-height="1080"></div>
  <noscript>
    <p class="bp-embed__fallback">Viewing the graph requires JavaScript.</p>
    <a class="bp-embed__download" href="/blueprints/dispatcher-fix-event-to-state.txt" download>Download Blueprint graph (.txt)</a>
  </noscript><figcaption class="bp-embed__caption">CustomEvent_1 sets bDelegate2Broadcast before the call. After binding, CustomEvent_2 checks the flag with a Branch and makes up for the missed broadcast.</figcaption></figure>
<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">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[1]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[2]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[3]</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[END TEST]</span>
</span></code></pre>
</div>
<p><code>[3]</code> appears synchronously here, still within the same key press. The late listener does not wait for another broadcast. It reads the state and catches up immediately. This is the only option that survives a listener created after everything else, at the cost of maintaining extra state.</p>
<h3 id="declare-the-order-instead-of-relying-on-it-silently">Declare the order instead of relying on it silently</h3>
<p>If the order is meant to be a contract, write it down: a comment on both <code>Bind Event to…</code> nodes stating which one comes first and what depends on it, an event name that identifies the phase, and an explicit condition that nothing unbinds from this dispatcher. This option documents the existing ordering dependency without changing the graph.</p>
<h3 id="separate-the-phases-bindings-in-one-broadcasts-in-the-next">Separate the phases: bindings in one, broadcasts in the next</h3>
<p>If every <code>Bind Event to…</code> runs in a phase earlier than any call, the order within the list stops meaning anything. Here the dependency disappears instead of moving further out. The condition is that the phase boundary is clearly defined in the project and nobody binds after it.</p>
<h2 id="when-this-matters">When this matters</h2>
<p>As long as every binding to a given dispatcher lives in one Blueprint, the problem barely exists. The order is visible at a glance, and nobody changes it by accident.</p>
<p>The risk shows up once listeners of the same dispatcher spread across different lifecycle phases: GameInstance, a subsystem, GameMode, an actor placed in the level, a spawned actor, a widget created on demand. Call order stops being a property of the graph and becomes a property of the whole project: it is set by the phases in which all the <code>Bind Event to…</code> nodes attached to that dispatcher run. Move one of them to a different phase and the rest shift position in the list.</p>
]]></content:encoded>
      
    </item>
    
  </channel>
</rss>
