<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Eric Friedman's Engineering Blog]]></title><description><![CDATA[Here are my opinions and tutorials on game development, focused around Unreal Engine]]></description><link>https://www.jooballin.com</link><image><url>https://substackcdn.com/image/fetch/$s_!7pLi!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa342a86e-3ce0-406c-8239-95912f322108_277x277.png</url><title>Eric Friedman&apos;s Engineering Blog</title><link>https://www.jooballin.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 12 May 2026 20:17:57 GMT</lastBuildDate><atom:link href="https://www.jooballin.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Jooballin]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[jooballin@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[jooballin@substack.com]]></itunes:email><itunes:name><![CDATA[Eric Friedman]]></itunes:name></itunes:owner><itunes:author><![CDATA[Eric Friedman]]></itunes:author><googleplay:owner><![CDATA[jooballin@substack.com]]></googleplay:owner><googleplay:email><![CDATA[jooballin@substack.com]]></googleplay:email><googleplay:author><![CDATA[Eric Friedman]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Unreal Engine - CallOrRegister Pattern]]></title><description><![CDATA[Minimizing Code Duplication When Handling Non-Deterministic Events]]></description><link>https://www.jooballin.com/p/callorregister-pattern</link><guid isPermaLink="false">https://www.jooballin.com/p/callorregister-pattern</guid><dc:creator><![CDATA[Eric Friedman]]></dc:creator><pubDate>Tue, 12 Aug 2025 06:36:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!oFCZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c98c17d-1c4a-4e63-b992-873a76039d3d_2354x2604.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2><p>In games, one system or object will often need to respond to an event that happens elsewhere in the code. One common scenario is showing or updating the HUD when part of the game simulation changes, e.g. the player&#8217;s actor spawning in the world. <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/delegates-and-lambda-functions-in-unreal-engine">Delegates</a> are Unreal Engine&#8217;s method of declaring, subscribing to, and executing events at runtime.</p><h2>Example</h2><p>Given the above example scenario,  it would be best to structure the code in a deterministic way such that the HUD always subscribes to the actor&#8217;s Begin Play delegate before that event is triggered. This leads to simple and straightforward code. Here&#8217;s an example of a demo actor and view model.</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0c98c17d-1c4a-4e63-b992-873a76039d3d_2354x2604.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/29419a08-4d85-4321-a827-dc4c4fc835b5_2820x1512.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e5dc718f-95ac-4de7-a36d-f74f8b8e2353_2103x1183.png&quot;}],&quot;caption&quot;:&quot;SimpleEventSubscription and Demo Actor&quot;,&quot;alt&quot;:&quot;&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/228fcf17-3e31-4615-9687-5c5e46fe0041_1456x474.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p> Here is a video of this code in action</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;8fd380a3-7039-4743-9b7a-ca476a10688f&quot;,&quot;duration&quot;:null}"></div><p>As expected, as soon as the button is pressed, the callback subscriptions are made, the actor begins play, and the event is triggered in one frame.</p><h2>Problem</h2><p>Given the number of different ways an actor may spawn, for example via the network, level loading, manual spawning, etc., having a deterministic order and a simple setup won&#8217;t always be possible. Take the following case where the subscription to and triggering of the event is purposefully out of order as a demonstration.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4Bre!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4Bre!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png 424w, https://substackcdn.com/image/fetch/$s_!4Bre!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png 848w, https://substackcdn.com/image/fetch/$s_!4Bre!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png 1272w, https://substackcdn.com/image/fetch/$s_!4Bre!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4Bre!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png" width="1456" height="1931" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1931,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:830652,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.jooballin.com/i/168792724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4Bre!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png 424w, https://substackcdn.com/image/fetch/$s_!4Bre!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png 848w, https://substackcdn.com/image/fetch/$s_!4Bre!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png 1272w, https://substackcdn.com/image/fetch/$s_!4Bre!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51be0fbf-5169-445e-bb64-b12c89431f06_2533x3360.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this function, the following happens:</p><ul><li><p>The first callback subscribes to the event immediately</p></li><li><p>The second callback subscribes to the event after two seconds</p></li><li><p>The actor finishes spawning, triggering the event, after one second</p></li></ul><p>Here is the video of this sequence</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;24061135-8ff8-4d2a-aa08-83f9dd314bb7&quot;,&quot;duration&quot;:null}"></div><p>As expected, the second callback is never triggered since the subscription happens after the event fires. </p><h2>Solution 1</h2><p>One naive solution is to check if the event being subscribed to has already happened. In the event the actor has already begun play, instead of subscribing to the event, immediately run the callback code. Here&#8217;s the same function as above with those checks.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IyZ-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IyZ-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png 424w, https://substackcdn.com/image/fetch/$s_!IyZ-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png 848w, https://substackcdn.com/image/fetch/$s_!IyZ-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png 1272w, https://substackcdn.com/image/fetch/$s_!IyZ-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IyZ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png" width="1456" height="2988" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2988,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1142290,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.jooballin.com/i/168792724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IyZ-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png 424w, https://substackcdn.com/image/fetch/$s_!IyZ-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png 848w, https://substackcdn.com/image/fetch/$s_!IyZ-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png 1272w, https://substackcdn.com/image/fetch/$s_!IyZ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd87de036-6347-43b1-8d53-bb71541f1a23_2497x5124.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Although the checks aren&#8217;t necessary in this case since the order is known, this demonstrates what will be scattered throughout a codebase when it&#8217;s unclear what comes first: the subscription to or the triggering of an event.</p><p>Here is the video of this function running:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;b2052c14-c70a-4b94-b5be-bfee55cb6d31&quot;,&quot;duration&quot;:null}"></div><p>This fixes the out of order subscription issue and is logically correct. The problem is that every subscription to this event must manually check and handle the case where the event has already fired. </p><h2>Solution 2 </h2><p>The CallOrRegister pattern can assist in writing simple, straightforward, and easy to understand event handling code while removing the unnecessary code duplication. The concept is simple: move the branching logic into a function that called instead of subscribing to the delegate directly. Unreal uses the pattern CallOrRegister_X where the X is the name of the event. Here is the updated version of the Demo Actor with the CallOrRegister pattern.</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/00ac2861-6082-475e-bc15-92d93bfd0204_3107x1747.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dba2d2d1-b976-4a93-b041-f62933a2b4b3_3931x2211.png&quot;}],&quot;caption&quot;:&quot;CallOrRegisterDemoActor Updated&quot;,&quot;alt&quot;:&quot;&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c8db8e9a-4dd3-427f-91fc-6c1c10540861_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>The header defines a new function. </p><p>The source file has two changes:</p><ul><li><p>The CallOrRegister_OnBeginPlay implementation</p><ul><li><p>This function takes in a type of X::FDelegate&amp;&amp;, where X is the type of delegate</p></li><li><p>If the actor has already begun play, immediately trigger the callback that was passed in</p></li><li><p>If the actor hasn&#8217;t already begun play, add the callback to the OnBeginPlay delegate for later</p></li></ul></li><li><p>After the event is triggered in BeginPlay, clear out the list of callbacks</p></li></ul><p>Here&#8217;s the View Model function that uses the new pattern</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8Bw4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8Bw4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png 424w, https://substackcdn.com/image/fetch/$s_!8Bw4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png 848w, https://substackcdn.com/image/fetch/$s_!8Bw4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png 1272w, https://substackcdn.com/image/fetch/$s_!8Bw4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8Bw4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png" width="1456" height="1304" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1304,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1021831,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.jooballin.com/i/168792724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8Bw4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png 424w, https://substackcdn.com/image/fetch/$s_!8Bw4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png 848w, https://substackcdn.com/image/fetch/$s_!8Bw4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png 1272w, https://substackcdn.com/image/fetch/$s_!8Bw4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f47e577-5245-41e2-a53c-e92691f818c4_3752x3360.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>When comparing to the other out of order subscriptions, this has the simplicity of the first example, but also has the correct logic of the second example</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;5ea7ec1f-ed02-440a-ad78-34088105096a&quot;,&quot;duration&quot;:null}"></div><h2>Conclusion</h2><p>Delegates in Unreal Engine are not conceptionally complex; they are essentially a list of callbacks that are triggered when the delegate is invoked. Those callbacks can be manually triggered or manually added to the delegate. Understanding this allows for the CallOrRegister pattern to be a simple and straightforward way to write event handling code when the order of the subscription to and triggering of an event is not deterministic. </p><p>Example code can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Source/JooballinUE/Private/CallOrRegister">here</a>.</p><p>Example data can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Content/JooballinUE/CallOrRegister">here</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.jooballin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Eric Friedman's Engineering Blog! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Unreal Engine - Using Tasks to Sequence Events]]></title><description><![CDATA[Let the engine manage dependencies and sequence the code]]></description><link>https://www.jooballin.com/p/unreal-engine-using-tasks-to-sequence</link><guid isPermaLink="false">https://www.jooballin.com/p/unreal-engine-using-tasks-to-sequence</guid><dc:creator><![CDATA[Eric Friedman]]></dc:creator><pubDate>Thu, 06 Feb 2025 14:51:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ncmY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2><p>In the course of a game, there are times that the input from a player will initiate a predetermined sequence of events. The individual steps of an event may have a complicated dependency structure where some parts are prerequisites of others while other parts may be able to run in parallel. Unreal Engine provides the <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/tasks-systems-in-unreal-engine">Tasks System</a>, a generic job system that can handle sequencing of events and does so asynchronously by default. </p><h2>Demonstration</h2><p>Below is a demonstration of how the Tasks System can be used inside of a <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/umg-viewmodel-for-unreal-engine">ViewModel</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ncmY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ncmY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png 424w, https://substackcdn.com/image/fetch/$s_!ncmY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png 848w, https://substackcdn.com/image/fetch/$s_!ncmY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png 1272w, https://substackcdn.com/image/fetch/$s_!ncmY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ncmY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:664777,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ncmY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png 424w, https://substackcdn.com/image/fetch/$s_!ncmY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png 848w, https://substackcdn.com/image/fetch/$s_!ncmY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png 1272w, https://substackcdn.com/image/fetch/$s_!ncmY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde32c928-5651-4d3f-ae5e-c0730bd0276d_3895x2191.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Tasks are run via the Launch function in the UE::Tasks namespace. The function takes the following parameters:</p><ol><li><p>A debug string. The provided UE_SOURCE_LOCATION macro will set the debug string to the name and line number of the source file</p></li><li><p>The task to run. This is either a lambda or an rvalue reference to a functor &#8212; an object with an overloaded operator()</p></li><li><p>An optional list of prerequisite tasks.  </p></li></ol><p>In this demonstration, Tasks 2 and 3 have a prerequisite of Task 1. The final task has prerequisites of Tasks 2 and 3. As the name implies, all prerequisites must be complete before a given task will start; therefore Task 2 and 3 will start in parallel as soon as Task 1 is complete. Task 4 will only start after both Task 2 and 3 are complete.</p><p>Below is the definition of the functor used in this function.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!x3G4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fefc142-bb56-477a-829f-587ce6849157_3100x1743.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x3G4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fefc142-bb56-477a-829f-587ce6849157_3100x1743.png 424w, https://substackcdn.com/image/fetch/$s_!x3G4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fefc142-bb56-477a-829f-587ce6849157_3100x1743.png 848w, https://substackcdn.com/image/fetch/$s_!x3G4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fefc142-bb56-477a-829f-587ce6849157_3100x1743.png 1272w, https://substackcdn.com/image/fetch/$s_!x3G4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fefc142-bb56-477a-829f-587ce6849157_3100x1743.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!x3G4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fefc142-bb56-477a-829f-587ce6849157_3100x1743.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9fefc142-bb56-477a-829f-587ce6849157_3100x1743.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:402531,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!x3G4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fefc142-bb56-477a-829f-587ce6849157_3100x1743.png 424w, https://substackcdn.com/image/fetch/$s_!x3G4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fefc142-bb56-477a-829f-587ce6849157_3100x1743.png 848w, https://substackcdn.com/image/fetch/$s_!x3G4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fefc142-bb56-477a-829f-587ce6849157_3100x1743.png 1272w, https://substackcdn.com/image/fetch/$s_!x3G4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fefc142-bb56-477a-829f-587ce6849157_3100x1743.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Finally, below is the SetLoadingTaskColor function that the functor calls:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VoCB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d38d1bd-b536-4f1c-afe6-cad67870b6fa_4032x2268.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VoCB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d38d1bd-b536-4f1c-afe6-cad67870b6fa_4032x2268.png 424w, https://substackcdn.com/image/fetch/$s_!VoCB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d38d1bd-b536-4f1c-afe6-cad67870b6fa_4032x2268.png 848w, https://substackcdn.com/image/fetch/$s_!VoCB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d38d1bd-b536-4f1c-afe6-cad67870b6fa_4032x2268.png 1272w, https://substackcdn.com/image/fetch/$s_!VoCB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d38d1bd-b536-4f1c-afe6-cad67870b6fa_4032x2268.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VoCB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d38d1bd-b536-4f1c-afe6-cad67870b6fa_4032x2268.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3d38d1bd-b536-4f1c-afe6-cad67870b6fa_4032x2268.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:526445,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VoCB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d38d1bd-b536-4f1c-afe6-cad67870b6fa_4032x2268.png 424w, https://substackcdn.com/image/fetch/$s_!VoCB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d38d1bd-b536-4f1c-afe6-cad67870b6fa_4032x2268.png 848w, https://substackcdn.com/image/fetch/$s_!VoCB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d38d1bd-b536-4f1c-afe6-cad67870b6fa_4032x2268.png 1272w, https://substackcdn.com/image/fetch/$s_!VoCB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d38d1bd-b536-4f1c-afe6-cad67870b6fa_4032x2268.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Since the Task System doesn&#8217;t run code on the GameThread, SetLoadingTaskColor must use the AsyncTask method to force the color changing code on the GameThread as Unreal requires all UI changes to happen there. </p><p>The video below shows the TaskDemonstration function in action:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;3798d66c-b13e-41db-a98b-7df0e3f88352&quot;,&quot;duration&quot;:null}"></div><h2>Example</h2><p>A common event sequence found in games is going from one world to another, for example a main menu screen to an in-game world. In Unreal Engine, this would commonly be the result of a ClientTravel call on the PlayerController. This process can be hidden behind a loading screen that persists until the target world is ready for the player.</p><p>The video below shows a travel sequence made up of several tasks. The loading screen remains visible until all tasks are complete, demonstrated by the three green squares at the completion of the sequence.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;94e4f4ad-4b83-4492-94f1-55029773ee1a&quot;,&quot;duration&quot;:null}"></div><h3>Task Events</h3><p>Sometimes Tasks may take several frames to complete, e.g. traveling to another world. Since a Task is marked as complete after the code in operator() completes (or the lambda code completes if using one), there needs to be another mechanism to inform dependent Tasks when a given Task is &#8220;actually&#8221; complete.</p><p>This is where Task Events can be useful. Instead of using a Task as a prerequisite, a Task Event that must be manually triggered can be used as a substitute.</p><p>Below is an example of a Task that travels to another world and triggers a Task Event when that travel is complete:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!97MX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e6c5b4a-5252-4d57-ad1f-17e0372b103d_4289x2412.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!97MX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e6c5b4a-5252-4d57-ad1f-17e0372b103d_4289x2412.png 424w, https://substackcdn.com/image/fetch/$s_!97MX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e6c5b4a-5252-4d57-ad1f-17e0372b103d_4289x2412.png 848w, https://substackcdn.com/image/fetch/$s_!97MX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e6c5b4a-5252-4d57-ad1f-17e0372b103d_4289x2412.png 1272w, https://substackcdn.com/image/fetch/$s_!97MX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e6c5b4a-5252-4d57-ad1f-17e0372b103d_4289x2412.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!97MX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e6c5b4a-5252-4d57-ad1f-17e0372b103d_4289x2412.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3e6c5b4a-5252-4d57-ad1f-17e0372b103d_4289x2412.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:678239,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!97MX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e6c5b4a-5252-4d57-ad1f-17e0372b103d_4289x2412.png 424w, https://substackcdn.com/image/fetch/$s_!97MX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e6c5b4a-5252-4d57-ad1f-17e0372b103d_4289x2412.png 848w, https://substackcdn.com/image/fetch/$s_!97MX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e6c5b4a-5252-4d57-ad1f-17e0372b103d_4289x2412.png 1272w, https://substackcdn.com/image/fetch/$s_!97MX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e6c5b4a-5252-4d57-ad1f-17e0372b103d_4289x2412.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Below is another example of a Task that defines a Task Event. The Task Event is passed along to another ViewModel for triggering completion after the simulated loading completes and the GameThread has updated the color accordingly.</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bcb88505-8640-4fa9-9682-be9fe9f02da7_4110x2312.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/11fbe36d-d099-46ab-999b-bbc9a5507b8a_5866x3300.png&quot;}],&quot;caption&quot;:&quot;Simulate Loading Task and Set Loading Task Color&quot;,&quot;alt&quot;:&quot;&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7f19a14d-7fc7-40cd-ad28-255579ae4569_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>Finally, here is the code that runs all the tasks when the Travel to Another World button is clicked:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5eMg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca6ac7c-168c-4f90-9550-b4e52121b947_6010x3444.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5eMg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca6ac7c-168c-4f90-9550-b4e52121b947_6010x3444.png 424w, https://substackcdn.com/image/fetch/$s_!5eMg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca6ac7c-168c-4f90-9550-b4e52121b947_6010x3444.png 848w, https://substackcdn.com/image/fetch/$s_!5eMg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca6ac7c-168c-4f90-9550-b4e52121b947_6010x3444.png 1272w, https://substackcdn.com/image/fetch/$s_!5eMg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca6ac7c-168c-4f90-9550-b4e52121b947_6010x3444.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5eMg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca6ac7c-168c-4f90-9550-b4e52121b947_6010x3444.png" width="1456" height="834" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8ca6ac7c-168c-4f90-9550-b4e52121b947_6010x3444.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:834,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1472326,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5eMg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca6ac7c-168c-4f90-9550-b4e52121b947_6010x3444.png 424w, https://substackcdn.com/image/fetch/$s_!5eMg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca6ac7c-168c-4f90-9550-b4e52121b947_6010x3444.png 848w, https://substackcdn.com/image/fetch/$s_!5eMg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca6ac7c-168c-4f90-9550-b4e52121b947_6010x3444.png 1272w, https://substackcdn.com/image/fetch/$s_!5eMg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca6ac7c-168c-4f90-9550-b4e52121b947_6010x3444.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The function breaks down as follows:</p><ol><li><p>A loading screen widget is placed over the entire screen</p></li><li><p>A Task is started to travel to another world</p></li><li><p>3 Simulate Loading Tasks are created, all with a prerequisite of the OnTravelComplete Task Event being triggered</p></li><li><p>Finally, using a lambda version of a Task, the loading screen widget is removed from the viewport after the 3 Task Events from the 3 Simulate Loading Tasks are triggered</p></li></ol><h2>Conclusion</h2><p>Unreal Engine&#8217;s Task System is a great built-in tool for running parallel tasks and can handle complicated dependency structures. </p><p>Example code can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Source/JooballinUE/Private/TaskSequencing">here</a>.</p><p>Example data can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Content/JooballinUE/TaskSequencing">here</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.jooballin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Eric Friedman&#8217;s Engineering Blog! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Unreal Engine - Data Oriented Design and the Cost of Tick]]></title><description><![CDATA[Simple changes that can can lead to big performance improvements]]></description><link>https://www.jooballin.com/p/unreal-engine-data-oriented-design</link><guid isPermaLink="false">https://www.jooballin.com/p/unreal-engine-data-oriented-design</guid><dc:creator><![CDATA[Eric Friedman]]></dc:creator><pubDate>Mon, 14 Oct 2024 22:18:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!g9DT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2><p>When creating games in Unreal Engine, the engine code and most tutorials push developers towards an Object Oriented Design. Objects in the world derive from Actor, player characters often derive from Pawn, etc. While Object Oriented Programming is what most engineers are taught and allow for a quick creation of a set of classes that designers and other developers can manipulate, there are downsides to this approach. Monolithic classes that are difficult to change, huge class hierarchies via inheritance, and poor performance are some examples. Addressing the issues of deep inheritance hierarchies via composition using Actor Components still won&#8217;t address a lot of the performance problems of Object Oriented Programming.</p><p><a href="https://en.wikipedia.org/wiki/Data-oriented_design">Data Oriented Design</a> isn&#8217;t a new concept; however it is infrequently seen in the games industry, especially when compared to Object Oriented Design. At a high level, Data Oriented Design is the idea that code should be structured and modeled around the data that will be transformed. This is in contrast to Object Oriented Design which structures code around an object hierarchy that is meant to model objects in the world. </p><p>Data Oriented Design is a large topic. This is an Unreal Engine specific and narrowly focused example demonstrating how a small change in mindset and code design can result in big performance gains. </p><h2>Example</h2><p>Here is a video of the example to be analyzed:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;1d33ab27-b0c1-4aaf-bff8-a32b6cab3cae&quot;,&quot;duration&quot;:null}"></div><p>There are 10,000 actors in this scene, each with a static mesh component with a custom material. The material applies a world Z Offset and tints the mesh red based on parameters passed into the material instance. The Z offset is changed by using a cosine function and the red tint is changed by distance from the camera. </p><h4>Actor Tick</h4><p>Using a typical Object Oriented Design, here is a simple actor class that implements getting the Z Offset based on a cosine function and calculating the distance from the camera to get a red tint. The values are updated every tick. The camera location must be retrieved for all 10,000 actors even though it won&#8217;t change in the middle of a frame. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!g9DT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!g9DT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png 424w, https://substackcdn.com/image/fetch/$s_!g9DT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png 848w, https://substackcdn.com/image/fetch/$s_!g9DT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png 1272w, https://substackcdn.com/image/fetch/$s_!g9DT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!g9DT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png" width="1456" height="990" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:990,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1081632,&quot;alt&quot;:&quot;ADODActor Tick&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="ADODActor Tick" title="ADODActor Tick" srcset="https://substackcdn.com/image/fetch/$s_!g9DT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png 424w, https://substackcdn.com/image/fetch/$s_!g9DT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png 848w, https://substackcdn.com/image/fetch/$s_!g9DT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png 1272w, https://substackcdn.com/image/fetch/$s_!g9DT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abb2c06-07ee-4b9c-9729-965432e62c2b_4820x3276.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">ADODActor Tick</figcaption></figure></div><p>Using <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-insights-in-unreal-engine">Unreal Insights</a>, the time it takes to run this code per frame can be analyzed. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R7Bv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2df3a7-aad9-43e1-8a16-561a669a0b89_3047x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R7Bv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2df3a7-aad9-43e1-8a16-561a669a0b89_3047x900.png 424w, https://substackcdn.com/image/fetch/$s_!R7Bv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2df3a7-aad9-43e1-8a16-561a669a0b89_3047x900.png 848w, https://substackcdn.com/image/fetch/$s_!R7Bv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2df3a7-aad9-43e1-8a16-561a669a0b89_3047x900.png 1272w, https://substackcdn.com/image/fetch/$s_!R7Bv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2df3a7-aad9-43e1-8a16-561a669a0b89_3047x900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R7Bv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2df3a7-aad9-43e1-8a16-561a669a0b89_3047x900.png" width="1456" height="430" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f2df3a7-aad9-43e1-8a16-561a669a0b89_3047x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:430,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:326406,&quot;alt&quot;:&quot;ADODActor Tick Insights&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="ADODActor Tick Insights" title="ADODActor Tick Insights" srcset="https://substackcdn.com/image/fetch/$s_!R7Bv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2df3a7-aad9-43e1-8a16-561a669a0b89_3047x900.png 424w, https://substackcdn.com/image/fetch/$s_!R7Bv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2df3a7-aad9-43e1-8a16-561a669a0b89_3047x900.png 848w, https://substackcdn.com/image/fetch/$s_!R7Bv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2df3a7-aad9-43e1-8a16-561a669a0b89_3047x900.png 1272w, https://substackcdn.com/image/fetch/$s_!R7Bv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2df3a7-aad9-43e1-8a16-561a669a0b89_3047x900.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">ADODActor Tick Insights</figcaption></figure></div><p>In a typical frame, the timer section is reporting about 8.2ms for ticking the 10,000 actors. Looking on the left side of the insights, the full cost is more clear &#8212; about 12.87ms. Dealing with a large number of actors that tick a relatively small amount of logic results in a large percentage of wasted time &#8212; about 36% in this example.</p><h4>Actor Tick by Manager</h4><p>While not actually an implementation of a Data Oriented Design, updating a large number of actors manually from an outside object can result in significant performance gains. As seen above, there is a lot of time spent in the management of the tick system. Here is how the same code could be executed from an outside object.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TuHD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F214cf9c1-8825-4cc5-8494-b05b07d71bc6_4999x3360.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TuHD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F214cf9c1-8825-4cc5-8494-b05b07d71bc6_4999x3360.png 424w, https://substackcdn.com/image/fetch/$s_!TuHD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F214cf9c1-8825-4cc5-8494-b05b07d71bc6_4999x3360.png 848w, https://substackcdn.com/image/fetch/$s_!TuHD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F214cf9c1-8825-4cc5-8494-b05b07d71bc6_4999x3360.png 1272w, https://substackcdn.com/image/fetch/$s_!TuHD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F214cf9c1-8825-4cc5-8494-b05b07d71bc6_4999x3360.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TuHD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F214cf9c1-8825-4cc5-8494-b05b07d71bc6_4999x3360.png" width="1456" height="979" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/214cf9c1-8825-4cc5-8494-b05b07d71bc6_4999x3360.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:979,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:954651,&quot;alt&quot;:&quot;ADODActorManager Tick Actors&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="ADODActorManager Tick Actors" title="ADODActorManager Tick Actors" srcset="https://substackcdn.com/image/fetch/$s_!TuHD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F214cf9c1-8825-4cc5-8494-b05b07d71bc6_4999x3360.png 424w, https://substackcdn.com/image/fetch/$s_!TuHD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F214cf9c1-8825-4cc5-8494-b05b07d71bc6_4999x3360.png 848w, https://substackcdn.com/image/fetch/$s_!TuHD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F214cf9c1-8825-4cc5-8494-b05b07d71bc6_4999x3360.png 1272w, https://substackcdn.com/image/fetch/$s_!TuHD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F214cf9c1-8825-4cc5-8494-b05b07d71bc6_4999x3360.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">ADODActorManager Tick Actors</figcaption></figure></div><p>Here is the timing information for this code:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rIRo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b792df-aa54-47bc-8104-56a1f9057bec_2244x516.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rIRo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b792df-aa54-47bc-8104-56a1f9057bec_2244x516.png 424w, https://substackcdn.com/image/fetch/$s_!rIRo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b792df-aa54-47bc-8104-56a1f9057bec_2244x516.png 848w, https://substackcdn.com/image/fetch/$s_!rIRo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b792df-aa54-47bc-8104-56a1f9057bec_2244x516.png 1272w, https://substackcdn.com/image/fetch/$s_!rIRo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b792df-aa54-47bc-8104-56a1f9057bec_2244x516.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rIRo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b792df-aa54-47bc-8104-56a1f9057bec_2244x516.png" width="1456" height="335" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/24b792df-aa54-47bc-8104-56a1f9057bec_2244x516.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:335,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:173376,&quot;alt&quot;:&quot;ADODActorManager Tick Actors Insights&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="ADODActorManager Tick Actors Insights" title="ADODActorManager Tick Actors Insights" srcset="https://substackcdn.com/image/fetch/$s_!rIRo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b792df-aa54-47bc-8104-56a1f9057bec_2244x516.png 424w, https://substackcdn.com/image/fetch/$s_!rIRo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b792df-aa54-47bc-8104-56a1f9057bec_2244x516.png 848w, https://substackcdn.com/image/fetch/$s_!rIRo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b792df-aa54-47bc-8104-56a1f9057bec_2244x516.png 1272w, https://substackcdn.com/image/fetch/$s_!rIRo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24b792df-aa54-47bc-8104-56a1f9057bec_2244x516.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">ADODActorManager Tick Actors Insights</figcaption></figure></div><p>In a typical frame, the timer section is reporting 4.7ms to tick all of the actors. This is approaching half the time it takes to call the virtual Tick on the actors individually. Additionally, the total time is down from 12.87m &#8212; a savings of about 64%.</p><h2>Tick Manager Data</h2><p>Finally, if instead of having each individual actor be responsible for updating its data, the data is stored internally to the manager and updated, that code could look like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IJq7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e76c069-9880-4e8b-a499-13ad14c3c94b_4820x2711.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IJq7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e76c069-9880-4e8b-a499-13ad14c3c94b_4820x2711.png 424w, https://substackcdn.com/image/fetch/$s_!IJq7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e76c069-9880-4e8b-a499-13ad14c3c94b_4820x2711.png 848w, https://substackcdn.com/image/fetch/$s_!IJq7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e76c069-9880-4e8b-a499-13ad14c3c94b_4820x2711.png 1272w, https://substackcdn.com/image/fetch/$s_!IJq7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e76c069-9880-4e8b-a499-13ad14c3c94b_4820x2711.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IJq7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e76c069-9880-4e8b-a499-13ad14c3c94b_4820x2711.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1e76c069-9880-4e8b-a499-13ad14c3c94b_4820x2711.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:890453,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IJq7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e76c069-9880-4e8b-a499-13ad14c3c94b_4820x2711.png 424w, https://substackcdn.com/image/fetch/$s_!IJq7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e76c069-9880-4e8b-a499-13ad14c3c94b_4820x2711.png 848w, https://substackcdn.com/image/fetch/$s_!IJq7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e76c069-9880-4e8b-a499-13ad14c3c94b_4820x2711.png 1272w, https://substackcdn.com/image/fetch/$s_!IJq7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e76c069-9880-4e8b-a499-13ad14c3c94b_4820x2711.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">ADODActorManager Tick Manager Data</figcaption></figure></div><p>This is an example of what Data Oriented Design is as opposed to Object Oriented. It&#8217;s not perfect as the code still reaches into the actors to update their dynamic material, which in the process is going to jump all over RAM, causing continual cache misses. That being said even just being able to grab the camera location once as opposed to 10,000 times is a significant savings.</p><p>Here is the timing information for this code:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0m4t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F913a2391-9488-48a4-b7dd-fe800b03f47a_2193x501.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0m4t!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F913a2391-9488-48a4-b7dd-fe800b03f47a_2193x501.png 424w, https://substackcdn.com/image/fetch/$s_!0m4t!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F913a2391-9488-48a4-b7dd-fe800b03f47a_2193x501.png 848w, https://substackcdn.com/image/fetch/$s_!0m4t!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F913a2391-9488-48a4-b7dd-fe800b03f47a_2193x501.png 1272w, https://substackcdn.com/image/fetch/$s_!0m4t!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F913a2391-9488-48a4-b7dd-fe800b03f47a_2193x501.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0m4t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F913a2391-9488-48a4-b7dd-fe800b03f47a_2193x501.png" width="1456" height="333" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/913a2391-9488-48a4-b7dd-fe800b03f47a_2193x501.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:333,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:161673,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0m4t!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F913a2391-9488-48a4-b7dd-fe800b03f47a_2193x501.png 424w, https://substackcdn.com/image/fetch/$s_!0m4t!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F913a2391-9488-48a4-b7dd-fe800b03f47a_2193x501.png 848w, https://substackcdn.com/image/fetch/$s_!0m4t!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F913a2391-9488-48a4-b7dd-fe800b03f47a_2193x501.png 1272w, https://substackcdn.com/image/fetch/$s_!0m4t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F913a2391-9488-48a4-b7dd-fe800b03f47a_2193x501.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">ADODActorManager Tick Manager Data Insights</figcaption></figure></div><p>In a typical frame, the timer section is reporting 3.6ms to tick the internal stored data. This is about a 24% savings over the manager ticking the actors, and about a 72% savings from ticking all of the actors individually!</p><h2>Conclusion</h2><p>Data Oriented Design is a large topic. From creating code that is easily parallelized, easily vectorized to use SIMD operations and more cache efficient, the performance gains can be significant. Epic Games is working on their Data Oriented gameplay framework, <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/mass-entity-in-unreal-engine">Mass</a>, which is currently experimental at the time of writing. Also, with <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/iris-replication-system-in-unreal-engine">Iris</a>, Epic took more of a Data Oriented design. See SphereNetObjectPrioritizer as an example of how Iris determines net priority based on distance to the camera.</p><p>Example code can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Source/JooballinUE/Private/DOD">here</a>.</p><p>Example data can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Content/JooballinUE/DOD">here</a>.</p><h2>Recommended Links</h2><p><a href="https://www.youtube.com/watch?v=rX0ItVEVjHc">GDC 2014: Mike Acton &#8220;Data-Oriented Design and C++&#8221;</a></p><p><a href="https://www.youtube.com/watch?v=Ge3aKEmZcqY">Casey Muratori&#8217;s Simple Code High Performance</a></p><p><a href="https://www.youtube.com/watch?v=xt1KNDmOYqA">Casey Muratori&#8217;s Becoming an N+2 Programmer</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.jooballin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Eric Friedman&#8217;s Engineering Blog! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Unreal Engine - The Asset Manager, Primary Assets and Asset Bundles]]></title><description><![CDATA[Loading only the desired assets]]></description><link>https://www.jooballin.com/p/unreal-engine-the-asset-manager-primary</link><guid isPermaLink="false">https://www.jooballin.com/p/unreal-engine-the-asset-manager-primary</guid><dc:creator><![CDATA[Eric Friedman]]></dc:creator><pubDate>Fri, 23 Aug 2024 19:49:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Qg88!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2><p>As the data for a game grows in size and complexity, it is important to load only what is needed given the player&#8217;s context. For the assets that won&#8217;t be managed automatically, Unreal Engine provides an <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/asset-management-in-unreal-engine">Asset Manager</a> that facilitates loading and unloading of assets manually. Combining the concepts of Primary Assets and Asset Bundles, subsets of these assets can also be loaded depending on the context.</p><div><hr></div><h2>Example</h2><p>Consider a game that has dozens of characters from which a player can choose. Characters could be described by a data asset. This data asset could have a texture of the character&#8217;s portrait to display in a lobby screen, a localized name, a blueprint to use as a pawn in game, and dozens of additional pieces of data and assets. The following code is an example of how that might be set up:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Qg88!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Qg88!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png 424w, https://substackcdn.com/image/fetch/$s_!Qg88!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png 848w, https://substackcdn.com/image/fetch/$s_!Qg88!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png 1272w, https://substackcdn.com/image/fetch/$s_!Qg88!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Qg88!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png" width="1456" height="977" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:977,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:300489,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Qg88!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png 424w, https://substackcdn.com/image/fetch/$s_!Qg88!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png 848w, https://substackcdn.com/image/fetch/$s_!Qg88!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png 1272w, https://substackcdn.com/image/fetch/$s_!Qg88!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5720d516-3379-4f46-bad9-54dd068bd08a_2503x1680.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Data Asset without Asset Bundles</figcaption></figure></div><p></p><h3>Asset Bundles</h3><p>Whenever a data asset of this type is loaded, it is also going to load every asset it references, even in a context where those assets might not be used. For example, the game may be designed such that the pawn blueprint isn&#8217;t used in the lobby. To address this wasteful loading time and memory usage, assets can be associated with Asset Bundles within Primary Assets. Here is the same object definition using Asset Bundles that adds context to the different assets to be loaded</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8ba86eaa-bb79-4db1-8e31-4f346e32c0b3_4032x2268.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b9c4c0a-257e-4112-8264-15aac30c6046_4331x2436.png&quot;}],&quot;caption&quot;:&quot;Header and CPP - Data Asset using Asset Bundles&quot;,&quot;alt&quot;:&quot;Header and CPP - Data Asset using Asset Bundles&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6448aff-df08-48eb-bec4-c077c15913fc_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p></p><p>The three different ways the data will be loaded are:</p><ul><li><p>CharacterLobbyPortrait is only loaded if the Data Asset is loaded with the Lobby Asset Bundle</p></li><li><p>CharacterGamePawnClass is only loaded if the Data Asset is loaded with the Game Asset Bundle</p></li><li><p>CharacterName is not an asset and will always be loaded</p></li></ul><p>Notice the property types of the objects that are associated with Asset Bundles. Instead of being TObjectPtr and TSubclassOf, they are now TSoftObjectPtr and TSoftClassPtr respectively. Internally, these contain paths to the asset and a pointer to that asset in memory if valid. TObjectPtr and TSubclassOf are &#8216;Hard&#8217; references, and will always be loaded when their owning assets are loaded.</p><p>In order for the Asset Manager to load the Asset Bundles requested, the owning object must be a Primary Asset.</p><h3>Primary Assets</h3><p>In order for AssetBundlesCharacterData to be considered a Primary Asset and discovered by the Asset Manager, the following must be done:</p><ul><li><p>Overload the GetPrimaryAssetId function from UObject and return a unique Id based on the type and asset</p><ul><li><p>This is handled automatically when deriving from UPrimaryDataAsset instead of UDataAsset</p></li><li><p>Note: Deriving from UPrimaryDataAsset also adds support for Asset Bundles</p></li></ul></li><li><p>Add the new Asset Type in Project Settings &#8594; Game &#8594; Asset Manager</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GEpM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32b51442-62f7-4f9c-830d-a2c9a407b8ad_2572x1136.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GEpM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32b51442-62f7-4f9c-830d-a2c9a407b8ad_2572x1136.png 424w, https://substackcdn.com/image/fetch/$s_!GEpM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32b51442-62f7-4f9c-830d-a2c9a407b8ad_2572x1136.png 848w, https://substackcdn.com/image/fetch/$s_!GEpM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32b51442-62f7-4f9c-830d-a2c9a407b8ad_2572x1136.png 1272w, https://substackcdn.com/image/fetch/$s_!GEpM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32b51442-62f7-4f9c-830d-a2c9a407b8ad_2572x1136.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GEpM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32b51442-62f7-4f9c-830d-a2c9a407b8ad_2572x1136.png" width="1456" height="643" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/32b51442-62f7-4f9c-830d-a2c9a407b8ad_2572x1136.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:643,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:188499,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GEpM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32b51442-62f7-4f9c-830d-a2c9a407b8ad_2572x1136.png 424w, https://substackcdn.com/image/fetch/$s_!GEpM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32b51442-62f7-4f9c-830d-a2c9a407b8ad_2572x1136.png 848w, https://substackcdn.com/image/fetch/$s_!GEpM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32b51442-62f7-4f9c-830d-a2c9a407b8ad_2572x1136.png 1272w, https://substackcdn.com/image/fetch/$s_!GEpM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32b51442-62f7-4f9c-830d-a2c9a407b8ad_2572x1136.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Interacting with the Asset Manager</h2><p>Here is an example of a ViewModel that loads all of the AssetBundlesCharacterData Data Assets with different Asset Bundles.</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/be892699-36c5-47d8-ac4f-665730987fe6_3709x4284.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d9f51230-447f-4406-856f-4c1ba65f3bce_4820x6636.png&quot;}],&quot;caption&quot;:&quot;Header and CPP - View Model&quot;,&quot;alt&quot;:&quot;Header and CPP - View Model&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1a4c0aaf-2f7d-4d33-b8ca-42f1f7ed3094_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>Note: The ClearCharacterData function forces garbage collection for demonstration purposes so that all assets that are no longer referenced are immediately deleted. </p><h2>Asset Bundles in Action</h2><p>Here is an example instance of an AssetBundlesCharacterData instance and the results of running a UI with the ViewModel above.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ezKB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99a6e2b6-5f85-494e-8736-c079285d79fe_2175x619.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ezKB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99a6e2b6-5f85-494e-8736-c079285d79fe_2175x619.png 424w, https://substackcdn.com/image/fetch/$s_!ezKB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99a6e2b6-5f85-494e-8736-c079285d79fe_2175x619.png 848w, https://substackcdn.com/image/fetch/$s_!ezKB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99a6e2b6-5f85-494e-8736-c079285d79fe_2175x619.png 1272w, https://substackcdn.com/image/fetch/$s_!ezKB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99a6e2b6-5f85-494e-8736-c079285d79fe_2175x619.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ezKB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99a6e2b6-5f85-494e-8736-c079285d79fe_2175x619.png" width="1456" height="414" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/99a6e2b6-5f85-494e-8736-c079285d79fe_2175x619.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:414,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:79323,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ezKB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99a6e2b6-5f85-494e-8736-c079285d79fe_2175x619.png 424w, https://substackcdn.com/image/fetch/$s_!ezKB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99a6e2b6-5f85-494e-8736-c079285d79fe_2175x619.png 848w, https://substackcdn.com/image/fetch/$s_!ezKB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99a6e2b6-5f85-494e-8736-c079285d79fe_2175x619.png 1272w, https://substackcdn.com/image/fetch/$s_!ezKB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99a6e2b6-5f85-494e-8736-c079285d79fe_2175x619.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;44a2ef1d-1917-49b4-9b6b-1508764e762a&quot;,&quot;duration&quot;:null}"></div><p></p><div><hr></div><h2>Conclusion</h2><p>Loading data when it&#8217;s not needed will increase loading times, memory footprint, and negatively affect players. It is important to understand what is loaded and when. Using the Asset Manager correctly is specific to each game and a necessary tool for the engineers to learn. </p><p>Example code can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Source/JooballinUE/Private/AssetBundles">here</a>.</p><p>Example assets can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Content/JooballinUE/AssetBundles">here</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.jooballin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Eric Friedman&#8217;s Engineering Blog! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Unreal Engine - The Type Container and MVVM(S) Architecture]]></title><description><![CDATA[MVVM, Services, and Dependency Injection... kind of]]></description><link>https://www.jooballin.com/p/unreal-engine-the-type-container</link><guid isPermaLink="false">https://www.jooballin.com/p/unreal-engine-the-type-container</guid><dc:creator><![CDATA[Eric Friedman]]></dc:creator><pubDate>Thu, 01 Aug 2024 16:13:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!wBXU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c0a96ef-81ed-48bc-9b1f-5199d54790e4_2175x1223.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2><p>The Model View View Model (MVVM) architecture is a software design pattern to separate the user interface from the business logic of an application. In game terms, business logic would be the state of the simulation. View Models often use a service, or multiple, to interact with an API to the backend of an application in what James Montemagno refers to as MVVMS in his video <a href="https://youtu.be/ve0DFu-arD8?si=j-TwdISeCEcO_a_L">here</a>.</p><p>In games it is common to have screens that have a one-to-one or one-to-many relationship with services. Examples are a player profile, loadouts or a store. Integrating the use of a Type Container with Unreal Engine&#8217;s View Models allows for the user interface to interact with these services in a decoupled and testable manner. </p><h2>Example</h2><p>Below is a simplified example of a common pattern for a ProfileService.  The service acts as the API and the implementation requires a ProfileRepo which does the actual retrieving of the profile.</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4c0a96ef-81ed-48bc-9b1f-5199d54790e4_2175x1223.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/67db089e-f3d2-4f7d-8211-4c103a636d86_3501x1969.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45e2575b-ec34-43ee-a9bb-09ca97587681_3286x1848.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/449f7175-7259-40ef-84c7-7644a77e5c1b_2175x1223.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5a0cce24-eeb2-431a-b92d-3e80e5272b31_3071x1727.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/58616981-04fa-4cc9-81fe-20b0370a50dc_4182x2520.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/efcda146-f5b9-4fff-a3fd-4f7ab1cc9b44_1786x1428.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cf182d0e-f05f-4b63-b410-817fe253928b_1464x823.png&quot;}],&quot;caption&quot;:&quot;Service and Repo interfaces and implementations. Profile Model and example json&quot;,&quot;alt&quot;:&quot;Service and Repo interfaces and implementations. Profile Model and example json&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/679baa65-e2d5-49b1-8418-0bee2c4ac001_1456x1700.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>Note the Expose_TNameOf lines after the declaration of the Interfaces. This is necessary. If forgotten, the GetInstance calls in the Type Container will not work and crash the program.  </p><h2>The Type Container</h2><p>Given the above service and repo, the concrete versions can be associated with, stored, and retrieved by their interface in a Type Container. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7d_s!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e7c7cb1-4346-4e0a-82c3-86438fb3d42e_4833x2718.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7d_s!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e7c7cb1-4346-4e0a-82c3-86438fb3d42e_4833x2718.png 424w, https://substackcdn.com/image/fetch/$s_!7d_s!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e7c7cb1-4346-4e0a-82c3-86438fb3d42e_4833x2718.png 848w, https://substackcdn.com/image/fetch/$s_!7d_s!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e7c7cb1-4346-4e0a-82c3-86438fb3d42e_4833x2718.png 1272w, https://substackcdn.com/image/fetch/$s_!7d_s!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e7c7cb1-4346-4e0a-82c3-86438fb3d42e_4833x2718.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7d_s!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e7c7cb1-4346-4e0a-82c3-86438fb3d42e_4833x2718.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3e7c7cb1-4346-4e0a-82c3-86438fb3d42e_4833x2718.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:398995,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7d_s!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e7c7cb1-4346-4e0a-82c3-86438fb3d42e_4833x2718.png 424w, https://substackcdn.com/image/fetch/$s_!7d_s!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e7c7cb1-4346-4e0a-82c3-86438fb3d42e_4833x2718.png 848w, https://substackcdn.com/image/fetch/$s_!7d_s!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e7c7cb1-4346-4e0a-82c3-86438fb3d42e_4833x2718.png 1272w, https://substackcdn.com/image/fetch/$s_!7d_s!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e7c7cb1-4346-4e0a-82c3-86438fb3d42e_4833x2718.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The Type Container provides four ways to register a concrete class with its interface, two of which are demonstrated here:</p><ul><li><p>RegisterClass takes the interface type and the concrete type as template parameters. It also takes a variable number of template parameters if the concrete implementation takes constructor arguments (not shown here). </p></li><li><p>RegisterInstance takes the interface type as a template parameter and then a concrete instance of that interface as an argument. </p></li><li><p>RegisterFactory and RegisterDelegate are the additional two ways to register types. See <a href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Core/Tests/Misc/TypeContainerTest.cpp">TypeContainerTests.cpp</a> for examples.</p></li></ul><p>Given a Type Container, call GetInstance with a template parameter of the interface type to retrieve a concrete implementation. Depending on the register method used, the object returned will differ in the following ways:</p><ul><li><p>RegisterClass will return a new insanitation on each call to GetInstance.</p></li><li><p>RegisterInstance will return the one instance that is passed in at registration time.</p></li><li><p>RegisterFactory and RegisterDelegate must return an instance of the interface type; however what they return is implementation specific.</p></li></ul><h3>Where to Instantiate a Type Container</h3><p>There are many options as to where the Type Container can be instantiated. Some common options are:</p><ul><li><p>A Subsystem (Engine, GameInstance, LocalPlayer, etc.)</p></li><li><p>On a custom Engine class</p></li><li><p>In a Module</p></li></ul><p>In this example, the Module class for a new ServicesModule has been chosen as the object to create and populate the Type Container as shown below:</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f79db6aa-fb50-4004-b49b-9b2813566cfc_2826x1589.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/87c684aa-e412-4e22-84c5-5e35fbc0c298_4976x2799.png&quot;}],&quot;caption&quot;:&quot;Type Container in a module&quot;,&quot;alt&quot;:&quot;Type Container in a module&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c5368d0b-5ebd-46a0-9733-2ff502ed1a6e_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>The pros of putting the Type Container in the Module are:</p><ul><li><p>During a normal run of the editor or game, StartupModule and ShutdownModule will be called exactly once</p></li><li><p>There is no need for a World Context Object, unlike a GameInstanceSubsystem</p></li></ul><h3>The Type Container in Action</h3><p>The following is an example of a View Model using the Type Container to get access to, storing, and using the Profile Service.</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/069edb55-aee2-4931-915c-eeb8d0ee3762_2897x3276.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/59b33614-51b6-4c51-ad27-72c0e5475897_3722x3108.png&quot;}],&quot;caption&quot;:&quot;View Model Header and CPP&quot;,&quot;alt&quot;:&quot;View Model Header and CPP&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fb010a00-81de-4be9-96c0-7ef0761beb8b_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>The asynchronous call to GetProfileAsync and usage of Promises and Futures is explained more <a href="https://www.jooballin.com/p/unreal-engine-asynctask-promises">here</a>.</p><p>The View Model stores the Profile Service in a TSharedRef which is non nullable version of a TSharedPtr. This has the benefit of preventing null pointer exceptions and the need to check for validity throughout its use. Since it can&#8217;t be null, it must be initialized in the Constructor member initializer list, including the version that takes the FVTableHelper which will be auto-generated and cause compiler errors if not explicitly written.</p><p>Unfortunately, Unreal Engine does not have a way to call a custom constructor with parameters on any class that derives from UObject; therefore real dependency injection can not be achieved. The alternatives are:</p><ul><li><p>What is shown here - in the constructors, get the object desired from a global Type Container. The tradeoffs of this option are:</p><ul><li><p>Pro - Guaranteed a valid object throughout the lifetime of the owning View Model.</p></li><li><p>Con - This code will crash if using a Type Container declared in the same Module where the View Model lives. This is because the Class Default Object (CDO) will be created before the StartupModule function is called, and the Type Container will not yet be populated with the desired type.</p></li></ul></li><li><p>Don&#8217;t store the service as a member variable in the class, but instead get it every time it is needed. The tradeoffs of this option are:</p><ul><li><p>Pro - This removes the need to declare the constructors and destructor in the example above. </p></li><li><p>Pro - The Type Container always returns a TSharedRef, so a valid object is still guaranteed at access time.</p></li><li><p>Con - Paying for access each time the type is needed</p></li><li><p>Con - More difficult to inspect the dependencies of the View Model while debugging. </p></li></ul></li></ul><p>Below is a video of this code in action:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;6a76a45d-36aa-41e0-96f9-8c5d78295b6f&quot;,&quot;duration&quot;:null}"></div><h2>Conclusion</h2><p>If a project has a use for the MVVMS architecture, it may not be as nice as other frameworks, but it is doable using Unreal Engine&#8217;s <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/umg-viewmodel">View Model</a> plugin coupled with a Type Container to store and retrieve services. </p><p>Example code can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Source/JooballinUE/Private/MVVMS">here</a>.</p><p>Example assets can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Content/JooballinUE/MVVMS">here</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.jooballin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Eric Friedman&#8217;s Engineering Blog! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Unreal Engine - AsyncTask, Promises, Futures, and Responsive UI]]></title><description><![CDATA[Keeping the user interface smooth]]></description><link>https://www.jooballin.com/p/unreal-engine-asynctask-promises</link><guid isPermaLink="false">https://www.jooballin.com/p/unreal-engine-asynctask-promises</guid><dc:creator><![CDATA[Eric Friedman]]></dc:creator><pubDate>Thu, 25 Jul 2024 14:49:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!n-v4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8dd4405-e797-4249-a80e-f659a04cc575_2748x2100.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2><p>Unreal Engine provides a few mechanisms to run asynchronous code. AsyncTask wraps functionality of the TaskGraph in a simple and convenient function that runs code in the form of a function or lambda on a specified thread. Promises and Futures, while not inherently multi-threaded, provide capabilities to trigger a continuation function once they are marked as completed in a thread safe manner. Combining AsyncTasks with Promises and Futures can keep the UI responsive even when doing expensive operations.</p><div><hr></div><h2>Example</h2><p>Below is a simplified example of a <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/umg-viewmodel">ViewModel</a> that simulates an expensive task, perhaps reading a file or requesting data from a backend service, by sleeping the thread for 3 seconds before increasing the RunCount by 1.</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e8dd4405-e797-4249-a80e-f659a04cc575_2748x2100.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c422dbed-92a9-44ad-932b-fcd6519689d8_2067x1163.png&quot;}],&quot;caption&quot;:&quot;Header and CPP File - Not Threaded&quot;,&quot;alt&quot;:&quot;Header and CPP File - Not Threaded&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aabc3631-cd07-4762-9c4a-94521f1459d3_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>This ViewModel is hooked up to the following widget using the View Bindings window. The bindings are: </p><ul><li><p>Clicking on the Run Button calls the function </p></li><li><p>The Common Text Box holds the FText representation of RunCount. </p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5aGf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6f0409f-c016-4254-9dcc-d478b330be26_2867x1710.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5aGf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6f0409f-c016-4254-9dcc-d478b330be26_2867x1710.png 424w, https://substackcdn.com/image/fetch/$s_!5aGf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6f0409f-c016-4254-9dcc-d478b330be26_2867x1710.png 848w, https://substackcdn.com/image/fetch/$s_!5aGf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6f0409f-c016-4254-9dcc-d478b330be26_2867x1710.png 1272w, https://substackcdn.com/image/fetch/$s_!5aGf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6f0409f-c016-4254-9dcc-d478b330be26_2867x1710.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5aGf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6f0409f-c016-4254-9dcc-d478b330be26_2867x1710.png" width="1456" height="868" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f6f0409f-c016-4254-9dcc-d478b330be26_2867x1710.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:868,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:209623,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5aGf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6f0409f-c016-4254-9dcc-d478b330be26_2867x1710.png 424w, https://substackcdn.com/image/fetch/$s_!5aGf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6f0409f-c016-4254-9dcc-d478b330be26_2867x1710.png 848w, https://substackcdn.com/image/fetch/$s_!5aGf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6f0409f-c016-4254-9dcc-d478b330be26_2867x1710.png 1272w, https://substackcdn.com/image/fetch/$s_!5aGf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6f0409f-c016-4254-9dcc-d478b330be26_2867x1710.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Widget - Synchronous</figcaption></figure></div><p>There&#8217;s also an animated spinner to demonstrate when the game thread is blocked. </p><p>The following video demonstrates this ViewModel in action</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;11d5bd22-a61b-49bf-83b1-a85d38468c70&quot;,&quot;duration&quot;:null}"></div><div><hr></div><h2>Making it Asynchronous</h2><p>This is the asynchronous version of the same ViewModel from above</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ebea36f3-af46-4e13-b473-48360c85b952_2676x2436.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e7d95587-cfbc-47b8-bed9-abc2523cb27a_2856x2604.png&quot;}],&quot;caption&quot;:&quot;Header and CPP File - Threaded&quot;,&quot;alt&quot;:&quot;Header and CPP File - Threaded&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/95015232-d64e-4e3b-816f-dc2920847f29_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><h4>Understanding TFuture and TPromise</h4><p>At a high level, Futures provide the capability to call a function via the Next method after it has become fulfilled. This is convenient, because the function passed to Next will be called even in the case where the Future was fulfilled before Next was invoked.</p><p>Promises wrap a Future and provide the SetValue method to fulfill its internal Future.</p><h4>Understanding the Threaded ViewModel</h4><p>When DoALongRunningAsyncTask is called, the following happens:</p><ul><li><p>The Task is marked as running immediately so the UI can respond accordingly</p></li><li><p>DoALongRunningAsyncTaskInternal is called</p><ul><li><p>A SharedRef (non nullable version of a SharedPtr) of a Promise is created</p></li><li><p>This Promise is passed to an AsyncTask call via a lambda which will now hold a reference which will prevent it from falling out of scope and being deleted</p></li><li><p>The AsyncTask will start a thread sometime in the future that will:</p><ul><li><p>Sleep for 3 seconds</p></li><li><p>Fulfill the Future of the Promise that was passed in by incrementing the RunCount by 1</p></li></ul></li><li><p>The Promise&#8217;s Future is returned immediately</p></li></ul></li><li><p>Next is called on the returned Future and a continuation function is passed in for whenever the Future is fulfilled. This continuation function passes a lambda to be called back on the Game Thread that will:</p><ul><li><p>Mark the Task as completed by setting bIsTaskRunning to false</p></li><li><p>Increment the RunCount by the Result from the Future</p></li></ul></li></ul><p> The threaded ViewModel is hooked up to the following UI. This widgets adds bindings for an animated throbber that is visible when the task is running and disabling the Run button when the task is running.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AzWH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa05f2c4b-6041-4e41-b123-92b3101059b3_2867x1707.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AzWH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa05f2c4b-6041-4e41-b123-92b3101059b3_2867x1707.png 424w, https://substackcdn.com/image/fetch/$s_!AzWH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa05f2c4b-6041-4e41-b123-92b3101059b3_2867x1707.png 848w, https://substackcdn.com/image/fetch/$s_!AzWH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa05f2c4b-6041-4e41-b123-92b3101059b3_2867x1707.png 1272w, https://substackcdn.com/image/fetch/$s_!AzWH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa05f2c4b-6041-4e41-b123-92b3101059b3_2867x1707.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AzWH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa05f2c4b-6041-4e41-b123-92b3101059b3_2867x1707.png" width="1456" height="867" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a05f2c4b-6041-4e41-b123-92b3101059b3_2867x1707.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:867,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:246510,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AzWH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa05f2c4b-6041-4e41-b123-92b3101059b3_2867x1707.png 424w, https://substackcdn.com/image/fetch/$s_!AzWH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa05f2c4b-6041-4e41-b123-92b3101059b3_2867x1707.png 848w, https://substackcdn.com/image/fetch/$s_!AzWH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa05f2c4b-6041-4e41-b123-92b3101059b3_2867x1707.png 1272w, https://substackcdn.com/image/fetch/$s_!AzWH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa05f2c4b-6041-4e41-b123-92b3101059b3_2867x1707.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The following video demonstrates the threaded ViewModel in action</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;8a6a5171-ac7c-44c3-b90f-e0b4719932ba&quot;,&quot;duration&quot;:null}"></div><div><hr></div><h2>Conclusion</h2><p>AsyncTask, Promises, and Futures are a subset of tools that Unreal Engine provides to run asynchronous code. Interacting with the TaskGraph directly and the <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/tasks-systems-in-unreal-engine">Task System</a> are others. Expensive calls, such as interacting with a backend service or doing File I/O can be done on a thread other than the Game Thread to keep the UI and simulation smooth</p><p>Example code can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Source/JooballinUE/Private/AsyncUI">here</a>.</p><p>Example assets can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Content/JooballinUE/AsyncUI">here</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.jooballin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Eric Friedman&#8217;s Engineering Blog! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Unreal Engine - Data Validation]]></title><description><![CDATA[Write simpler and safer runtime code]]></description><link>https://www.jooballin.com/p/data-validation</link><guid isPermaLink="false">https://www.jooballin.com/p/data-validation</guid><dc:creator><![CDATA[Eric Friedman]]></dc:creator><pubDate>Thu, 18 Jul 2024 23:47:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!71V2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11cadbbc-5417-4421-89c0-dedff197ce5d_2748x2268.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2><p><a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/data-validation-in-unreal-engine?application_version=5.4">Data Validation</a> is a plugin to inform developers when assets are in an invalid state at edit and build time. This means that errors can be caught when developers are making changes and during a project&#8217;s build pipeline. This allows programmers to write simpler code and, more importantly, prevent bugs and crashes from showing up in front of players. </p><div><hr></div><h2>Example</h2><p>The following is a simplified example of an actor that contains a pointer to a data asset that the actor will use and be referenced at runtime.</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/11cadbbc-5417-4421-89c0-dedff197ce5d_2748x2268.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ab62be22-2828-4273-9e37-4bd7270b2fdd_2605x2772.png&quot;}],&quot;caption&quot;:&quot;Header and CPP File - Without Validation&quot;,&quot;alt&quot;:&quot;Header and CPP File - Without Validation&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/14181382-fd7d-4ab4-aefa-3ba8e0fb3847_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p></p><p>Since the DataAsset pointer isn&#8217;t guaranteed to be valid, it is essential to check it before it is accessed both in the IncreaseSpeed and BeginPlay functions. </p><p>Consider how this code could be simplified if the pointer to the data asset was guaranteed to always be valid. Here is how the same class looks with Data Validation.</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5b66917d-d233-461d-9f44-5262cce39b41_3537x2772.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2c1e920d-160d-45d2-bc9a-7955be313668_3967x3192.png&quot;}],&quot;caption&quot;:&quot;Header and CPP File - With Validation&quot;,&quot;alt&quot;:&quot;Header and CPP File - With Validation&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8088107a-ebaa-4f35-ba4b-c4758efaba8c_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p></p><p>By guaranteeing that the pointer to the internal Data Asset is valid at build time, the code is now future proofed against null pointer exceptions and has been simplified in the following ways:</p><ul><li><p>The Data Asset can be returned by reference, signaling to other developers that they are guaranteed a valid, non-null object</p></li><li><p>Accessing the Data Asset doesn&#8217;t require validity checking and assertions</p></li><li><p>There is only the code path where the Data Asset is valid and there is no need for error logging or error handling</p></li></ul><p>One thing to note is that care must be taken not to lose an invalid data validation result from a parent class. Don&#8217;t ever return EDataValidationResult::Valid from the subclass implementation of IsDataValid, but instead capture the result from the parent class and change it to EDataValidationResult::Invalid when needed. This will ensure that all invalid cases from parent classes are caught reported appropriately. </p><div><hr></div><h2>Data Validation in Action</h2><p>Now that there is an asset type that can be created that implements data validation, an instance can be created in the editor to demonstrate what happens when an asset is in an invalid state. </p><h4>Data Validation Errors in Editor</h4><p>The most common place Data Validation errors are going to show up is in editor when developers are creating new or editing existing assets. </p><p>Given an invalid Blueprint instance of ADataValidationAsset, data validation errors will present in the following three ways:</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c9dc4808-ac2a-4647-a613-0610b705b45a_3840x2040.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9f8d4a83-95c2-4da0-8078-7e0b00410f15_3840x2040.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0dca2c63-5fd6-48a2-8fcb-a8a20db8f99d_3840x2055.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7c905d27-12a0-4531-a257-d412e1337384_3840x2055.png&quot;}],&quot;caption&quot;:&quot;From Top Left going Clockwise: Compiling a Blueprint Result, Saving a Blueprint Result, How to run Data Validation on an Asset(s), and Data Validation Results&quot;,&quot;alt&quot;:&quot;From Top Left going Clockwise: Compiling a Blueprint Result, Saving a Blueprint Result, How to run Data Validation on an Asset(s), and Data Validation Results&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/438b8ca6-f634-479d-88b2-67d66171dfaa_1456x1456.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p></p><h4>Data Validation Errors via Commandlet</h4><p>Unreal Engine provides a commandlet to do a Data Validation pass on all of the assets in a project. This is especially helpful when:</p><ul><li><p>Adding new validation paths which may introduce validation errors to existing assets </p></li><li><p>Running in an automation pipeline to verify the integrity of a build during a Continuous Integration (CI) process.</p><p></p></li></ul><p>Assuming an installed version of Unreal Engine the Data Validation Commandlet can be run via a command line prompt, e.g. Powershell, the following way:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;44de0b10-a29d-4a36-aca4-0512d297a4e4&quot;,&quot;duration&quot;:null}"></div><div><hr></div><h2>Conclusion</h2><p>It is an engineering responsibility to guarantee the code is performant, free of bugs or crashes, and easy to understand. Data Validation is one tool that can help in those efforts.</p><p>This is a brief introduction to Data Validation. Check out the documentation <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/data-validation-in-unreal-engine">here</a> to see the other ways that it can be implemented, e.g. creating a validator that derives from UEditorValidatorBase.</p><p>Example code can be found <a href="https://github.com/EricLeeFriedman/JooballinBlog/tree/master/Source/JooballinUE/Private/DataValidation">here</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.jooballin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Eric Friedman&#8217;s Engineering Blog! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>