johnaustin_squarespace.rss.xml - sfeed_tests - sfeed tests and RSS and Atom files
 (HTM) git clone git://git.codemadness.org/sfeed_tests
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       johnaustin_squarespace.rss.xml (269909B)
       ---
            1 <?xml version="1.0" encoding="UTF-8"?>
            2 <!--Generated by Site-Server v@build.version@ (http://www.squarespace.com) on Sat, 18 Jan 2025 10:00:47 GMT
            3 --><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://www.rssboard.org/media-rss" version="2.0"><channel><title>John Austin</title><link>https://johnaustin.io/</link><lastBuildDate>Tue, 14 Jan 2025 04:17:02 +0000</lastBuildDate><language>en-US</language><generator>Site-Server v@build.version@ (http://www.squarespace.com)</generator><description><![CDATA[]]></description><item><title>Issues with Color Spaces and Perceptual Brightness</title><category>Article</category><category>Technical</category><dc:creator>John Austin</dc:creator><pubDate>Tue, 14 Jan 2025 04:56:21 +0000</pubDate><link>https://johnaustin.io/articles/2025/issues-with-cielab-and-perceptual-brightness</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:6785e53e87fad21239890f26</guid><description><![CDATA[<p class="">Unlike RGB, the <a href="https://en.wikipedia.org/wiki/CIELAB_color_space" target="_blank">CIELab</a> color space<a href="#margin" target="_blank">And the more modern variants like CIECAM02 and Oklab.</a> is designed to be perceptually uniform. This is a broad goal, which roughly means that we would like changes in the numbers to match same changes that we observe when performing various studies of human perception. </p><p class="">It’s a bit less 'math’ and more ‘modeling’. We try to fit equations to the observed data with the lowest error possible, across many different axis. It’s helpful to imagine adding the word ‘predicted’ in front of all of the CIELAB terminology (ie. Predicted Perceptual Lightness), because it’s just trying to predict human test results! </p><p class="">This process means that there is some error. For example, ideally, a color with L=50 looks twice as bright as a color with L=25.  Except, with very strongly saturated colors like red, this isn’t actually the case in any of these color spaces.</p><p class="">This is what’s called the <a href="https://en.wikipedia.org/wiki/Helmholtz%E2%80%93Kohlrausch_effect">Helmholtz-Kohlrausch effect</a>. Take a look at the below panel of colors. Most humans agree that the red is quite a bit brighter than the others in the row, and yet they all have the exact same lightness value when calculated with something like CIELAB!</p>
            4 
            5 
            6 
            7 
            8 
            9 
           10 
           11 
           12 
           13 
           14 
           15 
           16 
           17 
           18 
           19 
           20 
           21 
           22 
           23 
           24 
           25   
           26   
           27 
           28 
           29 
           30 
           31 
           32 
           33 
           34 
           35 
           36 
           37 
           38 
           39 
           40 
           41 
           42 
           43 
           44 
           45 
           46 
           47 
           48 
           49 
           50 
           51 
           52 
           53 
           54 
           55 
           56 
           57 
           58 
           59 
           60 
           61 
           62 
           63 
           64 
           65 
           66 
           67 
           68 
           69 
           70 
           71 
           72 
           73   
           74 
           75     
           76   
           77     
           78 
           79       
           80 
           81       
           82         <figure class="
           83               sqs-block-image-figure
           84               intrinsic
           85             "
           86         >
           87           
           88         
           89         
           90 
           91         
           92           
           93             
           94           
           95             
           96                 
           97                 
           98                 
           99                 
          100                 
          101                 
          102                 
          103                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/eaf70676-eb13-4d48-b0b1-7051995f3027/Helmholtz-Kohlrausch_effect_visualized_improved.png" data-image-dimensions="440x181" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/eaf70676-eb13-4d48-b0b1-7051995f3027/Helmholtz-Kohlrausch_effect_visualized_improved.png?format=1000w" width="440" height="181" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/eaf70676-eb13-4d48-b0b1-7051995f3027/Helmholtz-Kohlrausch_effect_visualized_improved.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/eaf70676-eb13-4d48-b0b1-7051995f3027/Helmholtz-Kohlrausch_effect_visualized_improved.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/eaf70676-eb13-4d48-b0b1-7051995f3027/Helmholtz-Kohlrausch_effect_visualized_improved.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/eaf70676-eb13-4d48-b0b1-7051995f3027/Helmholtz-Kohlrausch_effect_visualized_improved.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/eaf70676-eb13-4d48-b0b1-7051995f3027/Helmholtz-Kohlrausch_effect_visualized_improved.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/eaf70676-eb13-4d48-b0b1-7051995f3027/Helmholtz-Kohlrausch_effect_visualized_improved.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/eaf70676-eb13-4d48-b0b1-7051995f3027/Helmholtz-Kohlrausch_effect_visualized_improved.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
          104 
          105             
          106           
          107         
          108           
          109         
          110 
          111         
          112           
          113           <figcaption class="image-caption-wrapper">
          114             <p class="">A set of colors demonstrating the Helmholtz-Kohlrausch effect. The red appears more vivid to the human eye.</p>
          115           </figcaption>
          116         
          117       
          118         </figure>
          119       
          120 
          121     
          122   
          123 
          124 
          125   
          126 
          127 
          128 
          129 
          130 
          131   <p class="">There is some recent <a href="https://onlinelibrary.wiley.com/doi/10.1002/col.22839" target="_blank">research</a> I found that models this effect and applies an additional transform to account for it, building off of work from <a href="https://onlinelibrary.wiley.com/doi/abs/10.1002/col.5080160608" target="_blank">Fairchild MD, Pirrotta E</a> in the 90’s. The end result is labeled as the “Predicted Equivalent Achromatic Lightness”, or in other words “The lightness of the gray that most closely matches the perceived lightness”. </p><p class="">This is actually quite a useful value, because it’s precisely the value we want when desaturating an image! Most desaturation operations spit out a [0..1] value which is then (later) rendered as an RGB gray color. Using the L_EAL value instead jumps us right to the end, giving the exact perceptual gray color to place in the resulting desaturated image.</p><p class="">How much of an issue is this? Well, I noticed. I’ve been writing a tool to desaturate game screenshots, to help us evaluate relative brightnesses between art assets, and improve overall game readability. All of the red assets appeared.. strangely dark in the desaturated images, even when using CIELAB!</p>
          132 
          133 
          134 
          135 
          136 
          137 
          138 
          139 
          140 
          141 
          142 
          143 
          144 
          145 
          146 
          147 
          148 
          149 
          150 
          151 
          152 
          153   
          154   
          155 
          156 
          157 
          158 
          159 
          160 
          161 
          162 
          163 
          164 
          165 
          166 
          167 
          168 
          169 
          170 
          171 
          172 
          173 
          174 
          175 
          176 
          177 
          178 
          179 
          180 
          181 
          182 
          183 
          184 
          185 
          186 
          187 
          188 
          189 
          190 
          191 
          192 
          193 
          194 
          195 
          196 
          197 
          198 
          199 
          200 
          201   
          202 
          203     
          204   
          205     
          206 
          207       
          208 
          209       
          210         <figure class="
          211               sqs-block-image-figure
          212               intrinsic
          213             "
          214         >
          215           
          216         
          217         
          218 
          219         
          220           
          221             
          222           
          223             
          224                 
          225                 
          226                 
          227                 
          228                 
          229                 
          230                 
          231                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c6de87d7-0d4a-4c3e-b748-632d6ed661a3/Screenshot+2025-01-13+at+8.51.36%E2%80%AFPM.png" data-image-dimensions="1624x1458" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c6de87d7-0d4a-4c3e-b748-632d6ed661a3/Screenshot+2025-01-13+at+8.51.36%E2%80%AFPM.png?format=1000w" width="1624" height="1458" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c6de87d7-0d4a-4c3e-b748-632d6ed661a3/Screenshot+2025-01-13+at+8.51.36%E2%80%AFPM.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c6de87d7-0d4a-4c3e-b748-632d6ed661a3/Screenshot+2025-01-13+at+8.51.36%E2%80%AFPM.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c6de87d7-0d4a-4c3e-b748-632d6ed661a3/Screenshot+2025-01-13+at+8.51.36%E2%80%AFPM.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c6de87d7-0d4a-4c3e-b748-632d6ed661a3/Screenshot+2025-01-13+at+8.51.36%E2%80%AFPM.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c6de87d7-0d4a-4c3e-b748-632d6ed661a3/Screenshot+2025-01-13+at+8.51.36%E2%80%AFPM.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c6de87d7-0d4a-4c3e-b748-632d6ed661a3/Screenshot+2025-01-13+at+8.51.36%E2%80%AFPM.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c6de87d7-0d4a-4c3e-b748-632d6ed661a3/Screenshot+2025-01-13+at+8.51.36%E2%80%AFPM.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
          232 
          233             
          234           
          235         
          236           
          237         
          238 
          239         
          240           
          241           <figcaption class="image-caption-wrapper">
          242             <p class="">Before desaturating.</p>
          243           </figcaption>
          244         
          245       
          246         </figure>
          247       
          248 
          249     
          250   
          251 
          252 
          253   
          254 
          255 
          256 
          257 
          258 
          259 
          260 
          261 
          262 
          263 
          264 
          265 
          266 
          267 
          268 
          269 
          270 
          271 
          272 
          273 
          274 
          275 
          276 
          277 
          278 
          279 
          280 
          281 
          282 
          283 
          284 
          285 
          286 
          287 
          288 
          289 
          290 
          291 
          292 
          293 
          294 
          295 
          296 
          297 
          298 
          299   
          300 
          301     
          302   
          303     
          304 
          305       
          306 
          307       
          308         <figure class="
          309               sqs-block-image-figure
          310               intrinsic
          311             "
          312         >
          313           
          314         
          315         
          316 
          317         
          318           
          319             
          320           
          321             
          322                 
          323                 
          324                 
          325                 
          326                 
          327                 
          328                 
          329                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/8c11be87-6530-4af8-94ab-e78a142a2927/Screenshot+2025-01-13+at+8.51.42%E2%80%AFPM.png" data-image-dimensions="1374x1468" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/8c11be87-6530-4af8-94ab-e78a142a2927/Screenshot+2025-01-13+at+8.51.42%E2%80%AFPM.png?format=1000w" width="1374" height="1468" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/8c11be87-6530-4af8-94ab-e78a142a2927/Screenshot+2025-01-13+at+8.51.42%E2%80%AFPM.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/8c11be87-6530-4af8-94ab-e78a142a2927/Screenshot+2025-01-13+at+8.51.42%E2%80%AFPM.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/8c11be87-6530-4af8-94ab-e78a142a2927/Screenshot+2025-01-13+at+8.51.42%E2%80%AFPM.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/8c11be87-6530-4af8-94ab-e78a142a2927/Screenshot+2025-01-13+at+8.51.42%E2%80%AFPM.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/8c11be87-6530-4af8-94ab-e78a142a2927/Screenshot+2025-01-13+at+8.51.42%E2%80%AFPM.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/8c11be87-6530-4af8-94ab-e78a142a2927/Screenshot+2025-01-13+at+8.51.42%E2%80%AFPM.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/8c11be87-6530-4af8-94ab-e78a142a2927/Screenshot+2025-01-13+at+8.51.42%E2%80%AFPM.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
          330 
          331             
          332           
          333         
          334           
          335         
          336 
          337         
          338           
          339           <figcaption class="image-caption-wrapper">
          340             <p class="">After desaturating.</p>
          341           </figcaption>
          342         
          343       
          344         </figure>
          345       
          346 
          347     
          348   
          349 
          350 
          351   
          352 
          353 
          354 
          355 
          356 
          357   <p class="">The reds are a bit darker here, but I think we can agree that they’re not that dark! This is because CIELAB does not account for the Helmholtz-Kohlrausch effect and so it undervalues the amount that red’s saturation contributes to the final lightness. And if we had used this tool to spot check, it might have caused us to make all of our red assets too bright to balance it out. Weird.</p><p class=""><br>Unfortunately, I haven’t been able to find any perceptually uniform color spaces that seem to include these transformations in the final output space. If you’re aware of one, I would love to know.</p>
          358 
          359 
          360 
          361 
          362 
          363 
          364 
          365 
          366 
          367 
          368 
          369 
          370 
          371 
          372 
          373 
          374 
          375 
          376 
          377 
          378 
          379   
          380   
          381 
          382 
          383 
          384 <hr />
          385 
          386 
          387   <p class=""><em>Update: Several folks on HackerNews had some good suggestions for better desaturation models.</em></p><p class="">The first is OSA-UCS or the Darktable UCS. This was designed for photo manipulation and there is a fantastic research explanation here: <a href="https://eng.aurelienpierre.com/2022/02/color-saturation-control-for-the-21th-century/#fn:11">https://eng.aurelienpierre.com/2022/02/color-saturation-control-for-the-21th-century/#fn:11</a></p><p class="">Second is Color2Gray. This tool goes further than any others, considering <em>the contextual surrounding</em> of each pixel and solves an optimization problem, to determine a sensible gray color that retains color differentials in the original image. This is not precisely desaturation, but may do a good job if that’s your goal.</p>]]></description></item><item><title>Composability: Designing a Visual Programming Language</title><category>Technical</category><dc:creator>John Austin</dc:creator><pubDate>Sat, 20 Apr 2024 22:52:29 +0000</pubDate><link>https://johnaustin.io/articles/2024/composability-designing-a-visual-programming-language</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:66243a9b5b6abb3c12bd039d</guid><description><![CDATA[<p class=""><em>Lattice is a high-performance visual scripting system targeting Unity ECS. Read more </em><a href="https://forum.unity.com/threads/lattice-visual-scripting-for-ecs.1508402/"><em>here</em></a><em>.</em></p><p class="">I wanted to write a few posts on the design of Lattice as a language. Today, let's focus on “composability”. This is intuitively something we desire in programming languages. Some systems feel like they are effortlessly reconfigurable, recombinable, and then others.. just don’t. Some languages seem to actively reject our efforts at organization.</p><p class="">So what makes a system composable? I see two major properties:</p><ol data-rte-list="default"><li><p class=""><strong>Self-Similarity</strong> — Composable systems usually have a simple primitive that is self-similar. Think of a game grid, tetris pieces, repeating tessellations, etc.&nbsp; Self-similarity means most things can plug into other things. They may not do something <em>useful,</em> but they can be infinitely re-arranged.</p></li><li><p class=""><strong>Merging / Splitting</strong> — Two groups of primitives can be <strong>merged</strong> into a single primitive, and that 'merged primitive' acts just like any other primitive. Similarly, a primitive can be <strong>split</strong> into several 'pieces' and each of those pieces acts like any other primitive.</p></li></ol><p class="">Take Legos, perhaps the most composable system in existence. So composable, in fact, that talking about them is almost reductive. But they follow these rules deeply:</p>
          388 
          389 
          390 
          391 
          392 
          393 
          394 
          395 
          396 
          397 
          398 
          399 
          400 
          401 
          402 
          403 
          404 
          405 
          406 
          407 
          408 
          409   
          410   
          411 
          412 
          413 
          414 
          415 
          416 
          417 
          418 
          419 
          420 
          421 
          422 
          423 
          424 
          425 
          426 
          427 
          428 
          429 
          430 
          431 
          432 
          433 
          434 
          435 
          436 
          437 
          438 
          439 
          440 
          441 
          442 
          443 
          444 
          445 
          446 
          447 
          448 
          449 
          450 
          451 
          452 
          453 
          454 
          455 
          456 
          457   
          458 
          459     
          460   
          461     
          462 
          463       
          464 
          465       
          466         <figure class="
          467               sqs-block-image-figure
          468               intrinsic
          469             "
          470         >
          471           
          472         
          473         
          474 
          475         
          476           
          477             
          478           
          479             
          480                 
          481                 
          482                 
          483                 
          484                 
          485                 
          486                 
          487                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/3e725a3f-1c4a-4379-a506-bcaaa02a98a3/Untitled-2024-02-08-1201%2819%29.png" data-image-dimensions="1923x1244" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/3e725a3f-1c4a-4379-a506-bcaaa02a98a3/Untitled-2024-02-08-1201%2819%29.png?format=1000w" width="1923" height="1244" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/3e725a3f-1c4a-4379-a506-bcaaa02a98a3/Untitled-2024-02-08-1201%2819%29.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/3e725a3f-1c4a-4379-a506-bcaaa02a98a3/Untitled-2024-02-08-1201%2819%29.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/3e725a3f-1c4a-4379-a506-bcaaa02a98a3/Untitled-2024-02-08-1201%2819%29.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/3e725a3f-1c4a-4379-a506-bcaaa02a98a3/Untitled-2024-02-08-1201%2819%29.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/3e725a3f-1c4a-4379-a506-bcaaa02a98a3/Untitled-2024-02-08-1201%2819%29.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/3e725a3f-1c4a-4379-a506-bcaaa02a98a3/Untitled-2024-02-08-1201%2819%29.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/3e725a3f-1c4a-4379-a506-bcaaa02a98a3/Untitled-2024-02-08-1201%2819%29.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
          488 
          489             
          490           
          491         
          492           
          493         
          494 
          495         
          496       
          497         </figure>
          498       
          499 
          500     
          501   
          502 
          503 
          504   
          505 
          506 
          507 
          508 
          509 
          510   <p class="">Notice how not only can Legos be split into separate pieces, but they can be split in <span>many different ways</span>. Put another way, Legos have <strong>lots of seams</strong>: places where they can be split apart into component chunks.</p>
          511 
          512 
          513 
          514 
          515 
          516 
          517 
          518 
          519 
          520 
          521 
          522 
          523 
          524 
          525 
          526 
          527 
          528 
          529 
          530 
          531 
          532   
          533   
          534 
          535 
          536 
          537 
          538 
          539 
          540 
          541 
          542 
          543 
          544 
          545 
          546 
          547 
          548 
          549 
          550 
          551 
          552 
          553 
          554 
          555 
          556 
          557 
          558 
          559 
          560 
          561 
          562 
          563 
          564 
          565 
          566 
          567 
          568 
          569 
          570 
          571 
          572 
          573 
          574 
          575 
          576 
          577 
          578 
          579 
          580   
          581 
          582     
          583   
          584     
          585 
          586       
          587 
          588       
          589         <figure class="
          590               sqs-block-image-figure
          591               intrinsic
          592             "
          593         >
          594           
          595         
          596         
          597 
          598         
          599           
          600             
          601           
          602             
          603                 
          604                 
          605                 
          606                 
          607                 
          608                 
          609                 
          610                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c91825a1-af75-422d-b906-6e545f1be831/Untitled-2024-02-08-1201%2820%29.png" data-image-dimensions="1974x1310" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c91825a1-af75-422d-b906-6e545f1be831/Untitled-2024-02-08-1201%2820%29.png?format=1000w" width="1974" height="1310" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c91825a1-af75-422d-b906-6e545f1be831/Untitled-2024-02-08-1201%2820%29.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c91825a1-af75-422d-b906-6e545f1be831/Untitled-2024-02-08-1201%2820%29.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c91825a1-af75-422d-b906-6e545f1be831/Untitled-2024-02-08-1201%2820%29.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c91825a1-af75-422d-b906-6e545f1be831/Untitled-2024-02-08-1201%2820%29.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c91825a1-af75-422d-b906-6e545f1be831/Untitled-2024-02-08-1201%2820%29.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c91825a1-af75-422d-b906-6e545f1be831/Untitled-2024-02-08-1201%2820%29.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c91825a1-af75-422d-b906-6e545f1be831/Untitled-2024-02-08-1201%2820%29.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
          611 
          612             
          613           
          614         
          615           
          616         
          617 
          618         
          619           
          620           <figcaption class="image-caption-wrapper">
          621             <p class="">Lego chunks can be split along many different seams.</p>
          622           </figcaption>
          623         
          624       
          625         </figure>
          626       
          627 
          628     
          629   
          630 
          631 
          632   
          633 
          634 
          635 
          636 
          637 
          638   <p class="">A system with many seams is indicative of a very composable system: new behavior is built purely through composition, not by defining new primitives.</p><p class="">I think about composability a lot in my game design work, why do some game mechanics have so much emergent behavior? We can see splitting/merging at work there too. Factorio, with its endlessly recombinable and splittable factory tiles. Or a roguelike deck-builders with their infinitely splittable/mergeable decks. Or an action game with 20 different abilities giving you an incredible number of seams to rip apart builds and reconfigure them.</p><h2>Blueprints are not composable.</h2>
          639 
          640 
          641 
          642 
          643 
          644 
          645 
          646 
          647 
          648 
          649 
          650 
          651 
          652 
          653 
          654 
          655 
          656 
          657 
          658 
          659 
          660   
          661   
          662 
          663 
          664 
          665 <p>Let's get back to Lattice, and visual programming language design. Unreal Engine’s Blueprints are a common design for visual programming, roughly mirroring code. But despite how easy it is to sketch something out, they seem to reject organization.<footnote data-preserve-html-node="true">Common wisdom from large teams in UE is “design in blueprints, then rewrite in C++”.</footnote> It's shockingly tough to split a messy graph into reusable, smaller nodes. It's also pretty tough to merge several nodes into a single multi-purpose node. Why is that?</p>
          666 
          667 
          668 
          669 
          670   <p class="">In my opinion, the problem is execution wires.</p><p class="">Execution wires don't have a trivial merge operation. Let's say I have two nodes I want to combine into a single node:</p>
          671 
          672 
          673 
          674 
          675 
          676 
          677 
          678 
          679 
          680 
          681 
          682 
          683 
          684 
          685 
          686 
          687 
          688 
          689 
          690 
          691 
          692   
          693   
          694 
          695 
          696 
          697 
          698 
          699 
          700 
          701 
          702 
          703 
          704 
          705 
          706 
          707 
          708 
          709 
          710 
          711 
          712 
          713 
          714 
          715 
          716 
          717 
          718 
          719 
          720 
          721 
          722 
          723 
          724 
          725 
          726 
          727 
          728 
          729 
          730 
          731 
          732 
          733 
          734 
          735 
          736 
          737 
          738 
          739 
          740   
          741 
          742     
          743   
          744     
          745 
          746       
          747 
          748       
          749         <figure class="
          750               sqs-block-image-figure
          751               intrinsic
          752             "
          753         >
          754           
          755         
          756         
          757 
          758         
          759           
          760             
          761           
          762             
          763                 
          764                 
          765                 
          766                 
          767                 
          768                 
          769                 
          770                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/193b73ab-d8e3-49bf-bc3a-b6f54de02276/Untitled-2024-02-08-1201%2810%29.png" data-image-dimensions="719x516" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/193b73ab-d8e3-49bf-bc3a-b6f54de02276/Untitled-2024-02-08-1201%2810%29.png?format=1000w" width="719" height="516" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/193b73ab-d8e3-49bf-bc3a-b6f54de02276/Untitled-2024-02-08-1201%2810%29.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/193b73ab-d8e3-49bf-bc3a-b6f54de02276/Untitled-2024-02-08-1201%2810%29.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/193b73ab-d8e3-49bf-bc3a-b6f54de02276/Untitled-2024-02-08-1201%2810%29.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/193b73ab-d8e3-49bf-bc3a-b6f54de02276/Untitled-2024-02-08-1201%2810%29.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/193b73ab-d8e3-49bf-bc3a-b6f54de02276/Untitled-2024-02-08-1201%2810%29.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/193b73ab-d8e3-49bf-bc3a-b6f54de02276/Untitled-2024-02-08-1201%2810%29.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/193b73ab-d8e3-49bf-bc3a-b6f54de02276/Untitled-2024-02-08-1201%2810%29.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
          771 
          772             
          773           
          774         
          775           
          776         
          777 
          778         
          779       
          780         </figure>
          781       
          782 
          783     
          784   
          785 
          786 
          787   
          788 
          789 
          790 
          791 
          792 
          793 
          794 
          795 
          796 
          797 
          798 
          799 
          800 
          801 
          802 
          803 
          804 
          805 
          806 
          807 
          808 
          809 
          810 
          811 
          812 
          813 
          814 
          815 
          816 
          817 
          818 
          819 
          820 
          821 
          822 
          823 
          824 
          825 
          826 
          827 
          828 
          829 
          830 
          831 
          832 
          833   
          834 
          835     
          836   
          837     
          838 
          839       
          840 
          841       
          842         <figure class="
          843               sqs-block-image-figure
          844               intrinsic
          845             "
          846         >
          847           
          848         
          849         
          850 
          851         
          852           
          853             
          854           
          855             
          856                 
          857                 
          858                 
          859                 
          860                 
          861                 
          862                 
          863                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/2c514df8-5857-4f3d-9797-7499dde8c12d/Untitled-2024-02-08-1201%288%29.png" data-image-dimensions="716x634" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/2c514df8-5857-4f3d-9797-7499dde8c12d/Untitled-2024-02-08-1201%288%29.png?format=1000w" width="716" height="634" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/2c514df8-5857-4f3d-9797-7499dde8c12d/Untitled-2024-02-08-1201%288%29.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/2c514df8-5857-4f3d-9797-7499dde8c12d/Untitled-2024-02-08-1201%288%29.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/2c514df8-5857-4f3d-9797-7499dde8c12d/Untitled-2024-02-08-1201%288%29.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/2c514df8-5857-4f3d-9797-7499dde8c12d/Untitled-2024-02-08-1201%288%29.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/2c514df8-5857-4f3d-9797-7499dde8c12d/Untitled-2024-02-08-1201%288%29.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/2c514df8-5857-4f3d-9797-7499dde8c12d/Untitled-2024-02-08-1201%288%29.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/2c514df8-5857-4f3d-9797-7499dde8c12d/Untitled-2024-02-08-1201%288%29.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
          864 
          865             
          866           
          867         
          868           
          869         
          870 
          871         
          872       
          873         </figure>
          874       
          875 
          876     
          877   
          878 
          879 
          880   
          881 
          882 
          883 <p>What do you do about the execution wires? It's illegal to split or merge an execution wire. Doing so would require a fundamentally parallel execution environment.<footnote data-preserve-html-node="true">It’s fun to muse on a language where splitting an execution wire means “run these operations in parallel”. It’s a bit of a can of worms, though, that PL folks have been attempting for decades.</footnote> You must manually order them, instead. But which one should execute first? Even if it doesn’t actually matter, the semantics of the language require you to pick an option.</p>
          884 
          885 &nbsp;
          886 
          887 
          888 
          889 
          890 
          891 
          892 
          893 
          894 
          895 
          896 
          897 
          898 
          899 
          900 
          901 
          902 
          903 
          904 
          905 
          906 
          907 
          908 
          909 
          910 
          911 
          912 
          913 
          914 
          915 
          916 
          917 
          918 
          919 
          920 
          921 
          922 
          923 
          924 
          925 
          926 
          927 
          928   
          929 
          930     
          931   
          932     
          933 
          934       
          935 
          936       
          937         <figure class="
          938               sqs-block-image-figure
          939               intrinsic
          940             "
          941         >
          942           
          943         
          944         
          945 
          946         
          947           
          948             
          949               
          950               
          951           
          952             
          953                 
          954                 
          955                 
          956                 
          957                 
          958                 
          959                 
          960                 <img data-stretch="true" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/273559e6-9355-419d-853a-17f721ad2477/Untitled-2024-02-08-1201%284%29.png" data-image-dimensions="945x626" data-image-focal-point="0.4894516363728049,0.24" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/273559e6-9355-419d-853a-17f721ad2477/Untitled-2024-02-08-1201%284%29.png?format=1000w" width="945" height="626" sizes="(max-width: 640px) 100vw, (max-width: 767px) 75vw, 75vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/273559e6-9355-419d-853a-17f721ad2477/Untitled-2024-02-08-1201%284%29.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/273559e6-9355-419d-853a-17f721ad2477/Untitled-2024-02-08-1201%284%29.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/273559e6-9355-419d-853a-17f721ad2477/Untitled-2024-02-08-1201%284%29.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/273559e6-9355-419d-853a-17f721ad2477/Untitled-2024-02-08-1201%284%29.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/273559e6-9355-419d-853a-17f721ad2477/Untitled-2024-02-08-1201%284%29.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/273559e6-9355-419d-853a-17f721ad2477/Untitled-2024-02-08-1201%284%29.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/273559e6-9355-419d-853a-17f721ad2477/Untitled-2024-02-08-1201%284%29.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
          961 
          962             
          963           
          964         
          965             
          966           
          967         
          968 
          969         
          970           
          971           <figcaption class="image-caption-wrapper">
          972             <p class="">Does thing 2 or thing 1 happen first? Does it matter?</p>
          973           </figcaption>
          974         
          975       
          976         </figure>
          977       
          978 
          979     
          980   
          981 
          982 
          983   
          984 
          985 
          986 &nbsp;<p>In this situation, Blueprints just gives up, exposing two execution wire inputs — the worst of all worlds. The issue here isn't Blueprints, itself. Execution wires are just a fundamentally non-composable primitive.<footnote data-preserve-html-node="true">Foot note:<br data-preserve-html-node="true">Why does code work better?<br data-preserve-html-node="true"><br data-preserve-html-node="true">It's interesting to ask why text code doesn't seem to have this problem. After all, C++ has 'execution wires', so to speak, between each line, but doesn't have these compositional issues. However, the execution wires in text code are 'implicit' -- it's based on the linear order of the text in the file. Every time you make an edit, you're answering the ordering question.<br data-preserve-html-node="true"><br data-preserve-html-node="true">With a visual programming language lifted to a 2D plane, we can't rely on that luxury because there is no implicit linear order of nodes. Of course, we can implicitly order nodes in other ways.</footnote></p>
          987 
          988 
          989 
          990 
          991 
          992 
          993 
          994 
          995 
          996 
          997 
          998 
          999 
         1000 
         1001 
         1002 
         1003 
         1004 
         1005 
         1006 
         1007 
         1008 
         1009 
         1010 
         1011 
         1012 
         1013 
         1014 
         1015 
         1016 
         1017 
         1018 
         1019 
         1020 
         1021 
         1022 
         1023 
         1024 
         1025 
         1026 
         1027 
         1028 
         1029 
         1030 
         1031   
         1032 
         1033     
         1034   
         1035     
         1036 
         1037       
         1038 
         1039       
         1040         <figure class="
         1041               sqs-block-image-figure
         1042               intrinsic
         1043             "
         1044         >
         1045           
         1046         
         1047         
         1048 
         1049         
         1050           
         1051             
         1052           
         1053             
         1054                 
         1055                 
         1056                 
         1057                 
         1058                 
         1059                 
         1060                 
         1061                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/a9618c39-05f9-428d-9faa-ff8447a7b86a/UnrealEditor_Niv5KzZ2No.png" data-image-dimensions="419x450" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/a9618c39-05f9-428d-9faa-ff8447a7b86a/UnrealEditor_Niv5KzZ2No.png?format=1000w" width="419" height="450" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/a9618c39-05f9-428d-9faa-ff8447a7b86a/UnrealEditor_Niv5KzZ2No.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/a9618c39-05f9-428d-9faa-ff8447a7b86a/UnrealEditor_Niv5KzZ2No.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/a9618c39-05f9-428d-9faa-ff8447a7b86a/UnrealEditor_Niv5KzZ2No.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/a9618c39-05f9-428d-9faa-ff8447a7b86a/UnrealEditor_Niv5KzZ2No.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/a9618c39-05f9-428d-9faa-ff8447a7b86a/UnrealEditor_Niv5KzZ2No.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/a9618c39-05f9-428d-9faa-ff8447a7b86a/UnrealEditor_Niv5KzZ2No.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/a9618c39-05f9-428d-9faa-ff8447a7b86a/UnrealEditor_Niv5KzZ2No.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         1062 
         1063             
         1064           
         1065         
         1066           
         1067         
         1068 
         1069         
         1070           
         1071           <figcaption class="image-caption-wrapper">
         1072             <p class="">Before merging.</p>
         1073           </figcaption>
         1074         
         1075       
         1076         </figure>
         1077       
         1078 
         1079     
         1080   
         1081 
         1082 
         1083   
         1084 
         1085 
         1086 
         1087 
         1088 
         1089 
         1090 
         1091 
         1092 
         1093 
         1094 
         1095 
         1096 
         1097 
         1098 
         1099 
         1100 
         1101 
         1102 
         1103 
         1104 
         1105 
         1106 
         1107 
         1108 
         1109 
         1110 
         1111 
         1112 
         1113 
         1114 
         1115 
         1116 
         1117 
         1118 
         1119 
         1120 
         1121 
         1122 
         1123 
         1124 
         1125 
         1126 
         1127 
         1128 
         1129   
         1130 
         1131     
         1132   
         1133     
         1134 
         1135       
         1136 
         1137       
         1138         <figure class="
         1139               sqs-block-image-figure
         1140               intrinsic
         1141             "
         1142         >
         1143           
         1144         
         1145         
         1146 
         1147         
         1148           
         1149             
         1150           
         1151             
         1152                 
         1153                 
         1154                 
         1155                 
         1156                 
         1157                 
         1158                 
         1159                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/35cbdede-3f9f-4142-8f09-138571d475d7/UnrealEditor_7B4GtkzKby.png" data-image-dimensions="362x273" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/35cbdede-3f9f-4142-8f09-138571d475d7/UnrealEditor_7B4GtkzKby.png?format=1000w" width="362" height="273" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/35cbdede-3f9f-4142-8f09-138571d475d7/UnrealEditor_7B4GtkzKby.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/35cbdede-3f9f-4142-8f09-138571d475d7/UnrealEditor_7B4GtkzKby.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/35cbdede-3f9f-4142-8f09-138571d475d7/UnrealEditor_7B4GtkzKby.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/35cbdede-3f9f-4142-8f09-138571d475d7/UnrealEditor_7B4GtkzKby.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/35cbdede-3f9f-4142-8f09-138571d475d7/UnrealEditor_7B4GtkzKby.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/35cbdede-3f9f-4142-8f09-138571d475d7/UnrealEditor_7B4GtkzKby.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/35cbdede-3f9f-4142-8f09-138571d475d7/UnrealEditor_7B4GtkzKby.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         1160 
         1161             
         1162           
         1163         
         1164           
         1165         
         1166 
         1167         
         1168           
         1169           <figcaption class="image-caption-wrapper">
         1170             <p class="">After merging.</p>
         1171           </figcaption>
         1172         
         1173       
         1174         </figure>
         1175       
         1176 
         1177     
         1178   
         1179 
         1180 
         1181   
         1182 
         1183 
         1184 
         1185 
         1186 
         1187   <h2>A Better Option - Value Graphs</h2><p class="">What happens if we design a language without execution wires? Actually, there's plenty of precedent for this already:</p><ul data-rte-list="default"><li><p class=""><a href="https://www.youtube.com/watch?v=cWuvb_lunJI">Houdini</a> / Blender <a href="https://www.youtube.com/watch?v=aO0eUnu0hO0">Geometry Nodes</a> (Mesh Manipulation Tools)</p></li><li><p class=""><a href="https://visualprogramming.net/">VVVV</a>, <a href="https://derivative.ca/">TouchDesigner</a>, <a href="https://cables.gl/">Cables</a> (Live Effects Tools)</p></li><li><p class=""><a href="https://cycling74.com/products/max">MaxMSP</a>, <a href="https://puredata.info/">Pure Data</a> (Audio Synthesizers)</p></li><li><p class=""><a href="https://docs.unity3d.com/Packages/com.unity.shadergraph@17.0/manual/index.html">ShaderGraph</a>, <a href="https://docs.unrealengine.com/4.27/en-US/AnimatingObjects/SkeletalMeshAnimation/AnimBlueprints/AnimGraph/">Animation Blueprints</a> (Shaders, Animations)</p></li></ul>
         1188 
         1189 
         1190 
         1191 
         1192 
         1193 
         1194 
         1195 
         1196 
         1197 
         1198 
         1199 
         1200 
         1201 
         1202 
         1203 
         1204 
         1205 
         1206 
         1207 
         1208 
         1209   
         1210   
         1211 
         1212 
         1213 
         1214 
         1215 
         1216 
         1217 
         1218 
         1219   
         1220     
         1221       
         1222 
         1223         
         1224 
         1225         
         1226           
         1227             
         1228               <img class="thumb-image" elementtiming="system-gallery-block-slider" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1713651469462-F1LE8BBLGFXNIYTAHAAY/Untitled+picture.jpg" data-image-dimensions="1200x725" data-image-focal-point="0.5,0.5" alt="Blender Geometry Nodes" data-load="false" data-image-id="66243f0d6b680d3fa2c78144" data-type="image" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1713651469462-F1LE8BBLGFXNIYTAHAAY/Untitled+picture.jpg?format=1000w" /><br>
         1229             
         1230           
         1231           
         1232         
         1233 
         1234         
         1235 
         1236       
         1237 
         1238         
         1239 
         1240         
         1241           
         1242             
         1243               <img class="thumb-image" elementtiming="system-gallery-block-slider" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1713651469512-9JUEN65HJJ2O6COX0WL1/Untitled+picture.png" data-image-dimensions="564x610" data-image-focal-point="0.5,0.5" alt="Houdini" data-load="false" data-image-id="66243f0dbf77681507fcc7d1" data-type="image" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1713651469512-9JUEN65HJJ2O6COX0WL1/Untitled+picture.png?format=1000w" /><br>
         1244             
         1245           
         1246           
         1247         
         1248 
         1249         
         1250 
         1251       
         1252     
         1253   
         1254 
         1255   
         1256     
         1257     
         1258     
         1259       
         1260       
         1261         
         1262           <a tabindex="0" role="button" class="previous" aria-label="Previous Slide"
         1263           ></a>
         1264           <a tabindex="0" role="button" class="next" aria-label="Next Slide"
         1265           ></a>
         1266         
         1267       
         1268     
         1269     
         1270      
         1271   
         1272 
         1273 
         1274 
         1275 
         1276 
         1277 
         1278 
         1279 
         1280   
         1281   
         1282 
         1283 
         1284 
         1285 
         1286   <p class="">All of these languages are value-based expression graphs under the hood. You're applying operations to input <strong>values</strong>, to produce certain output <strong>values</strong>, sometimes with some additional side effects.</p><p class="">If there's no execution wires, how do you control the ordering of execution? Usually, you don't have to. When a node precisely defines the data it requires, the order of execution is implicit. When does a node run? After all its inputs have finished.</p><p class="">Blueprints actually has this in the form of Pure nodes. No surprise, these nodes are much easier to combine and reuse throughout your graphs. <a href="#margin" target="_blank">Sadly, most BP nodes are not pure, so the functionality is limited in its usefulness.</a></p><p class="">These are fundamentally composable! It's perfectly valid to split an incoming value wire (it's just a copy of the same value on either end).</p>
         1287 
         1288 
         1289 
         1290 
         1291 
         1292 
         1293 
         1294 
         1295 
         1296 
         1297 
         1298 
         1299 
         1300 
         1301 
         1302 
         1303 
         1304 
         1305 
         1306 
         1307 
         1308   
         1309   
         1310 
         1311 
         1312 
         1313 &nbsp;
         1314 
         1315 
         1316 
         1317 
         1318 
         1319 
         1320 
         1321 
         1322 
         1323 
         1324 
         1325 
         1326 
         1327 
         1328 
         1329 
         1330 
         1331 
         1332 
         1333 
         1334 
         1335 
         1336 
         1337 
         1338 
         1339 
         1340 
         1341 
         1342 
         1343 
         1344 
         1345 
         1346 
         1347 
         1348 
         1349 
         1350 
         1351 
         1352 
         1353 
         1354 
         1355 
         1356   
         1357 
         1358     
         1359   
         1360     
         1361 
         1362       
         1363 
         1364       
         1365         <figure class="
         1366               sqs-block-image-figure
         1367               intrinsic
         1368             "
         1369         >
         1370           
         1371         
         1372         
         1373 
         1374         
         1375           
         1376             
         1377           
         1378             
         1379                 
         1380                 
         1381                 
         1382                 
         1383                 
         1384                 
         1385                 
         1386                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/4c99aa82-a683-4800-830f-c558dd22a318/Untitled-2024-02-08-1201%2811%29.png" data-image-dimensions="1240x1045" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/4c99aa82-a683-4800-830f-c558dd22a318/Untitled-2024-02-08-1201%2811%29.png?format=1000w" width="1240" height="1045" sizes="(max-width: 640px) 100vw, (max-width: 767px) 83.33333333333334vw, 83.33333333333334vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/4c99aa82-a683-4800-830f-c558dd22a318/Untitled-2024-02-08-1201%2811%29.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/4c99aa82-a683-4800-830f-c558dd22a318/Untitled-2024-02-08-1201%2811%29.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/4c99aa82-a683-4800-830f-c558dd22a318/Untitled-2024-02-08-1201%2811%29.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/4c99aa82-a683-4800-830f-c558dd22a318/Untitled-2024-02-08-1201%2811%29.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/4c99aa82-a683-4800-830f-c558dd22a318/Untitled-2024-02-08-1201%2811%29.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/4c99aa82-a683-4800-830f-c558dd22a318/Untitled-2024-02-08-1201%2811%29.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/4c99aa82-a683-4800-830f-c558dd22a318/Untitled-2024-02-08-1201%2811%29.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         1387 
         1388             
         1389           
         1390         
         1391           
         1392         
         1393 
         1394         
         1395       
         1396         </figure>
         1397       
         1398 
         1399     
         1400   
         1401 
         1402 
         1403   
         1404 
         1405 
         1406 &nbsp;
         1407 
         1408 
         1409   <p class="">Something else to notice: in a value graph, any <a href="https://en.wikipedia.org/wiki/Cut_(graph_theory)" target="_blank">cut</a> of the graph is a valid way of creating a sub-graph. The edges that that cross the 'cut' are precisely your inputs and outputs. Here we’ve selected a group of sub-nodes with 1 input crossing, and 1 output crossing:</p>
         1410 
         1411 
         1412 
         1413 
         1414 
         1415 
         1416 
         1417 
         1418 
         1419 
         1420 
         1421 
         1422 
         1423 
         1424 
         1425 
         1426 
         1427 
         1428 
         1429 
         1430 
         1431   
         1432   
         1433 
         1434 
         1435 
         1436 
         1437 
         1438 
         1439 
         1440 
         1441 
         1442 
         1443 
         1444 
         1445 
         1446 
         1447 
         1448 
         1449 
         1450 
         1451 
         1452 
         1453 
         1454 
         1455 
         1456 
         1457 
         1458 
         1459 
         1460 
         1461 
         1462 
         1463 
         1464 
         1465 
         1466 
         1467 
         1468 
         1469 
         1470 
         1471 
         1472 
         1473 
         1474 
         1475 
         1476 
         1477 
         1478 
         1479   
         1480 
         1481     
         1482   
         1483     
         1484 
         1485       
         1486 
         1487       
         1488         <figure class="
         1489               sqs-block-image-figure
         1490               intrinsic
         1491             "
         1492         >
         1493           
         1494         
         1495         
         1496 
         1497         
         1498           
         1499             
         1500           
         1501             
         1502                 
         1503                 
         1504                 
         1505                 
         1506                 
         1507                 
         1508                 
         1509                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/5f18c130-eb2a-4e09-804f-fd98885c8c87/Untitled-2024-02-08-1201%2815%29.png" data-image-dimensions="1171x693" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/5f18c130-eb2a-4e09-804f-fd98885c8c87/Untitled-2024-02-08-1201%2815%29.png?format=1000w" width="1171" height="693" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/5f18c130-eb2a-4e09-804f-fd98885c8c87/Untitled-2024-02-08-1201%2815%29.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/5f18c130-eb2a-4e09-804f-fd98885c8c87/Untitled-2024-02-08-1201%2815%29.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/5f18c130-eb2a-4e09-804f-fd98885c8c87/Untitled-2024-02-08-1201%2815%29.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/5f18c130-eb2a-4e09-804f-fd98885c8c87/Untitled-2024-02-08-1201%2815%29.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/5f18c130-eb2a-4e09-804f-fd98885c8c87/Untitled-2024-02-08-1201%2815%29.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/5f18c130-eb2a-4e09-804f-fd98885c8c87/Untitled-2024-02-08-1201%2815%29.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/5f18c130-eb2a-4e09-804f-fd98885c8c87/Untitled-2024-02-08-1201%2815%29.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         1510 
         1511             
         1512           
         1513         
         1514           
         1515         
         1516 
         1517         
         1518           
         1519           <figcaption class="image-caption-wrapper">
         1520             <p class="">Before</p>
         1521           </figcaption>
         1522         
         1523       
         1524         </figure>
         1525       
         1526 
         1527     
         1528   
         1529 
         1530 
         1531   
         1532 
         1533 
         1534 
         1535 
         1536 
         1537 
         1538 
         1539 
         1540 
         1541 
         1542 
         1543 
         1544 
         1545 
         1546 
         1547 
         1548 
         1549 
         1550 
         1551 
         1552 
         1553 
         1554 
         1555 
         1556 
         1557 
         1558 
         1559 
         1560 
         1561 
         1562 
         1563 
         1564 
         1565 
         1566 
         1567 
         1568 
         1569 
         1570 
         1571 
         1572 
         1573 
         1574 
         1575 
         1576 
         1577   
         1578 
         1579     
         1580   
         1581     
         1582 
         1583       
         1584 
         1585       
         1586         <figure class="
         1587               sqs-block-image-figure
         1588               intrinsic
         1589             "
         1590         >
         1591           
         1592         
         1593         
         1594 
         1595         
         1596           
         1597             
         1598           
         1599             
         1600                 
         1601                 
         1602                 
         1603                 
         1604                 
         1605                 
         1606                 
         1607                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/7031c964-c751-4381-89ee-ef90365e901d/Untitled-2024-02-08-1201%2816%29.png" data-image-dimensions="869x440" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/7031c964-c751-4381-89ee-ef90365e901d/Untitled-2024-02-08-1201%2816%29.png?format=1000w" width="869" height="440" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/7031c964-c751-4381-89ee-ef90365e901d/Untitled-2024-02-08-1201%2816%29.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/7031c964-c751-4381-89ee-ef90365e901d/Untitled-2024-02-08-1201%2816%29.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/7031c964-c751-4381-89ee-ef90365e901d/Untitled-2024-02-08-1201%2816%29.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/7031c964-c751-4381-89ee-ef90365e901d/Untitled-2024-02-08-1201%2816%29.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/7031c964-c751-4381-89ee-ef90365e901d/Untitled-2024-02-08-1201%2816%29.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/7031c964-c751-4381-89ee-ef90365e901d/Untitled-2024-02-08-1201%2816%29.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/7031c964-c751-4381-89ee-ef90365e901d/Untitled-2024-02-08-1201%2816%29.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         1608 
         1609             
         1610           
         1611         
         1612           
         1613         
         1614 
         1615         
         1616           
         1617           <figcaption class="image-caption-wrapper">
         1618             <p class="">After</p>
         1619           </figcaption>
         1620         
         1621       
         1622         </figure>
         1623       
         1624 
         1625     
         1626   
         1627 
         1628 
         1629   
         1630 
         1631 
         1632 
         1633 
         1634 
         1635   <p class="">Here’s another (more invasive) cut, with 1 input edge and 3 output edges:</p>
         1636 
         1637 
         1638 
         1639 
         1640 
         1641 
         1642 
         1643 
         1644 
         1645 
         1646 
         1647 
         1648 
         1649 
         1650 
         1651 
         1652 
         1653 
         1654 
         1655 
         1656 
         1657   
         1658   
         1659 
         1660 
         1661 
         1662 
         1663 
         1664 
         1665 
         1666 
         1667 
         1668 
         1669 
         1670 
         1671 
         1672 
         1673 
         1674 
         1675 
         1676 
         1677 
         1678 
         1679 
         1680 
         1681 
         1682 
         1683 
         1684 
         1685 
         1686 
         1687 
         1688 
         1689 
         1690 
         1691 
         1692 
         1693 
         1694 
         1695 
         1696 
         1697 
         1698 
         1699 
         1700 
         1701 
         1702 
         1703 
         1704 
         1705   
         1706 
         1707     
         1708   
         1709     
         1710 
         1711       
         1712 
         1713       
         1714         <figure class="
         1715               sqs-block-image-figure
         1716               intrinsic
         1717             "
         1718         >
         1719           
         1720         
         1721         
         1722 
         1723         
         1724           
         1725             
         1726           
         1727             
         1728                 
         1729                 
         1730                 
         1731                 
         1732                 
         1733                 
         1734                 
         1735                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6dc6b49a-40be-4c8a-902f-af08eab2e72e/Untitled-2024-02-08-1201%2814%29.png" data-image-dimensions="1060x626" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6dc6b49a-40be-4c8a-902f-af08eab2e72e/Untitled-2024-02-08-1201%2814%29.png?format=1000w" width="1060" height="626" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6dc6b49a-40be-4c8a-902f-af08eab2e72e/Untitled-2024-02-08-1201%2814%29.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6dc6b49a-40be-4c8a-902f-af08eab2e72e/Untitled-2024-02-08-1201%2814%29.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6dc6b49a-40be-4c8a-902f-af08eab2e72e/Untitled-2024-02-08-1201%2814%29.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6dc6b49a-40be-4c8a-902f-af08eab2e72e/Untitled-2024-02-08-1201%2814%29.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6dc6b49a-40be-4c8a-902f-af08eab2e72e/Untitled-2024-02-08-1201%2814%29.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6dc6b49a-40be-4c8a-902f-af08eab2e72e/Untitled-2024-02-08-1201%2814%29.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6dc6b49a-40be-4c8a-902f-af08eab2e72e/Untitled-2024-02-08-1201%2814%29.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         1736 
         1737             
         1738           
         1739         
         1740           
         1741         
         1742 
         1743         
         1744           
         1745           <figcaption class="image-caption-wrapper">
         1746             <p class="">Before</p>
         1747           </figcaption>
         1748         
         1749       
         1750         </figure>
         1751       
         1752 
         1753     
         1754   
         1755 
         1756 
         1757   
         1758 
         1759 
         1760 
         1761 
         1762 
         1763 
         1764 
         1765 
         1766 
         1767 
         1768 
         1769 
         1770 
         1771 
         1772 
         1773 
         1774 
         1775 
         1776 
         1777 
         1778 
         1779 
         1780 
         1781 
         1782 
         1783 
         1784 
         1785 
         1786 
         1787 
         1788 
         1789 
         1790 
         1791 
         1792 
         1793 
         1794 
         1795 
         1796 
         1797 
         1798 
         1799 
         1800 
         1801 
         1802 
         1803   
         1804 
         1805     
         1806   
         1807     
         1808 
         1809       
         1810 
         1811       
         1812         <figure class="
         1813               sqs-block-image-figure
         1814               intrinsic
         1815             "
         1816         >
         1817           
         1818         
         1819         
         1820 
         1821         
         1822           
         1823             
         1824           
         1825             
         1826                 
         1827                 
         1828                 
         1829                 
         1830                 
         1831                 
         1832                 
         1833                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1b470c01-e76e-4aae-8c16-162a9d7c060d/Untitled-2024-02-08-1201%2817%29.png" data-image-dimensions="1033x626" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1b470c01-e76e-4aae-8c16-162a9d7c060d/Untitled-2024-02-08-1201%2817%29.png?format=1000w" width="1033" height="626" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1b470c01-e76e-4aae-8c16-162a9d7c060d/Untitled-2024-02-08-1201%2817%29.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1b470c01-e76e-4aae-8c16-162a9d7c060d/Untitled-2024-02-08-1201%2817%29.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1b470c01-e76e-4aae-8c16-162a9d7c060d/Untitled-2024-02-08-1201%2817%29.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1b470c01-e76e-4aae-8c16-162a9d7c060d/Untitled-2024-02-08-1201%2817%29.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1b470c01-e76e-4aae-8c16-162a9d7c060d/Untitled-2024-02-08-1201%2817%29.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1b470c01-e76e-4aae-8c16-162a9d7c060d/Untitled-2024-02-08-1201%2817%29.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1b470c01-e76e-4aae-8c16-162a9d7c060d/Untitled-2024-02-08-1201%2817%29.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         1834 
         1835             
         1836           
         1837         
         1838           
         1839         
         1840 
         1841         
         1842           
         1843           <figcaption class="image-caption-wrapper">
         1844             <p class="">After</p>
         1845           </figcaption>
         1846         
         1847       
         1848         </figure>
         1849       
         1850 
         1851     
         1852   
         1853 
         1854 
         1855   
         1856 
         1857 
         1858 
         1859 
         1860 
         1861   <p class="">Put another way, value graphs have <strong>lots of seams.</strong> We can cut them up any way we like, simplify nodes, apply general transformations, and the graph stays valid. There are no ‘edge cases’. The same cannot be said for execution wires. </p><p class="">So, Lattice borrows from this lineage of programming languages.&nbsp;That get us an incredibly composable computation model, however we need a few super-powers to give us a more expressive language as a whole. I'll chat more about that next time.</p><p class="">Until then.</p>]]></description><media:content type="image/png" url="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1713652321606-7J2V17Z45O6HTA1GE2N8/Untitled-2024-02-08-1201%2819%29.png?format=1500w" medium="image" isDefault="true" width="1500" height="970"><media:title type="plain">Composability: Designing a Visual Programming Language</media:title></media:content></item><item><title>Lattice now compiles to .NET IL</title><category>Technical</category><dc:creator>John Austin</dc:creator><pubDate>Sat, 30 Mar 2024 23:38:53 +0000</pubDate><link>https://johnaustin.io/articles/2024/lattice-now-compiles-to-net-il</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:6608935a8c7eed6e4e74048a</guid><description><![CDATA[<p class=""><em>Lattice is a high-performance visual scripting system targeting Unity ECS. Read more </em><a href="https://forum.unity.com/threads/lattice-visual-scripting-for-ecs.1508402/"><em>here</em></a><em>.</em></p><p class="">I’ve tried several times to write blog posts about Lattice, and each time I’ve gotten lost in the weeds. It’s hard to pick a point to start. So instead, I’ve resolved to just start writing — quantity over quality, as they say.</p><p class="">Lattice has met a major milestone this week: programs now fully compile to .NET IL! If you’ve never written a programming language before, this may not mean much to you, but there’s something beautiful about seeing a relatively complex program compile down into flat assembly instructions.</p><p class="">I’m very proud of this work. One of the goals of the Lattice project was to design a visual language that could compile down into fast linear code — effectively just the procedural code that you would write if you were coding it by hand. This is very much inspired by Rust’s tradition of “zero-cost abstractions”. With the new compilation pipeline, the node-graph representation is stripped away entirely. Outputs of nodes become local variables and bodies of nodes become plain static methods.</p><p class=""><br>I’ve talked in the past about a plan for Lattice to generate C#, but IL was a much better option for a few reasons:</p><ul data-rte-list="default"><li><p class="">Generating C# requires a Domain Reload to compile which is a non-starter if you're editing scripts while the game runs.</p></li><li><p class="">Generating C# would require shipping a C# compiler at runtime if you want to modify scripts during standalone play.</p></li><li><p class="">C# is a sloppy thing to generate. There are a lot of syntactical concerns you get bogged down in just trying to get something valid.</p></li></ul><p class="">IL, as it turns out, is trivially easy to emit in-process with Reflection.Emit, and is actually really simple to work with. I use the Sigil library which is a validating wrapper which catches a number of type errors, etc, during generation. Plus, IL can do several things that C# can't like calling private methods, etc, which just makes the whole process smoother.</p><p class="">This is possible because of the IR representation I added to the compiler a month or two back. For example, the following graph is represented under the hood as a larger graph of simple primitives:</p>
         1862 
         1863 
         1864 
         1865 
         1866 
         1867 
         1868 
         1869 
         1870 
         1871 
         1872 
         1873 
         1874 
         1875 
         1876 
         1877 
         1878 
         1879 
         1880 
         1881 
         1882 
         1883   
         1884   
         1885 
         1886 
         1887 
         1888 
         1889 
         1890 
         1891 
         1892 
         1893 
         1894 
         1895 
         1896 
         1897 
         1898 
         1899 
         1900 
         1901 
         1902 
         1903 
         1904 
         1905 
         1906 
         1907 
         1908 
         1909 
         1910 
         1911 
         1912 
         1913 
         1914 
         1915 
         1916 
         1917 
         1918 
         1919 
         1920 
         1921 
         1922 
         1923 
         1924 
         1925 
         1926 
         1927 
         1928 
         1929 
         1930 
         1931   
         1932 
         1933     
         1934   
         1935     
         1936 
         1937       
         1938 
         1939       
         1940         <figure class="
         1941               sqs-block-image-figure
         1942               intrinsic
         1943             "
         1944         >
         1945           
         1946         
         1947         
         1948 
         1949         
         1950           
         1951             
         1952           
         1953             
         1954                 
         1955                 
         1956                 
         1957                 
         1958                 
         1959                 
         1960                 
         1961                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6d6e842d-0a3f-4dac-ac54-2acaa05c2d88/Screen_Shot_2024-03-29_at_10.10.42_PM.png" data-image-dimensions="1686x1162" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6d6e842d-0a3f-4dac-ac54-2acaa05c2d88/Screen_Shot_2024-03-29_at_10.10.42_PM.png?format=1000w" width="1686" height="1162" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6d6e842d-0a3f-4dac-ac54-2acaa05c2d88/Screen_Shot_2024-03-29_at_10.10.42_PM.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6d6e842d-0a3f-4dac-ac54-2acaa05c2d88/Screen_Shot_2024-03-29_at_10.10.42_PM.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6d6e842d-0a3f-4dac-ac54-2acaa05c2d88/Screen_Shot_2024-03-29_at_10.10.42_PM.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6d6e842d-0a3f-4dac-ac54-2acaa05c2d88/Screen_Shot_2024-03-29_at_10.10.42_PM.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6d6e842d-0a3f-4dac-ac54-2acaa05c2d88/Screen_Shot_2024-03-29_at_10.10.42_PM.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6d6e842d-0a3f-4dac-ac54-2acaa05c2d88/Screen_Shot_2024-03-29_at_10.10.42_PM.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/6d6e842d-0a3f-4dac-ac54-2acaa05c2d88/Screen_Shot_2024-03-29_at_10.10.42_PM.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         1962 
         1963             
         1964           
         1965         
         1966           
         1967         
         1968 
         1969         
         1970           
         1971           <figcaption class="image-caption-wrapper">
         1972             <p class="">The view of the authoring experience in Lattice. This is my Integration Test graph.</p>
         1973           </figcaption>
         1974         
         1975       
         1976         </figure>
         1977       
         1978 
         1979     
         1980   
         1981 
         1982 
         1983   
         1984 
         1985 
         1986 
         1987 
         1988 
         1989 
         1990 
         1991 
         1992 
         1993 
         1994 
         1995 
         1996 
         1997 
         1998 
         1999 
         2000 
         2001 
         2002 
         2003 
         2004 
         2005 
         2006 
         2007 
         2008 
         2009 
         2010 
         2011 
         2012 
         2013 
         2014 
         2015 
         2016 
         2017 
         2018 
         2019 
         2020 
         2021 
         2022 
         2023 
         2024 
         2025 
         2026 
         2027 
         2028 
         2029   
         2030 
         2031     
         2032   
         2033     
         2034 
         2035       
         2036 
         2037       
         2038         <figure class="
         2039               sqs-block-image-figure
         2040               intrinsic
         2041             "
         2042         >
         2043           
         2044         
         2045         
         2046 
         2047         
         2048           
         2049             
         2050           
         2051             
         2052                 
         2053                 
         2054                 
         2055                 
         2056                 
         2057                 
         2058                 
         2059                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/fcb4a95f-33a7-40bc-931f-2b87d01dd9e1/Screen_Shot_2024-03-29_at_10.14.22_PM.png" data-image-dimensions="1668x1378" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/fcb4a95f-33a7-40bc-931f-2b87d01dd9e1/Screen_Shot_2024-03-29_at_10.14.22_PM.png?format=1000w" width="1668" height="1378" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/fcb4a95f-33a7-40bc-931f-2b87d01dd9e1/Screen_Shot_2024-03-29_at_10.14.22_PM.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/fcb4a95f-33a7-40bc-931f-2b87d01dd9e1/Screen_Shot_2024-03-29_at_10.14.22_PM.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/fcb4a95f-33a7-40bc-931f-2b87d01dd9e1/Screen_Shot_2024-03-29_at_10.14.22_PM.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/fcb4a95f-33a7-40bc-931f-2b87d01dd9e1/Screen_Shot_2024-03-29_at_10.14.22_PM.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/fcb4a95f-33a7-40bc-931f-2b87d01dd9e1/Screen_Shot_2024-03-29_at_10.14.22_PM.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/fcb4a95f-33a7-40bc-931f-2b87d01dd9e1/Screen_Shot_2024-03-29_at_10.14.22_PM.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/fcb4a95f-33a7-40bc-931f-2b87d01dd9e1/Screen_Shot_2024-03-29_at_10.14.22_PM.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         2060 
         2061             
         2062           
         2063         
         2064           
         2065         
         2066 
         2067         
         2068           
         2069           <figcaption class="image-caption-wrapper">
         2070             <p class="">The IR representation of the graph. A single authored node becomes several IR nodes.</p>
         2071           </figcaption>
         2072         
         2073       
         2074         </figure>
         2075       
         2076 
         2077     
         2078   
         2079 
         2080 
         2081   
         2082 
         2083 
         2084 
         2085 
         2086 
         2087   <p class="">This IR is critical because it reduces the complexity of the next step of the compilation: code generation. While there may be hundreds of nodes available for user scripts, in the IR there are only 7 distinct types of operators:</p><ul data-rte-list="default"><li><p class="">Function (a handle to a static C# method)</p></li><li><p class="">Previous (allows referencing a node value from the previous frame)</p></li><li><p class="">Entity (returns a handle to the current Entity)</p></li><li><p class="">QualifierTransform (allows referencing other entities dynamically)</p></li><li><p class="">Barrier (execution barrier, waits for all inputs to finish)</p></li><li><p class="">Collect (collects several values into an array)</p></li><li><p class="">Malformed (a stub node that returns an error. Used for syntax errors)</p></li></ul><p class="">The IL Generation step only needs to implement generation for these 7 operators, dramatically reducing the complexity. In fact, the IL Generator is only ~500 lines of code. The final code looks something like:</p>
         2088 
         2089 
         2090 
         2091 
         2092 
         2093 
         2094 
         2095 
         2096 
         2097 
         2098 
         2099 
         2100 
         2101 
         2102 
         2103 
         2104 
         2105 
         2106 
         2107 
         2108 
         2109   
         2110   
         2111 
         2112 
         2113 
         2114 
         2115 
         2116 
         2117 
         2118 
         2119 
         2120 
         2121 
         2122 
         2123 
         2124 
         2125 
         2126 
         2127 
         2128 
         2129 
         2130 
         2131 
         2132 
         2133 
         2134 
         2135 
         2136 
         2137 
         2138 
         2139 
         2140 
         2141 
         2142 
         2143 
         2144 
         2145 
         2146 
         2147 
         2148 
         2149 
         2150 
         2151 
         2152 
         2153 
         2154 
         2155 
         2156 
         2157   
         2158 
         2159     
         2160   
         2161     
         2162 
         2163       
         2164 
         2165       
         2166         <figure class="
         2167               sqs-block-image-figure
         2168               intrinsic
         2169             "
         2170         >
         2171           
         2172         
         2173         
         2174 
         2175         
         2176           
         2177             
         2178           
         2179             
         2180                 
         2181                 
         2182                 
         2183                 
         2184                 
         2185                 
         2186                 
         2187                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/16b176c7-94e4-428e-89fa-357198b11c9c/Screen_Shot_2024-03-29_at_10.10.00_PM.png" data-image-dimensions="1870x1594" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/16b176c7-94e4-428e-89fa-357198b11c9c/Screen_Shot_2024-03-29_at_10.10.00_PM.png?format=1000w" width="1870" height="1594" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/16b176c7-94e4-428e-89fa-357198b11c9c/Screen_Shot_2024-03-29_at_10.10.00_PM.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/16b176c7-94e4-428e-89fa-357198b11c9c/Screen_Shot_2024-03-29_at_10.10.00_PM.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/16b176c7-94e4-428e-89fa-357198b11c9c/Screen_Shot_2024-03-29_at_10.10.00_PM.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/16b176c7-94e4-428e-89fa-357198b11c9c/Screen_Shot_2024-03-29_at_10.10.00_PM.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/16b176c7-94e4-428e-89fa-357198b11c9c/Screen_Shot_2024-03-29_at_10.10.00_PM.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/16b176c7-94e4-428e-89fa-357198b11c9c/Screen_Shot_2024-03-29_at_10.10.00_PM.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/16b176c7-94e4-428e-89fa-357198b11c9c/Screen_Shot_2024-03-29_at_10.10.00_PM.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         2188 
         2189             
         2190           
         2191         
         2192           
         2193         
         2194 
         2195         
         2196       
         2197         </figure>
         2198       
         2199 
         2200     
         2201   
         2202 
         2203 
         2204   
         2205 
         2206 
         2207 
         2208 
         2209 
         2210   <p class="">With the new backend, Lattice is now quite fast — as fast as C#! I still need to do comparative profiling, but I’m fairly confident Lattice is by far the fastest visual scripting system in Unity by a long shot. Bolt, NodeCanvas, and Playmaker all interpret their node graphs. Lattice emits a single static method that executes all lattice scripts in the game in a single pass. The .NET and Mono JITs eat pure static methods for breakfast. </p><p class="">This work also enables some interesting next steps for the compiler:</p><ul data-rte-list="default"><li><p class="">Automatic parallelization &amp; jobification (Unity Job system)</p></li><li><p class="">Burst compilation of Lattice Graphs</p></li></ul><p class="">However, Lattice is now plenty fast for my needs. So for the time being, I’m pivoting back to working on gameplay workflows and UX. Stay tuned for more updates.</p>]]></description></item><item><title>Why I Like Rust: Documentation</title><dc:creator>John Austin</dc:creator><pubDate>Mon, 19 Feb 2024 20:57:08 +0000</pubDate><link>https://johnaustin.io/articles/2024/qnrfslcq0zakur955ntxabmi8cps8o</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:65d3b91e624f260b58fd08ca</guid><description><![CDATA[<p class="">The other day, I was helping a friend learn Python. They were writing a simple HTTP request to an API, and at one point they asked me "what does <code>requests.get(...).json()</code> return? A string or a parsed JSON object?”</p><p class="">I thought this would be a good "teach a man to fish" sort of lesson: look it up the docs. <a href="https://requests.readthedocs.io/en/latest/">requests</a> is a massively popular library that forms the bedrock of many Python scripts. Here's what the documentation has to say:</p><blockquote><p class="">json(**kwargs)<br>Returns the json-encoded content of a response, if any. <br>Parameters:**kwargs – Optional arguments that json.loads takes.</p></blockquote><p class="">Brutal. It barely answers the question. The description says it "returns a json-encoded value". It wouldn't make sense to call a python object "encoded", so is it a JSON encoded string.</p><p class="">Well, no. Looking at the parameter, <code>json.loads()</code> is called internally. That would only make sense if <code>json()</code> returned a deserialized value. This is, in fact, the correct answer, conveyed through pure luck in an optional parameter. </p><p class="">And what happens if the body is not JSON? Does it return None? Does it throw? What kind of exception should I catch?</p><p class="">In truth, the Pythonic answer to this question is "try it for yourself". Boot up a python interactive console, pip install requests, dig up a fake API to hit, and see what it returns. Hope and pray that you don't hit some snag along the way.</p>
         2211 
         2212 
         2213 
         2214 
         2215 
         2216 
         2217 
         2218 
         2219 
         2220 
         2221 
         2222 
         2223 
         2224 
         2225 
         2226 
         2227 
         2228 
         2229 
         2230 
         2231 
         2232   
         2233   
         2234 
         2235 
         2236 
         2237 <hr />
         2238 
         2239 
         2240   <p class="">Let's see how Rust handles this. </p><p class=""><a href="https://docs.rs/reqwest/latest/reqwest/blocking/struct.Response.html#method.json">https://docs.rs/reqwest/latest/reqwest/blocking/struct.Response.html#method.json</a></p><p class="">We've got:</p><ul data-rte-list="default"><li><p class="">A clear answer: it returns the deserialized object. <a href="#margin" target="_blank">Specifically from the content body. </a></p></li><li><p class="">A short example. In case you need a little context. </p></li><li><p class="">Gotchas: you'll need to enable the JSON feature.<a href="#margin" target="_blank">Rust libraries generally only make you pay for what you use. If you don't need JSON support, it won't even download the dependency.</a></p></li><li><p class="">Failure cases: If it's not JSON, the Result will return a specific error.</p></li></ul><p class="">Further, the broader docs experience is phenomenal:</p><ul data-rte-list="default"><li><p class="">A single global website (<a href="https://docs.rs" target="_blank">docs.rs</a>) where all Rust docs are hosted. </p></li><li><p class="">Real, symbol-based doc search.</p></li><li><p class="">Source files. I can browse the exact version of the code that I'm exploring.</p></li><li><p class="">Inter-library links for symbols! Even across crates, with versioning.</p></li></ul><p class="">This isn't an exception, it's the rule. Here are a random sampling of libraries I recently pulled for a project: [<a href="https://docs.rs/hyper/latest/hyper/" target="">hyper</a>, <a href="https://docs.rs/petgraph/latest/petgraph/">petgraph</a>, <a href="https://docs.rs/base64/0.21.7/base64/">base64</a>]. And just <a href="https://doc.rust-lang.org/std/collections/struct.HashMap.html" target="_blank">look</a> at the <a href="https://doc.rust-lang.org/std/sync/struct.Mutex.html" target="_blank">standard</a> <a href="https://doc.rust-lang.org/std/primitive.char.html" target="_blank">library</a>! Beautiful. Rust has somehow managed to cultivate an ecosystem where this quality of documentation is the standard. </p><p class="">I can't begin to tell you how pleasant this makes developing in Rust. When people ask me why I enjoy the language, sometimes it's hard to convey. It's the little things. Discovering new libraries is <strong>fun</strong>. It's a new box of toys you can pick through, not a slog of trying to dig up arcane, undocumented knowledge. </p><p class="">People assume the "Rewrite it in Rust" is popular because of memory safety, but I have this sneaking feeling that an even larger part is because developing in Rust is just so dang lovely. Of course I want all my libraries to be this level of quality. Who wouldn't?</p>
         2241 
         2242 
         2243 
         2244 
         2245 
         2246 
         2247 
         2248 
         2249 
         2250 
         2251 
         2252 
         2253 
         2254 
         2255 
         2256 
         2257 
         2258 
         2259 
         2260 
         2261 
         2262   
         2263   
         2264 
         2265 
         2266 
         2267 <hr />
         2268 
         2269 
         2270   <p class="">Nothing Rust is doing here is particularly novel, nor technically complex. Heavily inspired by Go<a href="#margin" target="_blank">See gofmt and godoc.</a>, Rust has a focus on strong, unified tooling. Any Rust library can trivially generate docs with a few config settings. Slap <code>#![warn(missing_docs)]</code> and clippy will automatically warn on public symbols missing docs. </p><p class="">But there's also something bigger at work here. Rust sets the bar high. When I'm surrounded by incredible documentation, it feels shoddy to release a library with anything less. It's not a 'gun to the head' kind of threat. It's more like walking into a nice home and feeling like you want to take off your shoes. Everyone here takes so much pride in their work, that it feels good to maintain that. Quality breeds more quality.</p>]]></description></item><item><title>A Gameplay Programming Puzzle for ECS</title><dc:creator>John Austin</dc:creator><pubDate>Sat, 23 Sep 2023 01:16:32 +0000</pubDate><link>https://johnaustin.io/articles/2023/a-gameplay-programming-brain-teaser-for-ecs</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:650e3733ce36643e83e70df0</guid><description><![CDATA[<p class="">Entity Components Systems are hot stuff right now in the world of game engine design. Most of Unity’s <a href="https://unity.com/ecs">new engine workflows</a> is based on ECS, and there are other junior up-starts like the open-source <a href="https://bevyengine.org">Bevy Engine</a>. They’re extremely fast, and well suited to today’s machines with many cores. But they can be a real pain sometimes when implementing gameplay code.</p><p class="">Every now and then, I stumble on a gameplay pattern that’s particularly challenging to implement within an ECS architecture. When that happens, I like to try to simplify the problem – distill it down – and save a draft for posterity. These simplified puzzles are fun because they’re tractable to think about, but they still derive from real-world gameplay needs!</p><p class="">It’s great practice for breaking the bonds of OOP and thinking of the world in a data driven way, and they also provide a sort of ‘design benchmark’ for any promising ECS coming onto the scene. If you can’t implement these patterns with your ECS, it’s pretty likely you’ll run into problems as your games get bigger and more complex!</p><p class="">Let’s dig into one I hit recently, which I’m calling the <strong>Pet Adoption</strong>:</p><h2><strong>Pet Adoption</strong></h2><p class="">Here are the rules of the (extremely simple) game. </p><ul data-rte-list="default"><li><p class="">There are many Humans and many Pets.</p></li><li><p class="">Each human adopts the first pet that likes them.</p></li><li><p class="">Pets may arbitrarily decide if they will let a given human adopt them.</p></li><li><p class="">Each pet can only be adopted once, and each human only adopts one pet.</p></li></ul><p class="">Gameplay goes as follows:</p><ul data-rte-list="default"><li><p class="">Each human goes from pet to pet, asking them if they’d like to be adopted.</p></li><li><p class="">They leave with the first pet that says yes.</p></li><li><p class="">We run our adoption game every frame.</p></li></ul><p class="">—</p><p class="">There’s no concept of ranking here: pets will take the first human that suits them! Similarly, humans will take any pet that allows it!</p><p class="">The thing that makes this tricky is that you may have 50 different types of pets, all with different logic for determining which humans they like. For example, when asked to be adopted, they might:</p><ul data-rte-list="default"><li><p class="">Read from the human state (that person is too ugly!)</p></li><li><p class="">Read from their own state (I’m scared and don’t like anyone!)</p></li><li><p class="">Read from global state (It’s cold today, so I’ll take anyone!)</p></li></ul><p class="">They could even <em>modify</em> their own state based on the previous humans. Maybe the first few humans are so unpleasant that a pet decides to say no to everyone.</p><p class="">This problem is fundamentally one of <strong>indirection</strong>, usually solved by polymorphism. With OOP, this is trivial: add a function to the Pet base class <code>bool CanBeAdopted(Human adopter)</code> and call it as humans are inspecting different pets.<a href="#margin">It’s no wonder UI libraries tend to heavily make use of OOP.</a> ECS generally bans this entirely. Components should be plain, blittable data. Systems should contain your logic.</p><p class="">Instead, other types of decoupling are suggested, such as event systems. However, those don’t work here. Event systems are one-directional: you fire off an event and expect another system to handle it. This gameplay requires bi-directional coupling: a human needs to know a pet’s response first, so it can move on to the next pet in line.<a href="#margin">Tag Components are another way of handling indirection. For example, adding a “Collision” component to any objects that collide with something. However, that’s also  one-directional and fails for the same reasons.</a></p><p class="">The human can’t wait for this information – it needs to know now! This is the distinction between <strong>events</strong> which send information, and <strong>callbacks</strong> which send information and retrieve results.</p><p class="">So it’s tricky. ECS asks you to queue up big chunky systems and pipeline everything, but this logic needs to reflect on objects in the moment, while a human is traversing the list of pets. </p><p class="">So, how would you architect this?</p><p data-rte-preserve-empty="true" class=""></p><p class="">(Discuss on <a href="https://www.reddit.com/r/programming/comments/16prxvm/a_gameplay_programming_puzzle_for_ecs/">/r/programming</a>)</p>
         2271 
         2272 
         2273 
         2274 
         2275 
         2276 
         2277 
         2278 
         2279 
         2280 
         2281 
         2282 
         2283 
         2284 
         2285 
         2286 
         2287 
         2288 
         2289 
         2290 
         2291 
         2292   
         2293   
         2294 
         2295 
         2296 
         2297 <hr />
         2298 
         2299 
         2300   <p class=""><strong>Real World Context</strong></p><p class="">If you’re curious how this maps to the real world, here are two examples.</p><p class=""><strong>Object Interaction:</strong> Characters (Players or NPCs) can interact with objects in the game, but the object chooses whether it will allow a specific character to interact with it, depending on arbitrary factors (time of day, weather, character stats, etc). Certain chests may only open for players, and not NPCs. Or only if the player has &lt;500 gold in their inventory.</p><p class="">Essentially, each object has a ‘shot’ at the interaction event, but can choose to reject it, and the character will move the next valid thing in front of it. This continuation is important, otherwise when an object rejects the interaction event, it could swallow the interaction for a perfectly valid object sitting on top of it.</p><p class="">Characters are humans, Pets are objects, and adoption is whether the interactable allows a certain character to interact with it.</p><p class=""><strong>User Interface:</strong> UI systems tend to have big trees of elements. We want to be able to propagate events down the tree, allow elements to handle them in arbitrary ways, and also return whether an element has “consumed” an event. Event handlers are bidirectional: they affect the propagation of the event itself.</p><p class="">The UI systems are humans, layout elements are pets, and the consumption return value of the event handler is a pet deciding whether to be adopted.</p><p class="">This pattern tends to occur in any system that has single-ownership. Ie. Where several different entities in the game need to agree on who is assigned which other objects. </p><p class=""><br><br></p>]]></description></item><item><title>How to Actually Move Gamedev Off Twitter</title><dc:creator>John Austin</dc:creator><pubDate>Sat, 08 Apr 2023 08:18:42 +0000</pubDate><link>https://johnaustin.io/articles/2023/how-we-can-actually-move-gamedev-off-twitter</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:64291e2da71828296f7fff5d</guid><description><![CDATA[<figure class="
         2301               sqs-block-image-figure
         2302               intrinsic
         2303             "
         2304         >
         2305           
         2306         
         2307         
         2308 
         2309         
         2310           
         2311             
         2312           
         2313             
         2314                 
         2315                 
         2316                 
         2317                 
         2318                 
         2319                 
         2320                 
         2321                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/24890d3e-9dab-4841-9557-4c91cf79a26a/chrome_hkic8TWCwB.png" data-image-dimensions="1666x935" data-image-focal-point="0.4790181650362688,0.23201933022374" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/24890d3e-9dab-4841-9557-4c91cf79a26a/chrome_hkic8TWCwB.png?format=1000w" width="1666" height="935" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/24890d3e-9dab-4841-9557-4c91cf79a26a/chrome_hkic8TWCwB.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/24890d3e-9dab-4841-9557-4c91cf79a26a/chrome_hkic8TWCwB.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/24890d3e-9dab-4841-9557-4c91cf79a26a/chrome_hkic8TWCwB.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/24890d3e-9dab-4841-9557-4c91cf79a26a/chrome_hkic8TWCwB.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/24890d3e-9dab-4841-9557-4c91cf79a26a/chrome_hkic8TWCwB.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/24890d3e-9dab-4841-9557-4c91cf79a26a/chrome_hkic8TWCwB.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/24890d3e-9dab-4841-9557-4c91cf79a26a/chrome_hkic8TWCwB.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         2322 
         2323             
         2324           
         2325         
         2326           
         2327         
         2328 
         2329         
         2330       
         2331         </figure>
         2332       
         2333 
         2334     
         2335   
         2336 
         2337 
         2338   
         2339 
         2340 
         2341 
         2342 
         2343 
         2344   <p class="">Twitter is going downhill fast. First they banned Mastodon promotion (and then reversed it), and now it’s Substack in it’s entirety. We all want to move off, and yet.. here we are. I’ve been doing some thinking and I see two fairly obvious problems that have been keeping everyone on Twitter:  </p><ol data-rte-list="default"><li><p class="">The <strong>Chicken/Egg</strong>: Nobody wants to leave Twitter for a place that has no community, but there can’t be another gamedev community unless we all leave Twitter! It might work if we all leave at once, but that’s proven difficult. We have to break this cycle.</p></li><li><p class="">The <strong>Algorithm: </strong>I think the algorithmic feed is more important than we give it credit. Seeing what your direct followers post is nice, but it doesn’t <strong>really</strong> feel like being a part of the broader community. For better or worse, people are on Twitter to hear about current events, news, and other.. well.. gossip. Any alternative needs to provide that.</p></li></ol><p class="">But we are game designers! If nothing else we’re great at working around gnarly systemic problems! </p><p class="">So! I want to present two concrete actions you can personally take to begin to fix these problems.</p><h2>Action 1. Mirror your Twitter posts to Mastodon with <a href="https://moa.party.">moa.party.</a></h2><p class="">We can’t leave Twitter because all the good content is still on Twitter. It’s classic FOMO. Theoretically, we could <strong>all</strong> leave Twitter at the same time, but I think we should accept the fact that’s not going to happen. It’s kind of like the Prisoner’s Dilemma — nobody wants to make the first move and be left out.<a href="#margin">Okay… technically it’s a &lt;a href=https://en.wikipedia.org/wiki/Stag_hunt&gt;Stag Hunt&lt;/a&gt;. Which is the <em>opposite</em> of the Prisoner’s Dilemma. Sorry Mr. Nash, I’ll turn in my game designer card now.</a></p><p class="">But, if everyone mirrors their tweets, it could be possible to move between the two freely without missing out. All the discourse, all the hot takes will be on Mastodon, too, just with a different UI!</p><p class="">This won’t fix the problem overnight, but it does remove Twitter’s main draw. With enough people mirroring, it wouldn’t matter much which platform you’re on. This means that <strong>people can leave Twitter at their own pace</strong>, and we’ll never need to have a single large exodus. </p><p class="">This breaks the chicken/egg problem, and even you stay on Twitter.. <strong>worst case you’re getting more engagement for your Tweets? </strong>There’s kind of no argument against mirroring.<a href="#margin">Instead… eventually we’ll have two chickens… which are clones?</a></p><p class="">Luckily<strong> it’s trivial to setup using </strong><a href="https://moa.party"><strong>moa.party</strong></a><strong>.</strong> It takes literal seconds! Then you can leave it running and forget about it. </p><p class="">Every single person who mirrors their tweets is taking a concrete step to erode the wall Twitter has around it. Destroy FOMO! Mirror your tweets!</p><h2>Action 2. Install <a href="https://elk.zone">Elk.zone</a>, a Mastodon interface that’s actually good.</h2><p class=""><a href="https://elk.zone">Elk.zone</a> is by far the best UI for using Mastodon. It works on all platforms, and they basically copied the Twitter UI, so you don’t have to learn anything new. I know: the normal Mastodon UI is nice in it’s own way, but in the interest of getting more people onto the platform, I think it’s better to focus on something expected. We’re trying to make it easy to switch! We can dazzle folks with features later.<a href="#margin">You can access it with the #Explore tab in the menu.</a></p><p class=""><strong>Importantly, it also has an algorithmic feed!</strong> As much as a raw timeline is nice, Twitter gamedev thrives because you can keep a pulse on what’s happening in the broader community. In my opinion, without some sort of algorithmic ranking, we’ll never be able to recreate the value of Twitter.</p><p class="">Elk’s algorithmic feed isn’t perfect, but it’s the first time I’ve felt connected with the community when using Mastodon. And because it’s open-source, we can improve it ourselves.</p><p class="">Elk is a web-app, but it <a href="https://www.wikihow.com/Install-Web-Apps-on-iPhone-or-iPad">can be installed</a> into Android and iPhones like any other normal app. It’s well built and feels like a native app. Consider installing it to your phone’s home screen, and try to build it into your Twitter habit!</p>
         2345 
         2346 
         2347 
         2348 
         2349 
         2350 
         2351 
         2352 
         2353 
         2354 
         2355 
         2356 
         2357 
         2358 
         2359 
         2360 
         2361 
         2362 
         2363 
         2364 
         2365 
         2366   
         2367   
         2368 
         2369 
         2370 
         2371 
         2372 
         2373 
         2374 
         2375 
         2376 
         2377 
         2378 
         2379 
         2380 
         2381 
         2382 
         2383 
         2384 
         2385 
         2386 
         2387 
         2388 
         2389 
         2390 
         2391 
         2392 
         2393 
         2394 
         2395 
         2396 
         2397 
         2398 
         2399 
         2400 
         2401 
         2402 
         2403 
         2404 
         2405 
         2406 
         2407 
         2408 
         2409 
         2410 
         2411 
         2412 
         2413 
         2414   
         2415 
         2416     
         2417   
         2418     
         2419 
         2420       
         2421 
         2422       
         2423         <figure class="
         2424               sqs-block-image-figure
         2425               intrinsic
         2426             "
         2427         >
         2428           
         2429         
         2430         
         2431 
         2432         
         2433           
         2434             
         2435           
         2436             
         2437                 
         2438                 
         2439                 
         2440                 
         2441                 
         2442                 
         2443                 
         2444                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c117f42d-d85c-44f8-a891-0c8aebc9210c/chrome_8ew13OczUv.png" data-image-dimensions="1244x845" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c117f42d-d85c-44f8-a891-0c8aebc9210c/chrome_8ew13OczUv.png?format=1000w" width="1244" height="845" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c117f42d-d85c-44f8-a891-0c8aebc9210c/chrome_8ew13OczUv.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c117f42d-d85c-44f8-a891-0c8aebc9210c/chrome_8ew13OczUv.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c117f42d-d85c-44f8-a891-0c8aebc9210c/chrome_8ew13OczUv.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c117f42d-d85c-44f8-a891-0c8aebc9210c/chrome_8ew13OczUv.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c117f42d-d85c-44f8-a891-0c8aebc9210c/chrome_8ew13OczUv.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c117f42d-d85c-44f8-a891-0c8aebc9210c/chrome_8ew13OczUv.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/c117f42d-d85c-44f8-a891-0c8aebc9210c/chrome_8ew13OczUv.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         2445 
         2446             
         2447           
         2448         
         2449           
         2450         
         2451 
         2452         
         2453           
         2454           <figcaption class="image-caption-wrapper">
         2455             <p class="">An example of Elk’s algorithmic “#Explore” tab. I don’t follow any of these people!</p>
         2456           </figcaption>
         2457         
         2458       
         2459         </figure>
         2460       
         2461 
         2462     
         2463   
         2464 
         2465 
         2466   
         2467 
         2468 
         2469 
         2470 
         2471 
         2472   <h2>Will This Work?</h2><p class="">Neither of these suggestions is a silver bullet, but I think there’s good reasons to believe that we can actually make a difference. I’m sure there are other small things we can do, too. So let’s put our brains together and figure this out!</p><p class="">It might take longer than we hope, but: baby steps.</p>
         2473 
         2474 
         2475 
         2476 
         2477 
         2478 
         2479 
         2480 
         2481 
         2482 
         2483 
         2484 
         2485 
         2486 
         2487 
         2488 
         2489 
         2490 
         2491 
         2492 
         2493 
         2494   
         2495   
         2496 
         2497 
         2498 
         2499 <hr />
         2500 
         2501 
         2502   <p class=""><span>Footnote A: Why Mastodon?</span></p><p class="">I should probably write a longer article on why I think Mastodon is the best option. For one, Mastodon is where most people have moved to — it seems to have won the battle. But, to be pragmatic, we just need to pick something, and Mastodon is good!</p><p class="">It’s literally Twitter, except it’s open-source, developed by a non-profit, and it operates like email<a href="#margin">No single company controls the application. It’s shared communication, like email. Importantly, Mastodon has the <strong>least</strong> likely chance of becoming another Twitter in 10 years. It’s late-stage capitalism resistant.</a>. Most people are on <a href="https://mastodon.gamedev.place">gamedev.place</a> or <a href="https://peoplemaking.games">peoplemaking.games</a>, and both are owned by upstanding community members. It doesn’t matter which one you choose: they all go to the same place. It’s like choosing a Gmail or Yahoo email address.</p><p class=""><span>Footnote B: The “Fediverse”</span></p><p class="">Since I have your attention: can we stop calling it the “Fediverse”? Nobody knows what that is, and it just gives off the vibes of “Metaverse” or “Federated Blockchain”. Just call it Mastodon! It’s a cute elephant website! People understand that kind of thing.</p>]]></description><media:content type="image/png" url="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1680943000856-MKGG643PLN03A9AE4RJF/chrome_hkic8TWCwB.png?format=1500w" medium="image" isDefault="true" width="1500" height="842"><media:title type="plain">How to Actually Move Gamedev Off Twitter</media:title></media:content></item><item><title>Writing a Fast C# Code-Search Tool in Rust</title><dc:creator>John Austin</dc:creator><pubDate>Sat, 26 Nov 2022 23:58:54 +0000</pubDate><link>https://johnaustin.io/articles/2022/blazing-fast-structural-search-for-c-sharp-in-rust</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:6381a6f3287ff4075fdb8dbf</guid><description><![CDATA[<p class="">When I began learning Rust, I had several expectations.  I figured that I would be a little less productive <em>writing</em> code, in exchange for memory safety and C-like performance. </p><p class="">What I did <strong>not</strong> expect, was that I would actually be <strong>more</strong> productive writing code. So much has that been the case, that for all of the one-off scripts I used to write in Python, I now write in Rust. And it’s not out of casual fancy, but because it <em>legitimately</em> keeps turning out to be the best tool for the job. </p><p class="">I know. These are wild times.<a href="#margin">I’ll have to write up another post at some point exploring the reasons for this. For this one we’re just going to look at one case. </a></p>
         2503 
         2504 
         2505 
         2506 
         2507 
         2508 
         2509 
         2510 
         2511 
         2512 
         2513 
         2514 
         2515 
         2516 
         2517 
         2518 
         2519 
         2520 
         2521 
         2522 
         2523 
         2524   
         2525   
         2526 
         2527 
         2528 
         2529 <hr />
         2530 
         2531 
         2532   <p class="">As a recent example of this development, I was working on a Unity C# codebase where I had a few questions  about the code. Namely, I wanted to find “all classes with a private field, that extend from interface X”. Normally, I would reach for Resharper’s <a href="https://www.jetbrains.com/help/resharper/Navigation_and_Search__Structural_Search_and_Replace.html">Structural Search</a>, but I just moved over to Jetbrains Rider, and sadly… structural search is in the <a href="https://youtrack.jetbrains.com/issue/RIDER-11489">backlog</a>. </p><p class="">I’d heard good things about <a href="https://tree-sitter.github.io/tree-sitter/">Tree-sitter</a>, though, and it was my day off, so I figured I’d take a crack at writing a  CLI tool. Several hours and 130~ lines of code later, I had a tiny little script that could query 6000 files in under a second!<a href="#margin">As it happens, I wrote a similar tool in C# using Microsoft’s Roslyn library, and a similar query took nearly 30 seconds. Rust is fast! &lt;br/&gt; &lt;br/&gt;To be fair, the Roslyn version was not multi-threaded. Although, the ease of multi-threading in Rust is the primary reason the Rust version is threaded and the C# one is not.</a></p><p class="">So let’s see how that came together. Read on, or <a href="https://github.com/Kleptine/csharp-semantic-search">skip straight to the code</a>. </p><h2>Part 0: Setup</h2><p class="">Let’s start with the hard bit. Tree-sitter is a general AST parsing library that supports a huge number of languages. It works by generating a C library for each language it parses. For simplicity, we can pull the pre-generated <a href="https://github.com/tree-sitter/tree-sitter-c-sharp">C# parser</a> in as a submodule to our project and add it to the build. In order to build this C library as part of our Rust executable, we’ll need to add a few lines to a <code>build.rs</code> file: <a href="#margin">&lt;code&gt;build.rs&lt;/code&gt; is a file that executes as a part of the Cargo build system. You can use it to compile C code, or define other more complicated modifications to the way your library is built. </a></p>
         2533 
         2534 
         2535 
         2536 
         2537 
         2538 
         2539 
         2540 
         2541 
         2542 
         2543 
         2544 
         2545 
         2546 
         2547 
         2548 
         2549 
         2550 
         2551 
         2552 
         2553 
         2554   
         2555   
         2556 
         2557 
         2558 
         2559 <pre class="language-rust"><code>use std::path::PathBuf;
         2560 
         2561 fn main() {
         2562     let dir: PathBuf = ["tree-sitter-c-sharp", "src"].iter().collect();
         2563 
         2564     cc::Build::new()
         2565         .include(&dir)
         2566         .file(dir.join("parser.c"))
         2567         .file(dir.join("scanner.c"))
         2568         .compile("tree-sitter-c-sharp");
         2569 }</code></pre>
         2570 
         2571 
         2572   <p class="">This grabs the headers and source files from <code>tree-sitter-c-sharp</code> and compiles them as a pre-build step. </p><h2>Part 1: Tree-sitter and Rust Bindings</h2><p class="">Tree-sitter provides Rust bindings, which we can use to load this parser library in as an FFI. Because C is unsafe by Rust standards, we’ll need to mark it as unsafe.</p>
         2573 
         2574 
         2575 
         2576 
         2577 
         2578 
         2579 
         2580 
         2581 
         2582 
         2583 
         2584 
         2585 
         2586 
         2587 
         2588 
         2589 
         2590 
         2591 
         2592 
         2593 
         2594   
         2595   
         2596 
         2597 
         2598 
         2599 <pre class="language-rust"><code>extern "C" {
         2600     fn tree_sitter_c_sharp() -> Language;
         2601 }
         2602 
         2603 fn main() {
         2604     // Invoke the top-level C function from the Tree-sitter C# library.
         2605     // Unsafe, because the C FFI is unsafe.
         2606     let language = unsafe { tree_sitter_c_sharp() };
         2607 
         2608     // Create a tree-sitter parser.
         2609     let mut parser = tree_sitter::Parser::new();
         2610     parser.set_language(language).unwrap();
         2611 
         2612     // Parse the file.
         2613     let source_code = fs::read_to_string("my_csharp_file.cs")?;
         2614     let tree = parser.parse(&source_code, None).unwrap();
         2615 
         2616     // ...
         2617 }
         2618 </code>
         2619 </pre>
         2620 
         2621 
         2622   <p class="">Hey! We parsed a C# file! Easy.</p><p class="">Now we need some way to define our query. Tree-sitter actually provides a <a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries">query language</a> built-in! It’s based on <a href="https://en.wikipedia.org/wiki/S-expression">S-Expressions</a>, and looks a bit like this: <code>(binary_expression (number_literal) (number_literal))</code>. That query would find all pieces of code that are a binary operator over two numbers. ie. <code>2 + 2</code>, <code>7 &lt;&lt; 2</code>, etc.</p><p class="">My original question: “all of the classes with a private field, that extend from interface IComponentData” would be encoded as:</p><p class=""><code>(class_declaration bases: (base_list (identifier) @parent) body: (declaration_list (field_declaration (modifier) @modifier) @field (#eq? @modifier ""private"")) (#eq? @parent ""IComponentData""))</code></p><p class="">The <code>@name</code> bits tell Tree-sitter to <strong>capture</strong> the given AST node into a named variable. These can be sent through additional filtering, as in <code>(#eq? @modifier “private”)</code> , or they can simply be read out in our Rust script, the same way you’d read captures out of a regular expression.</p><p class="">For ergonomics, we’ll accept a query as an argument on the command line. We’ll then build a tree-sitter query object, and match it against the AST we just parsed from the file:</p>
         2623 
         2624 
         2625 
         2626 
         2627 
         2628 
         2629 
         2630 
         2631 
         2632 
         2633 
         2634 
         2635 
         2636 
         2637 
         2638 
         2639 
         2640 
         2641 
         2642 
         2643 
         2644   
         2645   
         2646 
         2647 
         2648 
         2649 <pre class="language-rust"><code>#[derive(Parser, Debug)]
         2650 #[command(author, version, about)]
         2651 struct Args {
         2652     path: PathBuf, // Directory to search.
         2653     pattern: String, // Query string (s-exp)
         2654 }
         2655 
         2656 fn parse_file(/*..*/) {
         2657     // ..
         2658   
         2659     let args = Args::parse();
         2660     let full_pattern = format!("{} @full_pattern_cli_capture", args.pattern); // Add an extra root pattern to force capturing the root pattern for display.
         2661 
         2662     // The final query built from the user's string.
         2663     let query =
         2664         Query::new(language, &full_pattern).expect("Error building query from given string.");
         2665 
         2666     // Iterate the nodes in the AST that match the given query:
         2667     let source_bytes = &*source_code.as_bytes();
         2668     let root_node = tree.root_node();
         2669     let mut cursor = QueryCursor::new();
         2670     for m in cursor.matches(query, root_node, source_bytes) {
         2671         // Captures is a hashmap that stores the value of each capture in the query string.
         2672         let captures: HashMap<_, _> = m
         2673             .captures
         2674             .iter()
         2675             .map(|c: &QueryCapture| (query.capture_names()[c.index as usize].clone(), c))
         2676             .collect();
         2677           
         2678         // Here are our matches! 
         2679         // Do anything we want here. Print the results, filter them more, etc.   
         2680     }
         2681 }
         2682 </code></pre>
         2683 
         2684 
         2685   <p class="">That’s all we need for code search. The variable <code>captures</code> now contains the captured nodes of the AST tree, specified in the query above. We can use this to print out results, or do whatever we like. </p><p class="">I added one extra trick here. By default tree-sitter doesn’t provide access to the root node of a query, so I’ve automatically appended a capture variable called <code>@full_pattern_cli_capture</code> to the user’s pattern to capture the root node. <a href="#margin">The reason Tree-sitter doesn’t expose access to the root node is because some patterns may have &lt;a href=”https://github.com/tree-sitter/tree-sitter/discussions/898”&gt;multiple&lt;/a&gt; root nodes! Luckily, this is a quick script, so we don’t have to worry about those patterns.</a></p><h2>Part 2: Directory Traversal</h2><p class="">We’ll use <a href="https://crates.io/crates/walkdir">walkdir</a> library for traversal. It’s not in the standard library, but it might as well be, because it’s reliable and quite performant. </p><p class="">Notice how Rust explicitly marks potential edge-cases with <code>unwrap()</code> and <code>expect()</code> . I’ve chosen just to bubble them up for this script, but it’s nice to know which errors <em>could</em> occur and where. <a href="#margin">In this case, walkdir could encounter an I/O error (disk inaccessible, can’t be read, etc), encounter an infinite loop of symbolic links, or the filename could be invalid utf-8 (and would need more complex handling for file type). &lt;br/&gt;This is laid out nicely in the &lt;a href=https://doc.servo.org/walkdir/struct.Error.html&gt;walkdir doc page&lt;/a&gt;.  </a></p>
         2686 
         2687 
         2688 
         2689 
         2690 
         2691 
         2692 
         2693 
         2694 
         2695 
         2696 
         2697 
         2698 
         2699 
         2700 
         2701 
         2702 
         2703 
         2704 
         2705 
         2706 
         2707   
         2708   
         2709 
         2710 
         2711 
         2712 <pre class="language-rust"><code>
         2713     // Scan the entire directory *first*, so that we can more easily split the work among worker threads later.
         2714     let mut paths = Vec::new();
         2715     for entry in WalkDir::new(args.path).into_iter() {
         2716         let path = entry.unwrap().into_path();
         2717         let path_str = path.to_str().expect("filename was not valid utf-8");
         2718         if path_str.ends_with(".cs") {
         2719             paths.push(path);
         2720         }
         2721     }
         2722 
         2723     println!("Searching {} files.", paths.len());</code></pre>
         2724 
         2725 
         2726   <p class="">We feed these paths into the above parsing code, printing any files that have parse errors. </p>
         2727 
         2728 
         2729 
         2730 
         2731 
         2732 
         2733 
         2734 
         2735 
         2736 
         2737 
         2738 
         2739 
         2740 
         2741 
         2742 
         2743 
         2744 
         2745 
         2746 
         2747 
         2748   
         2749   
         2750 
         2751 
         2752 
         2753 <pre class="language-rust"><code>    for path in paths {
         2754         if let Err(e) = parse_file(&path, &query, &output_text) {
         2755             println!("Skipping [{}] [{}]", path.display(), e);
         2756         }
         2757     }
         2758 </code></pre>
         2759 
         2760 
         2761   <p class="">With these two pieces, we have a fully functional query tool! Let’s try it out. </p>
         2762 
         2763 
         2764 
         2765 
         2766 
         2767 
         2768 
         2769 
         2770 
         2771 
         2772 
         2773 
         2774 
         2775 
         2776 
         2777 
         2778 
         2779 
         2780 
         2781 
         2782 
         2783   
         2784   
         2785 
         2786 
         2787 
         2788 <pre class="language-powershell"><code>
         2789 PS C:\projects\tree-search> cargo run --release -- D:\Projects\EcsEngine "(class_declaration name: (identifier) @classname bases: (base_list (identifier) @parent) body: (declaration_list (field_declaration (modifier) @modifier) @field (#eq? @modifier ""private"")) (#eq? @parent ""IComponentData""))"
         2790    Compiling tree-search v0.1.0 (C:\projects\tree-search)
         2791     Finished release [optimized] target(s) in 1.16s
         2792      Running `target\release\tree-search.exe D:\Projects\EcsEngine "(class_declaration name: (identifier) @classname bases: (base_list (identifier) @parent) body: (declaration_list (field_declaration (modifier) @modifier) @field (#eq? @modifier private)) (#eq? @parent IComponentData))"`
         2793 Searching 6441 files.
         2794 
         2795 ManagedComponent [D:\Projects\EcsEngine\Assets\SnapshottingSystem.cs:at byte 5935]
         2796 TestEmbedInterface [D:\Projects\EcsEngine\Library\PackageCache\com.unity.entities@1.0.0-exp.8\Unity.Entities.Tests\TypeManagerTests.cs:at byte 46500]
         2797 TestTestEmbedBaseClass [D:\Projects\EcsEngine\Library\PackageCache\com.unity.entities@1.0.0-exp.8\Unity.Entities.Tests\TypeManagerTests.cs:at byte 46621]
         2798 ==> Skipping [D:\Projects\EcsEngine\Library\PackageCache\com.unity.platforms@1.0.0-exp.6\Tests\Editor\Unity.Build.Tests\ResultBaseTests.cs] [stream did not contain valid UTF-8]
         2799 PostLoadCommandBuffer [D:\Projects\EcsEngine\Library\PackageCache\com.unity.entities@1.0.0-exp.8\Unity.Entities\Types\SceneTagComponent.cs:at byte 6757]
         2800 
         2801 Found 4 total results.
         2802 PS C:\projects\tree-search>
         2803 </code></pre>
         2804 
         2805 
         2806   <p class="">Looks like there are four results, and one of my files contains non-UTF8 text! I’ll have to look into that. </p><p class="">The nice thing about this tool over IDE code search is that it’s stateless. Code doesn’t need to be indexed first by an IDE before searching. This is particularly useful in Unity, because not all code in a Unity project is exposed into the .NET solution.</p><h2>Bonus: Parallelization and Performance</h2><p class="">Single-threaded isn’t fun, so let’s parallelize this!  The <a href="https://github.com/rayon-rs/rayon">rayon</a> library provides a <code>par_iter()</code> iterator extension that automatically splits the input iterator between threads:</p>
         2807 
         2808 
         2809 
         2810 
         2811 
         2812 
         2813 
         2814 
         2815 
         2816 
         2817 
         2818 
         2819 
         2820 
         2821 
         2822 
         2823 
         2824 
         2825 
         2826 
         2827 
         2828   
         2829   
         2830 
         2831 
         2832 
         2833 <pre class="language-diff-rust diff-highlight"><code>
         2834 // Divide the files to be searched into equal portions and send to worker threads, via rayon's par_iter.
         2835 - for path in paths {
         2836 + paths.par_iter().for_each(|path| {
         2837 </code></pre>
         2838 
         2839 
         2840   <p class="">That’s basically it. Rust guarantees we have no data races or unsafe sharing. I’ve also sped things up a bit more by having our threads <a href="https://github.com/Kleptine/csharp-semantic-search/blob/master/src/main.rs#L159">output text to a shared buffer of strings</a>, with a simple Mutex, rather having them all trying to lock the standard output stream with <code>println!</code> .</p><p class="">Look at this utilization! This is a tool I’d be happy to use.</p>
         2841 
         2842 
         2843 
         2844 
         2845 
         2846 
         2847 
         2848 
         2849 
         2850 
         2851 
         2852 
         2853 
         2854 
         2855 
         2856 
         2857 
         2858 
         2859 
         2860 
         2861 
         2862   
         2863   
         2864 
         2865 
         2866 
         2867 
         2868 
         2869 
         2870 
         2871 
         2872 
         2873 
         2874 
         2875 
         2876 
         2877 
         2878 
         2879 
         2880 
         2881 
         2882 
         2883 
         2884 
         2885 
         2886 
         2887 
         2888 
         2889 
         2890 
         2891 
         2892 
         2893 
         2894 
         2895 
         2896 
         2897 
         2898 
         2899 
         2900 
         2901 
         2902 
         2903 
         2904 
         2905 
         2906 
         2907 
         2908 
         2909 
         2910   
         2911 
         2912     
         2913   
         2914     
         2915 
         2916       
         2917 
         2918       
         2919         <figure class="
         2920               sqs-block-image-figure
         2921               intrinsic
         2922             "
         2923         >
         2924           
         2925         
         2926         
         2927 
         2928         
         2929           
         2930             
         2931           
         2932             
         2933                 
         2934                 
         2935                 
         2936                 
         2937                 
         2938                 
         2939                 
         2940                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/be6a97e9-e6e8-4ab6-943b-64e8a93f652d/FiJkXzlXEAEAE4D.png" data-image-dimensions="1514x673" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/be6a97e9-e6e8-4ab6-943b-64e8a93f652d/FiJkXzlXEAEAE4D.png?format=1000w" width="1514" height="673" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/be6a97e9-e6e8-4ab6-943b-64e8a93f652d/FiJkXzlXEAEAE4D.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/be6a97e9-e6e8-4ab6-943b-64e8a93f652d/FiJkXzlXEAEAE4D.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/be6a97e9-e6e8-4ab6-943b-64e8a93f652d/FiJkXzlXEAEAE4D.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/be6a97e9-e6e8-4ab6-943b-64e8a93f652d/FiJkXzlXEAEAE4D.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/be6a97e9-e6e8-4ab6-943b-64e8a93f652d/FiJkXzlXEAEAE4D.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/be6a97e9-e6e8-4ab6-943b-64e8a93f652d/FiJkXzlXEAEAE4D.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/be6a97e9-e6e8-4ab6-943b-64e8a93f652d/FiJkXzlXEAEAE4D.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         2941 
         2942             
         2943           
         2944         
         2945           
         2946         
         2947 
         2948         
         2949           
         2950           <figcaption class="image-caption-wrapper">
         2951             <p class="">Profile courtesy of Superluminal.</p>
         2952           </figcaption>
         2953         
         2954       
         2955         </figure>
         2956       
         2957 
         2958     
         2959   
         2960 
         2961 
         2962   
         2963 
         2964 
         2965 
         2966 
         2967 
         2968   <h2>Closing Thoughts</h2><p class="">Sometimes it’s hard to exactly convey why I find Rust so appealing. There’s a lot of zealotry around the community, which is.. in good faith, but can leave a bad taste in the mouth of those who are just exploring. We don’t need to rush to rewrite everything in Rust. It took Rust 10 years to get here, we’ve got time.</p><p class="">That said, I really like the language. It’s as if someone set out to design a programming language, and just picked all the right answers. Great ecosystem, flawless cross platform<a href="#margin">Finally, a language where Windows is not an after-thought. I’m looking at you Python and Go…</a>, built-in build tools, no “magic”, static binaries, performance-focused, built-in concurrency checks. Maybe these “correct” choices are just laser-targeted at my soul, but in my experience, once you leap over the initial hurdles, it all just works™️, without much fanfare. </p><p class="">It’s magic that I spent a few hours playing with a new library and came out with a tool that is blazingly fast <em>and</em> not going to fall over in a stiff breeze like so many bash scripts.</p><p class="">And that leads to my primary feeling: <span>Rust is just fun</span>. Programming is finally fun again. <br></p>]]></description></item><item><title>Fast Post-Processing on the Oculus Quest and Unity</title><dc:creator>John Austin</dc:creator><pubDate>Sun, 18 Sep 2022 00:32:46 +0000</pubDate><link>https://johnaustin.io/articles/2022/fast-post-processing-on-the-oculus-quest</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:6326489e4be25f1d0d1d198e</guid><description><![CDATA[<p class="">One of the earliest effects I implemented for The Last Clockwinder was HDR Tonemapping and Color Grading. We had decided from an early stage that the game was going to be lit entirely with static lightmapping<a href="#margin">The entire environment is lit with a single, massive bounced area light!</a>, and post processing goes hand-in-hand with that approach. </p>
         2969 
         2970 
         2971 
         2972 
         2973 
         2974 
         2975 
         2976 
         2977 
         2978 
         2979 
         2980 
         2981 
         2982 
         2983 
         2984 
         2985 
         2986 
         2987 
         2988 
         2989 
         2990   
         2991   
         2992 
         2993 
         2994 
         2995 
         2996 
         2997 
         2998   
         2999 
         3000   
         3001 
         3002 
         3003 
         3004   
         3005     
         3006       
         3007 
         3008         
         3009           
         3010             
         3011               
         3012                 <img class="thumb-image" elementtiming="system-gallery-block-slideshow" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1663546063780-OP1PP8Y7VZDXL6KN1EAU/The_Last_Clockwinder_Umgebung-1200x675.jpg" data-image-dimensions="1200x675" data-image-focal-point="0.4965986394557823,0.38365963855421686" alt=" The Last Clockwinder uses ACES tonemapping and a pre-renderered Color Lookup Table (LUT). the brightest colors flatten towards desaturated white, a feature of using a filmic tonemapper. " data-load="false" data-image-id="6327b2cfca5fb034dd2682e0" data-type="image" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1663546063780-OP1PP8Y7VZDXL6KN1EAU/The_Last_Clockwinder_Umgebung-1200x675.jpg?format=1000w" /><br>
         3013               
         3014 
         3015               
         3016                 
         3017                   
         3018                   
         3019                     
         3020                       
         3021                       <p class="">The Last Clockwinder uses ACES tonemapping and a pre-renderered Color Lookup Table (LUT). the brightest colors flatten towards desaturated white, a feature of using a filmic tonemapper.</p>
         3022                     
         3023                   
         3024                 
         3025               
         3026               
         3027             
         3028           
         3029           
         3030         
         3031 
         3032         
         3033 
         3034         
         3035 
         3036       
         3037 
         3038         
         3039           
         3040             
         3041               
         3042                 <img class="thumb-image" elementtiming="system-gallery-block-slideshow" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1663546055041-YQEI1A2SP64GTNFBWACY/Unity_koBvB96Bih.png" data-image-dimensions="1306x798" data-image-focal-point="0.5,0.5" alt=" The same shot, without HDR tonemapping or color grading. " data-load="false" data-image-id="6327b2c5787a9c6850704518" data-type="image" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1663546055041-YQEI1A2SP64GTNFBWACY/Unity_koBvB96Bih.png?format=1000w" /><br>
         3043               
         3044 
         3045               
         3046                 
         3047                   
         3048                   
         3049                     
         3050                       
         3051                       <p class="">The same shot, without HDR tonemapping or color grading.</p>
         3052                     
         3053                   
         3054                 
         3055               
         3056               
         3057             
         3058           
         3059           
         3060         
         3061 
         3062         
         3063 
         3064         
         3065 
         3066       
         3067     
         3068   
         3069 
         3070   
         3071 
         3072 
         3073 
         3074 
         3075   
         3076 
         3077     
         3078       
         3079           
         3080 
         3081         
         3082 
         3083         
         3084       
         3085           
         3086 
         3087         
         3088 
         3089         
         3090       
         3091     
         3092 
         3093   
         3094 
         3095 
         3096 
         3097 
         3098 
         3099 
         3100 
         3101 
         3102   <p class="">If you’re a VR developer you might find that surprising! </p><p class="">Generally, post processing isn’t <a href="https://developer.oculus.com/blog/pc-rendering-techniques-to-avoid-when-developing-for-mobile-vr/">viable</a> on platforms like the Oculus Quest. Rendering a second pass requires you to first resolve your intermediate framebuffer to main memory and then read it back again. This can eat up 20-30% of your frame budget just waiting on memory!</p><p class="">Additionally, at least in Unity, MSAA and Foveated Rendering are only supported on the backbuffer, and not on intermediate buffers. Obviously, these are non-starters for efficient rendering. </p><h2>A simple trick: move Post Post-Processing to the Forward Pass </h2><p class="">Luckily, we can apply a trick here. Any post-processing that is independent per pixel<a href="#margin">Ex: Color Grading, Tonemapping, Vignette, Film Grain. But <em>not </em>Blur, Bloom.</a> can be moved to run directly in the forward geometry pass, rendered right at the end of the pixel shader. The pixel shaders calculate with 16bit HDR values, and are tone-mapped down to 8bit RGB just before outputting the final pixel color.</p><p class="">In our case, I modified the Unity URP forward renderer and Shader Graph packages to enable this tweak with a checkbox in the render pipelines settings asset. If you’re interested in replicating this you can take a look at the PR <a href="https://github.com/AStrangerGravity/Graphics/pull/3">here</a> in our public branch of the Unity Graphics repo. </p>
         3103 
         3104 
         3105 
         3106 
         3107 
         3108 
         3109 
         3110 
         3111 
         3112 
         3113 
         3114 
         3115 
         3116 
         3117 
         3118 
         3119 
         3120 
         3121 
         3122 
         3123 
         3124   
         3125   
         3126 
         3127 
         3128 
         3129 <pre class="language-hlsl"><code>// Used in Standard (Physically Based) shader
         3130 half4 LitPassFragment(Varyings input) : SV_Target
         3131 {
         3132     UNITY_SETUP_INSTANCE_ID(input);
         3133     UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
         3134   
         3135     half4 color = UniversalFragmentPBR(inputData, surfaceData.albedo, surfaceData.metallic, surfaceData.specular, surfaceData.smoothness, surfaceData.occlusion, surfaceData.emission, surfaceData.alpha);
         3136     // ... snipped for space
         3137     color.rgb = MixFog(color.rgb, inputData.fogCoord);
         3138 
         3139     // (ASG) Add tonemapping and color grading in forward pass.
         3140     // This uses the same color grading function as the post processing shader.
         3141 #ifdef _COLOR_TRANSFORM_IN_FORWARD
         3142     color.rgb = ApplyColorGrading(color.rgb, _Lut_Params.w, TEXTURE2D_ARGS(_InternalLut, sampler_LinearClamp), _Lut_Params.xyz, TEXTURE2D_ARGS(_UserLut, sampler_LinearClamp), _UserLut_Params.xyz, _UserLut_Params.w);
         3143 #endif
         3144 
         3145     // Return linear color. Conversion to sRGB happens automatically through the target texture format.
         3146     // (ASG) Note: sRGB conversion *must* be done in hardware, so that filtering / msaa
         3147     // averaging is done properly in linear space, rather than in sRGB space.
         3148     return color;
         3149 }</code></pre>
         3150 
         3151 
         3152   <p class="">I’ve heard of a few other developers applying this technique, so I suspect that most of the AAA-looking games on the Quest store are doing something similar. I’d be curious to hear of any others doing something similar!</p><h2>Downsides</h2><p class="">Of course, there are some downsides, which are good reasons not to have this on by default for all Unity projects.</p><p class=""><strong>Incorrect Transparent Blending</strong></p><p class="">In a standard two-pass HDR pipeline, blending transparent objects with the pixels behind it occurs on the HDR frame buffer. This means it happens in a linear RGB color space.<a href="#margin">If you’re not familiar with linear/non-linear RGB, you might check out my Strangeloop talk &lt;a href=”https://www.youtube.com/watch?v=AS1OHMW873s”&gt;here&lt;/a&gt; on color science.</a> Moving tone-mapping to the forward pass means that blending happens <strong>after</strong> tone-mapping. </p><p class="">But tone-mapping is a non-linear function. And so applying a linear blend operation after tone-mapping is not equivalent to applying the blend before tone-mapping.  </p>
         3153 
         3154 
         3155 
         3156 
         3157 
         3158 
         3159 
         3160 
         3161 
         3162 
         3163 
         3164 
         3165 
         3166 
         3167 
         3168 
         3169 
         3170 
         3171 
         3172 
         3173 
         3174   
         3175   
         3176 
         3177 
         3178 
         3179 
         3180 
         3181 
         3182 
         3183 
         3184 
         3185 
         3186 
         3187 
         3188 
         3189 
         3190 
         3191 
         3192 
         3193 
         3194 
         3195 
         3196 
         3197 
         3198 
         3199 
         3200 
         3201 
         3202 
         3203 
         3204 
         3205 
         3206 
         3207 
         3208 
         3209 
         3210 
         3211 
         3212 
         3213 
         3214 
         3215 
         3216 
         3217 
         3218 
         3219 
         3220 
         3221 
         3222   
         3223 
         3224     
         3225   
         3226     
         3227 
         3228       
         3229 
         3230       
         3231         <figure class="
         3232               sqs-block-image-figure
         3233               intrinsic
         3234             "
         3235         >
         3236           
         3237         
         3238         
         3239 
         3240         
         3241           
         3242             
         3243           
         3244             
         3245                 
         3246                 
         3247                 
         3248                 
         3249                 
         3250                 
         3251                 
         3252                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/62c92622-3358-4c9c-9dc0-3523f202e8ab/image11.png" data-image-dimensions="602x431" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/62c92622-3358-4c9c-9dc0-3523f202e8ab/image11.png?format=1000w" width="602" height="431" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/62c92622-3358-4c9c-9dc0-3523f202e8ab/image11.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/62c92622-3358-4c9c-9dc0-3523f202e8ab/image11.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/62c92622-3358-4c9c-9dc0-3523f202e8ab/image11.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/62c92622-3358-4c9c-9dc0-3523f202e8ab/image11.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/62c92622-3358-4c9c-9dc0-3523f202e8ab/image11.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/62c92622-3358-4c9c-9dc0-3523f202e8ab/image11.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/62c92622-3358-4c9c-9dc0-3523f202e8ab/image11.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         3253 
         3254             
         3255           
         3256         
         3257           
         3258         
         3259 
         3260         
         3261           
         3262           <figcaption class="image-caption-wrapper">
         3263             <p class="">The ACES filmic tone-mapping curve. It’s a non-linear transformation!  (image: Chris Brejon) </p>
         3264           </figcaption>
         3265         
         3266       
         3267         </figure>
         3268       
         3269 
         3270     
         3271   
         3272 
         3273 
         3274   
         3275 
         3276 
         3277 
         3278 
         3279 
         3280   <p class="">Pragmatically, this means that your transparent objects will look a little bit different than they would in a traditional HDR pipeline, especially if you’re additively layering effects such that the final brightness starts to drift into either shoulder of the ACES S-Curve.<a href="#margin">Theoretically there should also be some incorrect blending around the anti-aliased edges of geometry. However, I’ve never noticed this in practice. </a> </p><p class="">In our case, this effect was entirely unnoticeable. We don’t use many transparent materials to begin with, and none of them were very bright or dark. And for the vast majority of projects, you could adjust for these affects in your materials. </p><p class=""><strong>More Expensive Pixel Shaders</strong></p><p class="">We’ve added a handful of math operations (Tonemapping) and a texture LUT read (Color Grading), so naturally our pixel shaders are a bit more expensive. And it’s more expensive to do this work in the pixel shader than a separate pass:</p><ul data-rte-list="default"><li><p class="">If you’re using MSAA 4x, any pixels hit by MSAA (pixels on the edges of triangles) will be executed 4 times for every final pixel. A standard post-process pass only executes once per pixel. </p></li><li><p class="">The GPU renders in quads, and some of these pixels are discarded. So you will be running post processing for these discarded pixels as well. This is called <a href="https://www.youtube.com/watch?t=404s&amp;v=UZH4vZ0NDAw">Quad Overdraw</a>.</p></li><li><p class="">Inefficient draw ordering. If your objects aren’t perfectly sorted, pixel shaders may execute for occluded pixels, before being rendered over by later geometry.  This is highly hardware specific. For example, the Qualcomm Snapdragon XR2 in the Quest 2 implements a <a href="https://developer.qualcomm.com/sites/default/files/docs/adreno-gpu/snapdragon-game-toolkit/gdg/gpu/overview.html#lrz">Low Resolution Z-pass</a> (aka. “Order independent depth testing”). However, it’s disabled for alpha cutout shaders, and only applies conservatively because of the low resolution.      </p></li></ul><p class="">That said, the cost of these additional operations is still quite low, especially if the LUT fits in cache.</p><h2>Conclusion</h2><p class="">The nice thing about our implementation is that we can continue to use all of the standard Post Processing Volume components and configuration on the Unity side. No changes needed to happen except in the renderer. </p><p class="">Looking forward to the future, <a href="https://developer.samsung.com/galaxy-gamedev/resources/articles/renderpasses.html">Vulkan Subpasses</a> should make this kind of work much easier. But until then, rolling it into the forward pass worked well for us, and I’d recommend it for anyone on a tile-based GPU platform.</p><p class="">  </p>]]></description></item><item><title>Hot Reloading Rust: Windows and Linux</title><category>Article</category><dc:creator>John Austin</dc:creator><pubDate>Sun, 06 Feb 2022 23:03:52 +0000</pubDate><link>https://johnaustin.io/articles/2022/hot-reloading-rust</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:61e2d7212036f06114d81c2e</guid><description><![CDATA[<p class="">Recently, I’ve been implementing hot reloading for a new game engine I’m writing in Rust. If you’re interested in the subject, you may have seen the Faster Than Lime <a href="https://fasterthanli.me/articles/so-you-want-to-live-reload-rust">article</a>, which is one of the most detailed sources out there. Although Amos encountered major issues implementing hot reloading on Linux, I was surprised to discover that hot reloading works just fine on Windows! </p><p class="">What makes these platforms different? Read on!</p><h2>Hot Reloading and Thread Local Storage</h2><p class="">Check out the Faster Than Lime article for the gory details, but to summarize: Hot reloading interacts poorly with values in Thread Local Storage, when these values have destructors<a href="#margin">Any object that allocates (Vec, Box, String, Rc)  must run a destructor to free the underlying memory.</a>. To see why, let’s consider this situation:</p><ol data-rte-list="default"><li><p class="">The main thread loads a dynamic library.</p></li><li><p class="">Thread 2 calls a function in the library, which creates a TLS value <code>x</code>, associated with Thread 2.</p></li><li><p class="">The main thread unloads the dynamic library.</p></li><li><p class="">Thread 2 exits.</p></li></ol><p class="">When should the destructor for <code>x</code> be run? </p><p class="">Normally, TLS destructors run when a thread exits (step 4), but the dynamic library has already unloaded! The code for the destructor may be long gone! Clearly it cannot run at step 4.</p><p class="">Step 3 is a possibility, but it’s unsafe to access a TLS value from a different thread that the one it was created on. At step 3, we’re running on the main thread. Thread 2 may be busy, or even using that TLS value — we can’t just interrupt it to tell it to run a destructor. Clearly, we cannot destruct the value at step 3!</p><p class="">We shouldn’t leak the value — it could be holding a socket or other RAII resource.</p><h2>What Linux Does</h2><p class="">The second half of the Faster Than Lime article explores this problem. What Linux does, in this case, is to skip unloading the dynamic library. Even if you request unloading the library at step 3, it will hang on to the library until all threads with TLS values from the dynamic library have exited.<a href="#margin">By waiting for the thread to exit, the TLS destructors can be run normally, because the dynamic library is still loaded.</a></p><p class="">This means any thread that enters the dynamic library becomes “poisoned”. The library now can’t be unloaded until all of these threads exit. Worse, if the main thread ever calls into the library, the library can <strong>never </strong>be unloaded. Once Rust registers a TLS destruction callback via <code>__cxa_thread_atexit</code>, we’re stuck.</p><p class="">Obviously, this is not great for our hot-reloading plans. Two options cross my mind:</p><ul data-rte-list="default"><li><p class="">Create a special thread used exclusively for each plugin library.<a href="#margin">You can kill it before unloading.</a></p></li><li><p class="">Push all TLS usage to an <em>additional</em>, unloadable, dynamic library. Ban TLS in all plugins libraries. <a href="#margin">TLS values are associated with the library that creates them. In Amos’s case, the println! macro was allocating TLS in the std. Dynamically linking the standard library to each plugin would allow the core plugin to reload, because the TLS usage is in a separate library.</a></p></li></ul><p class="">Frankly, neither of those solutions are great. If you’re aware of a great solution on Linux, I’d love to know. But as I’m primarily developing for Windows, I won’t dig deeper here.</p><h2>What Windows Does</h2><p class="">Unlike Linux, the Windows TLS API does not take a destruction callback. Instead, the <code>DllMain</code> function of the dynamic library is called whenever threads are created and destroyed. It’s up to the author of the dynamic library to <a href="https://docs.microsoft.com/en-us/windows/win32/dlls/using-thread-local-storage-in-a-dynamic-link-library">manually manage</a> the creation and destruction of TLS values. So in this case, it’s up to <strong>Rust</strong> how it wants to implement TLS Destructors.</p><p class="">What does Rust do? </p><p class="">Let’s write a short program to test out the behavior of TLS destructors on Windows, simulating the scenario at the start of this article. For our TLS value, we’re using a custom type that prints whenever it is constructed or drops.</p>
         3281 
         3282 
         3283 
         3284 
         3285 
         3286 
         3287 
         3288 
         3289 
         3290 
         3291 
         3292 
         3293 
         3294 
         3295 
         3296 
         3297 
         3298 
         3299 
         3300 
         3301 
         3302   
         3303   
         3304 
         3305 
         3306 
         3307 <pre class="language-bash"><code>PS C:\Projects\hotreload> .\harness.exe
         3308 Loading library on main thread. Thread=19944.
         3309 Calling plugin function on thread 2. Thread=28996
         3310 (TLS) Initialized X on thread 28996
         3311 Unloading plugin on main thread. Thread=19944
         3312 Finished thread 2. Thread=28996
         3313 </code></pre>
         3314 
         3315 
         3316   <p class="">The behavior we see is:</p><ol data-rte-list="default"><li><p class="">The plugin is loaded.</p></li><li><p class="">The TLS value is lazily<a href="#margin">Note the fact that TLS values are lazy! If you don’t use them, they never get created.</a> initialized on first use.</p></li><li><p class="">The plugin is unloaded.</p></li><li><p class="">The TLS value is <strong>not </strong>dropped. </p></li></ol><p class="">Odd.. the TLS value is never dropped — it never printed ‘Destroyed’. But the plugin library <strong>was</strong> successfully unloaded. We can confirm that with WinDBG’s module explorer. What’s happening here? </p><p class="">Let’s dig a bit deeper. The beauty of an open source standard library is that we can just browse the code! Destructors for TLS values are handled in the standard library by <code><a href="https://github.com/rust-lang/rust/blob/17dfae79bbc3dabe1427073086acf7f7bd45148c/library/std/src/sys/windows/thread_local_key.rs#L196">std::sys::windows::on_tls_callback</a></code>. </p><p class="">The documentation for this file is absolutely fantastic, so I highly recommend giving it a read. <code>on_tls_callback</code> is called by the OS when a thread is destroyed (<code><a href="https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain#parameters">DLL_THREAD_DETACH</a></code>), or the process exits  / library is unloaded (<code><a href="https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain#parameters">PROCESS_DETACH</a></code>).</p><p class="">We can drop down a break point into this function to see what happens. Note that in Rust, the standard library is compiled statically into both the main executable and the dynamic library, so there are actually <strong>two</strong> versions of  <code>on_tls_callback</code> in our program, but the one in the dynamic library is the one that will be called when the library unloads.</p><p class="">In WinDBG we can use the command: <code>bu plugin!std::sys::windows::thread_local_key::on_tls_callback</code>.</p>
         3317 
         3318 
         3319 
         3320 
         3321 
         3322 
         3323 
         3324 
         3325 
         3326 
         3327 
         3328 
         3329 
         3330 
         3331 
         3332 
         3333 
         3334 
         3335 
         3336 
         3337 
         3338   
         3339   
         3340 
         3341 
         3342 
         3343 
         3344 
         3345 
         3346 
         3347 
         3348 
         3349 
         3350 
         3351 
         3352 
         3353 
         3354 
         3355 
         3356 
         3357 
         3358 
         3359 
         3360 
         3361 
         3362 
         3363 
         3364 
         3365 
         3366 
         3367 
         3368 
         3369 
         3370 
         3371 
         3372 
         3373 
         3374 
         3375 
         3376 
         3377 
         3378 
         3379 
         3380 
         3381 
         3382 
         3383 
         3384 
         3385 
         3386   
         3387 
         3388     
         3389   
         3390     
         3391 
         3392       
         3393 
         3394       
         3395         <figure class="
         3396               sqs-block-image-figure
         3397               intrinsic
         3398             "
         3399         >
         3400           
         3401         
         3402         
         3403 
         3404         
         3405           
         3406             
         3407           
         3408             
         3409                 
         3410                 
         3411                 
         3412                 
         3413                 
         3414                 
         3415                 
         3416                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/baf7f613-ea77-4e09-ae25-d2992e80d958/DbgX.Shell_2022-01-29_15-54-00.png" data-image-dimensions="1188x686" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/baf7f613-ea77-4e09-ae25-d2992e80d958/DbgX.Shell_2022-01-29_15-54-00.png?format=1000w" width="1188" height="686" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/baf7f613-ea77-4e09-ae25-d2992e80d958/DbgX.Shell_2022-01-29_15-54-00.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/baf7f613-ea77-4e09-ae25-d2992e80d958/DbgX.Shell_2022-01-29_15-54-00.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/baf7f613-ea77-4e09-ae25-d2992e80d958/DbgX.Shell_2022-01-29_15-54-00.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/baf7f613-ea77-4e09-ae25-d2992e80d958/DbgX.Shell_2022-01-29_15-54-00.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/baf7f613-ea77-4e09-ae25-d2992e80d958/DbgX.Shell_2022-01-29_15-54-00.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/baf7f613-ea77-4e09-ae25-d2992e80d958/DbgX.Shell_2022-01-29_15-54-00.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/baf7f613-ea77-4e09-ae25-d2992e80d958/DbgX.Shell_2022-01-29_15-54-00.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         3417 
         3418             
         3419           
         3420         
         3421           
         3422         
         3423 
         3424         
         3425       
         3426         </figure>
         3427       
         3428 
         3429     
         3430   
         3431 
         3432 
         3433   
         3434 
         3435 
         3436 
         3437 
         3438 
         3439   <p class="">Stepping through we see the following behavior:</p><p class=""><code>dwReason</code> (aka register <code>edx</code>) contains the value of 0 (<code><a href="https://github.com/rust-lang/rust/blob/17dfae79bbc3dabe1427073086acf7f7bd45148c/library/std/src/sys/windows/c.rs#L184">DLL_PROCESS_DETACH</a></code>) when the library unloads. This is what we expect based on Microsoft documentation, for an unloading dynamic library.</p><p class="">Both <code>run_dtors</code> and <code>run_keyless_dtors</code> are called, but when stepping through, both of them have an empty list of destructors! So we’re not running any destructors at all.</p><p class="">This makes some sense though. The library is being unloaded on the main thread, but in our use case, the main thread never calls into the library! The TLS values in our plugin are never initialized on the main thread, so there’s nothing to destroy.</p><p class="">So what happens if we unload the plugin instead on thread 2? </p>
         3440 
         3441 
         3442 
         3443 
         3444 
         3445 
         3446 
         3447 
         3448 
         3449 
         3450 
         3451 
         3452 
         3453 
         3454 
         3455 
         3456 
         3457 
         3458 
         3459 
         3460 
         3461   
         3462   
         3463 
         3464 
         3465 
         3466 <pre class="language-bash"><code>PS C:\Projects\hotreload> .\harness.exe
         3467 Loading library on main thread. Thread=6456.
         3468 Calling plugin function on thread 2. Thread=14024
         3469 (TLS) Initialized X on thread 14024
         3470 Unloading plugin on thread 2. Thread=14024
         3471 (TLS) Dropping X on thread 14024
         3472 Finished thread 2. Thread=14024
         3473 </code></pre>
         3474 
         3475 
         3476   <p class="">Oho! So now we’re properly dropping our TLS value. </p>
         3477 
         3478 
         3479 
         3480 
         3481 
         3482 
         3483 
         3484 
         3485 
         3486 
         3487 
         3488 
         3489 
         3490 
         3491 
         3492 
         3493 
         3494 
         3495 
         3496 
         3497 
         3498   
         3499   
         3500 
         3501 
         3502 
         3503 <hr />
         3504 
         3505 
         3506   <p class="">This leads to our final conclusion:</p><p class=""><strong>Rust will run the TLS destructors for the thread that unloads the library. All other TLS values are leaked. </strong><a href="#margin">Note: Rust only runs the TLS destructors associated with the unloading library. The TLS values associated with the primary executable will be left alone — as they should, because the rest of the program is still running!</a> </p><p class="">The leaks are unfortunate, but to be fair, that’s probably the best we can do in this situation. Windows is unloading the library whether we like it or not, and the TLS destructors on the current thread are the only ones that are safe to access. </p><p class="">A silver lining of this approach is that if the library is only used from a single thread, hot-reloading works perfectly: it can be loaded and unloaded promptly, and without TLS leaks. <a href="#margin">And unlike Linux, that thread can continue to live on past the usage of the dynamic library.</a></p><h2>Hot-reloading in Rust</h2><p class="">So which strategy is better, Windows or Linux? Honestly, it feels like a bit of a wash. </p><p class="">Linux is arguably the safest. It guarantees that no TLS values are leaked. However, it does that... by leaking the entire dynamic library itself. I’m not sure that’s an obvious win, especially if you’re running on the main thread, and it’s certainly a problem for implementing hot-reloading on Linux.</p><p class="">Rust’s Windows implementation is more flexible, but this leads to situations where TLS values are leaked, which is dangerous for RAII resources. But.. that flexibility enables us to make our own choice. If we know the TLS values are safe to leak for our plugins, hot-reloading works just fine. And so far I haven’t <em>actually found </em>one that has caused issues. I’d be curious from others if they have examples of TLS values with RAII semantics. </p><p class="">At the end of the day, hot-reloading in Rust is usable on Windows by default, so it’s hard not to at least give it a point there. At least for me as a game developer, hot-reloaded modules are a critical engine feature.</p><p class="">Perhaps the real criminal here is thread-local storage itself. The very nature of tying value lifetime to thread lifetime conflicts with the ability to unload dynamic libraries. But as far as performance goes, it’s hard to imagine life without them. Perhaps one day Rust could expose a different primitive that provides the benefits of both!</p><h3>Further Reading:</h3><ul data-rte-list="default"><li><p class=""><a href="https://github.com/rust-lang/rust/issues/28129">The Rust Team Grapples with TLS Destructors - Github Issues</a> </p></li><li><p class=""><a href="https://docs.microsoft.com/en-us/windows/win32/procthread/thread-local-storage">Thread Local Storage - MDSN</a></p></li></ul>
         3507 
         3508 
         3509 
         3510 
         3511 
         3512 
         3513 
         3514 
         3515 
         3516 
         3517 
         3518 
         3519 
         3520 
         3521 
         3522 
         3523 
         3524 
         3525 
         3526 
         3527 
         3528   
         3529   
         3530 
         3531 
         3532 
         3533 <hr />]]></description></item><item><title>Scene View Debug Modes in the Unity URP</title><category>Article</category><dc:creator>John Austin</dc:creator><pubDate>Wed, 10 Mar 2021 22:41:29 +0000</pubDate><link>https://johnaustin.io/articles/2021/scene-view-debug-modes-in-the-unity-urp</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:6049490ae2d41705723143ad</guid><description><![CDATA[<p class="">In the Unity Universal Render Pipeline, the scene view debug shader list is fairly empty. There aren’t any of the standard view modes you would expect for debugging PBR materials (Normal, Albedo, etc). </p><p class="">So I added them, here you go.</p><p data-rte-preserve-empty="true" class=""></p><p class=""><a href="https://johnaustin.io/s/SceneDebugViews.zip">Download Here</a></p><blockquote><p class=""><strong>Installation</strong>: Unzip the folder into the path <code>Assets/Editor/SceneDebugViews/</code> or modify <code>SceneDebugViewsAsset.cs</code> to point to the correct installation location.</p></blockquote><p data-rte-preserve-empty="true" class=""></p><p class="">This package gives you a few modes:</p><ul data-rte-list="default"><li><p class="">World Normals (Per-Vertex)</p></li><li><p class="">World Normals (Per-Pixel)</p></li><li><p class="">World Normals (Per-Pixel, Absolute)</p></li><li><p class="">Object Normals (Per-Vertex)</p></li><li><p class="">Object Normals (Per-Vertex, Absolute)</p></li><li><p class="">Albedo</p></li><li><p class="">Occlusion</p></li><li><p class="">Smoothness</p><p data-rte-preserve-empty="true" class=""></p></li></ul><p class="">Normals can be shown in standard or absolute. Absolute shows the raw normal color, but clips negative normals as black. Standard shows the normals, remapped from [-1 .. 1] to [0 .. 1].</p><p class="">These only support the URP, but it would not be hard to modify the shaders to support the HDRP or Legacy pipeline.</p>
         3534 
         3535 
         3536 
         3537 
         3538 
         3539 
         3540 
         3541 
         3542 
         3543 
         3544 
         3545 
         3546 
         3547 
         3548 
         3549 
         3550 
         3551 
         3552 
         3553 
         3554 
         3555   
         3556   
         3557 
         3558 
         3559 
         3560 
         3561 
         3562 
         3563   
         3564 
         3565   
         3566 
         3567 
         3568 
         3569   
         3570     
         3571       
         3572 
         3573         
         3574 
         3575         
         3576 
         3577         
         3578           
         3579             
         3580               
         3581                 
         3582                 <a role="presentation" class="
         3583                     image-slide-anchor
         3584                     
         3585                     content-fill
         3586                   "
         3587                 >
         3588                   
         3589                   <img class="thumb-image" elementtiming="system-gallery-block-grid" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1615416323579-STAKM9Q2XNGT2YBVYB4P/Unity_Y4gZBjP8dh.png" data-image-dimensions="872x645" data-image-focal-point="0.5,0.5" alt="Unity_Y4gZBjP8dh.png" data-load="false" data-image-id="60494c0230dfbe479515235d" data-type="image" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1615416323579-STAKM9Q2XNGT2YBVYB4P/Unity_Y4gZBjP8dh.png?format=1000w" /><br>
         3590                 </a>
         3591                 
         3592               
         3593             
         3594           
         3595 
         3596           
         3597         
         3598 
         3599       
         3600 
         3601         
         3602 
         3603         
         3604 
         3605         
         3606           
         3607             
         3608               
         3609                 
         3610                 <a role="presentation" class="
         3611                     image-slide-anchor
         3612                     
         3613                     content-fill
         3614                   "
         3615                 >
         3616                   
         3617                   <img class="thumb-image" elementtiming="system-gallery-block-grid" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1615416323338-X4KSPSW9G2MGFBP3I3HW/Unity_OhzFvYutzh.png" data-image-dimensions="872x645" data-image-focal-point="0.5,0.5" alt="Unity_OhzFvYutzh.png" data-load="false" data-image-id="60494c0230e2f3294865fbcf" data-type="image" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1615416323338-X4KSPSW9G2MGFBP3I3HW/Unity_OhzFvYutzh.png?format=1000w" /><br>
         3618                 </a>
         3619                 
         3620               
         3621             
         3622           
         3623 
         3624           
         3625         
         3626 
         3627       
         3628 
         3629         
         3630 
         3631         
         3632 
         3633         
         3634           
         3635             
         3636               
         3637                 
         3638                 <a role="presentation" class="
         3639                     image-slide-anchor
         3640                     
         3641                     content-fill
         3642                   "
         3643                 >
         3644                   
         3645                   <img class="thumb-image" elementtiming="system-gallery-block-grid" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1615416325198-O2NS0KDCYI6PBIV85QAU/Unity_orHisuWDCl.png" data-image-dimensions="872x645" data-image-focal-point="0.5,0.5" alt="Unity_orHisuWDCl.png" data-load="false" data-image-id="60494c0351e5ef74e67ef762" data-type="image" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1615416325198-O2NS0KDCYI6PBIV85QAU/Unity_orHisuWDCl.png?format=1000w" /><br>
         3646                 </a>
         3647                 
         3648               
         3649             
         3650           
         3651 
         3652           
         3653         
         3654 
         3655       
         3656 
         3657         
         3658 
         3659         
         3660 
         3661         
         3662           
         3663             
         3664               
         3665                 
         3666                 <a role="presentation" class="
         3667                     image-slide-anchor
         3668                     
         3669                     content-fill
         3670                   "
         3671                 >
         3672                   
         3673                   <img class="thumb-image" elementtiming="system-gallery-block-grid" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1615416325285-8FICKIBIK9F3CYRHVPO9/Unity_Xdjqo1kaHx.png" data-image-dimensions="872x645" data-image-focal-point="0.5,0.5" alt="Unity_Xdjqo1kaHx.png" data-load="false" data-image-id="60494c042e18cc7ab988e55f" data-type="image" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1615416325285-8FICKIBIK9F3CYRHVPO9/Unity_Xdjqo1kaHx.png?format=1000w" /><br>
         3674                 </a>]]></description></item><item><title>Fast Domain Reloads in Unity</title><category>Article</category><dc:creator>John Austin</dc:creator><pubDate>Tue, 27 Oct 2020 15:16:28 +0000</pubDate><link>https://johnaustin.io/articles/2020/domain-reloads-in-unity</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:5f7dcb12ecabfd5e06f2e217</guid><description><![CDATA[Whenever you make a change in a Unity project, the Editor will freeze while 
         3675 it swaps in your new code. This process is called a Domain Reload. While it 
         3676 only takes a few seconds in an small project, domain reloads are the bane 
         3677 of all large Unity projects. As a project grows, reloads can take 10 
         3678 seconds, 20 seconds, and sometimes minutes.
         3679 
         3680 But this doesn’t have to be the case. As an engineer, you have more control 
         3681 over your domain reload times than you might think. Most projects should be 
         3682 able to achieve a 5 second reload time, almost regardless of overall size.
         3683 
         3684 So let’s dig into this a bit, deeper than we usually go. What are the main 
         3685 components of the Domain Reload, and how can we achieve those golden 5 
         3686 second iteration times?]]></description><content:encoded><![CDATA[<p class="">Whenever you make a change in a Unity project, the Editor will freeze while it swaps in your new code. This process is called a <a href="https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.unload?view=netcore-3.1">Domain Reload</a>. While it only takes a few seconds in an small project, domain reloads are the bane of all large Unity projects. As a project grows, reloads can take 10 seconds, 20 seconds, and sometimes minutes. </p><p class="">But this doesn’t have to be the case. As an engineer, you have more control over your domain reload times than you might think. <em>Most </em>projects should be able to achieve a <strong>5 second</strong> reload time, almost regardless of overall size.</p><p class="">So let’s dig into this a bit, deeper than we usually go. What are the main components of the Domain Reload, and how can we achieve those golden 5 second iteration times? </p><h1>The Anatomy of a Domain Reload</h1><p class="">There are a few main phases to code compilation:</p><ol data-rte-list="default"><li><p class="">Find and import C# file changes.</p></li><li><p class="">Compile C# code.</p></li><li><p class="">Swap the AppDomain.</p></li><li><p class="">Run InitializeOnLoad callbacks.</p></li></ol><p class="">A long domain reload is almost always a symptom of a problem in one of these phases.  Let’s look at each of these individually. </p><h3>1. Importing C# File Changes</h3><p class="">As the number of files in a project grows, Unity spends more and more time scanning those files for changes. The time spent in this step is grows with respect to the number of files in the project. </p><p class="">Luckily, Unity has been working on this problem. In the latest Unity 2020.1 there is a new option: Preferences &gt; General &gt; <a href="https://unity.com/releases/2020-1/editor-team-workflows#asset-import-pipeline-v2">Directory Monitoring</a>. Checking this box will cause Unity to watch for new changes using the underlying OS APIs, rather than scanning the entire Assets folder for updated files. </p><p class="">With this feature, asset scanning times should drop to nearly <strong>zero.</strong></p><h3>2. C# Compilation</h3><p class="">Once Unity knows which files have changed, it must compile the assembly for the changed files, as well as recompile any code that <em>depends </em>on those assemblies. This can take a long time if your change causes a large percentage of the code to be recompiled. </p><p class="">This is where the value of <code>.asmdef</code> files really comes into play. You should architect your project such that <strong>most</strong> code changes happen in Assemblies with few dependencies. This can be tricky, but there is some low hanging fruit: </p><ul data-rte-list="default"><li><p class="">Create a <code>Core</code> assembly, and place gameplay code in a few different leaf-node assemblies, each depending on <code>Core</code>.</p></li><li><p class="">Make sure installed assets and plugins are in a separate folder (ie. the Plugins folder).</p></li></ul><p class="">Doing this correctly, most code changes should only touch a few assemblies, and the code compilation should take no more than <strong>1-2 seconds</strong>. </p><p class="">There are a few tools to help debug this step. For instance, <a href="https://openupm.com/packages/com.needle.compilation-visualizer/">Compilation Time Visualizer</a>.</p><h3>3. Swap the AppDomain</h3><p class="">This is the big one, and the most opaque. Unity doesn’t do a great job of documenting <em>what actually happens</em> during a Domain Reload. However, they have released a fantastic profiling tool, the <a href="https://forum.unity.com/threads/introducing-the-editor-iteration-profiler.908390/">Editor Iteration Profiler</a>. Using this we can dig in a bit, to see what is actually taking so long.</p><p class="">As it turns out, the <code>AppDomain.Unload</code> and <code>AppDomain.Load</code> calls are not the problem. Those are fairly fast — on the order of hundreds of milliseconds. The majority of the time in this phase is actually spent saving and restoring managed objects. </p><p class="">What does that mean? All C# objects (classes, structs, etc) are tied to an AppDomain that created them. When Unity reloads the AppDomain, those old objects are permanently destroyed.  So to maintain the state of the Editor process, Unity finds all <code>UnityEngine.Object</code> or <code>[Serializable]</code> objects that are currently alive, serializes them, and restores them after the AppDomain has been swapped.</p><p class="">This serialization can take <strong>ages</strong>. And worse, <span>it is linear with the number of objects currently alive in the Editor process</span>. This explains why Domain Reload times tend to grow as the Editor stays open: as more objects are created, Unity must save/load more state.</p><p class="">As a project grows, we need to keep a close watch on the total memory footprint of objects in the Editor. Luckily, the Unity tools team is at it again. There is the <a href="https://docs.unity3d.com/Packages/com.unity.memoryprofiler@0.2/manual/index.html">Memory Profiler</a> package, which lets us do just that.  Here’s what a capture of a recent project looks like:</p>
         3687 
         3688 
         3689 
         3690 
         3691 
         3692 
         3693 
         3694 
         3695 
         3696 
         3697 
         3698 
         3699 
         3700 
         3701 
         3702 
         3703 
         3704 
         3705 
         3706 
         3707 
         3708   
         3709   
         3710 
         3711 
         3712 
         3713 
         3714 
         3715 
         3716 
         3717 
         3718 
         3719 
         3720 
         3721 
         3722 
         3723 
         3724 
         3725 
         3726 
         3727 
         3728 
         3729 
         3730 
         3731 
         3732 
         3733 
         3734 
         3735 
         3736 
         3737 
         3738 
         3739 
         3740 
         3741 
         3742 
         3743 
         3744 
         3745 
         3746 
         3747 
         3748 
         3749 
         3750 
         3751 
         3752 
         3753 
         3754 
         3755 
         3756   
         3757 
         3758     
         3759   
         3760     
         3761 
         3762       
         3763 
         3764       
         3765         <figure class="
         3766               sqs-block-image-figure
         3767               intrinsic
         3768             "
         3769         >
         3770           
         3771         
         3772         
         3773 
         3774         
         3775           
         3776             
         3777           
         3778             
         3779                 
         3780                 
         3781                 
         3782                 
         3783                 
         3784                 
         3785                 
         3786                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1602084302570-SYAMGMMOIWPSBOFXSQD9/Unity_oZ8zpbpHvr.png" data-image-dimensions="2500x1523" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1602084302570-SYAMGMMOIWPSBOFXSQD9/Unity_oZ8zpbpHvr.png?format=1000w" width="2500" height="1523" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1602084302570-SYAMGMMOIWPSBOFXSQD9/Unity_oZ8zpbpHvr.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1602084302570-SYAMGMMOIWPSBOFXSQD9/Unity_oZ8zpbpHvr.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1602084302570-SYAMGMMOIWPSBOFXSQD9/Unity_oZ8zpbpHvr.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1602084302570-SYAMGMMOIWPSBOFXSQD9/Unity_oZ8zpbpHvr.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1602084302570-SYAMGMMOIWPSBOFXSQD9/Unity_oZ8zpbpHvr.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1602084302570-SYAMGMMOIWPSBOFXSQD9/Unity_oZ8zpbpHvr.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1602084302570-SYAMGMMOIWPSBOFXSQD9/Unity_oZ8zpbpHvr.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         3787 
         3788             
         3789           
         3790         
         3791           
         3792         
         3793 
         3794         
         3795       
         3796         </figure>
         3797       
         3798 
         3799     
         3800   
         3801 
         3802 
         3803   
         3804 
         3805 
         3806 
         3807 
         3808 
         3809   <p class="">This is a <a href="https://en.wikipedia.org/wiki/Treemapping">treemap</a> visualization of all managed C# objects in the current AppDomain. Notice the 96mb byte[] array. In this case, serializing this one byte array was adding 1-2 seconds of time to the domain reload! This array was being generated by a custom importer for a large asset file. </p><p class="">Memory Profiler also lets you <em>diff</em> between two snapshots. It’s a great idea to take regular snapshots, to keep track of how the memory footprint of your project grows over time, or in different scenes.</p><p class="">There is low hanging fruit here:</p><ul data-rte-list="default"><li><p class="">Avoid large, serializable data in Unity Objects. </p></li><li><p class="">Assets objects are loaded lazily, as they are needed (ex: clicking an asset in the project window). A few careful calls to <code><a href="https://docs.unity3d.com/ScriptReference/Resources.UnloadUnusedAssets.html">AssetDatabase.UnloadUnusedAssets</a></code> can reduce the total memory footprint dramatically.</p></li><li><p class="">Avoid heavy calls in <a href="OnAfterDeserialize">OnAfterDeserialize</a>/<a href="https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.OnBeforeSerialize.html">OnBeforeSerialize</a>, as well as in constructors and functions like <code>Awake</code> (if an object is ExecuteInEditMode). These methods are called during serialization, and run during Domain Reload for all Objects.</p></li><li><p class="">Try to limit the overall size of scenes. All objects and scripts a scene depends on will contribute to your memory footprint. Splitting up scenes can reduce this.</p></li><li><p class="">Keep windows closed, unless you need them! There’s no need to have objects from those windows floating around, taking up serialization time.</p></li></ul><p class="">You don’t need to worry about asset size. Native assets like textures, models, audio, or any other data that is stored in the native layer (ie. the C++ core of Unity) do not get serialized during this phase. What matters is the size of the C# classes.</p><p class="">At the end of the day, this step is likely going to take a second or two. But at least it can be lowered with a bit of careful work.</p><h3>4. Run InitializeOnLoad Callbacks</h3><p class="">After a domain reload, Unity calls all functions tagged with <code>[InitializeOnLoad]</code>. This is the phase that most people have already optimized, but can still take a bit of time. The best you can do here is either optimize these functions, or remove them. A few notable examples:</p><ul data-rte-list="default"><li><p class="">The new Unity Input System takes ~800ms to <a href="https://forum.unity.com/threads/inputsystem-adding-1-second-to-domain-reload.984281/">reload</a> and recreate input devices.</p></li><li><p class="">Empty folder scanners (like <a href="https://assetstore.unity.com/packages/tools/utilities/maintainer-32199">Maintainer</a>) can take ~350ms to scan the Assets directory.</p></li><li><p class="">Packages for editors like Rider and Visual Studio often take a bit of time to regenerate solution files. </p></li></ul><p class="">There’s already a good amount of information on this phase, so I won’t cover it more.</p><h3>Bonus: Miscellaneous Operations</h3><p class="">There’s a few other tasks that Unity does when reloading the domain. Most are small, but they can add up:</p><ul data-rte-list="default"><li><p class="">Unity immediately repaints the Editor UI after reload. Normally this is pretty fast (~200ms), but if you have complicated editor windows, this could be a bottleneck.</p></li><li><p class="">Unity calls the constructors for EditorWindow windows. Keep heavy code out of here.</p></li><li><p class="">Unity calls OnPostProcessAllAssets (for the changed C# files). </p></li><li><p class="">Unity regenerates the <a href="https://docs.unity3d.com/ScriptReference/TypeCache.html">TypeCache</a>. This takes ~300ms, depending on number of types in your assemblies. (Using this class saves time in the long run, however)</p></li></ul><h2>Conclusion</h2><p class="">Long iteration times are brutal, and tricky to diagnose. But, don’t despair! <em>Profile it</em>! In a recent large project, we were able to reduce iteration times from ~12 seconds, all the way down to 5 seconds. You don’t <em>have</em> to live with it! </p><p class="">Happy to answer questions: reach out on <a href="https://twitter.com/kleptine">Twitter</a>, or post a comment below.</p>]]></content:encoded></item><item><title>Running Unity 2020.1 in Docker</title><category>Article</category><dc:creator>John Austin</dc:creator><pubDate>Mon, 24 Aug 2020 19:58:43 +0000</pubDate><link>https://johnaustin.io/articles/2020/running-unity-20201-in-docker</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:5f440bdc3759d66b0fd8538a</guid><description><![CDATA[<p class="">With every new update, Unity changes their command line interface a bit, and this is still the case with 2020.1. I spent a few hours getting Unity up and running again within Linux Docker, and wanted to document it for those that follow.</p><p class="">Running Unity within Docker is current unsupported, but seems to work if you know the correct incantation of commands. You can read our general approach in <a href="https://johnaustin.io/articles/2020/automated-unity-builds-at-a-stranger-gravity">this</a> previous article. If you already had Unity 2019.3 running properly in Docker, here are the changes we made to our Docker setup for 2020.1:</p><ol data-rte-list="default"><li><p class="">Activation now requires a special flag to get the .alf. I followed <a href="https://gitlab.com/gableroux/unity3d-gitlab-ci-example#b-locally">these</a> steps for activation, generally, but here’s the modified command I ran to get the .alf XML string:</p><p class=""><code>/opt/Unity/Editor/Unity -batchmode -nographics -manualActivation -logFile /dev/stdout -username myemail@gmail.com -password mypassword</code></p><p class=""><br></p></li><li><p class="">When activating, you must <strong>not</strong> run Unity with xvfb. It will cause a segfault. Use <code>-nographics</code> instead:</p><p class=""><code>/opt/Unity/Editor/Unity -batchmode -nographics</code></p><p class=""><br></p></li><li><p class="">Once you’ve uploaded the .alf file to Unity’s website, and downloaded the .ulf license file, you can specify the license for subsequent Unity runs using the new flag: <code>-manualLicenseFile</code></p><p class="">However, we don’t use this flag, instead we manually inject the license file at the correct path:</p><p class=""><strong>Root User:  </strong><code>/root/.local/share/unity3d/Unity/Unity_lic.ulf</code></p><p class=""><strong>Normal User: </strong><code>/home/username/.local/share/unity3d/Unity/Unity_lic.ulf</code></p></li></ol><p data-rte-preserve-empty="true" class=""></p><p class="">If you’re interested in the docker files we use to build our Unity images, here they are. They will, however, need a bit of modification for your own build processes.</p><ul data-rte-list="default"><li><p class=""><a href="https://johnaustin.io/s/unity_clean_unactivated.Dockerfile">unity_clean_unactivated.Dockerfile</a> — builds an Ubuntu image with Unity installed, but unactivated</p></li><li><p class=""><a href="https://johnaustin.io/s/unity_clean.Dockerfile">unity_clean.Dockerfile</a> — consumes the previously built image and activates Unity with a .alf file.</p><p data-rte-preserve-empty="true" class=""></p></li></ul><p class="">Reach out on <a href="https://twitter.com/kleptine/">Twitter</a>.</p>
         3810 
         3811 
         3812 
         3813 
         3814 
         3815 
         3816 
         3817 
         3818 
         3819 
         3820 
         3821 
         3822 
         3823 
         3824 
         3825 
         3826 
         3827 
         3828 
         3829 
         3830 
         3831   
         3832   
         3833 
         3834 
         3835 
         3836 
         3837 
         3838 
         3839 
         3840 <a href="https://johnaustin.io/?format=rss" title="Blog RSS" class="social-rss">Blog RSS</a>]]></description></item><item><title>Angular Velocity Integration in PhysX</title><category>Article</category><dc:creator>John Austin</dc:creator><pubDate>Sat, 09 May 2020 02:23:15 +0000</pubDate><link>https://johnaustin.io/articles/2020/when-an-angular-velocity-isnt-an-angular-velocity</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:5eb606ced4d3cc5898b643af</guid><description><![CDATA[I came across some odd behavior when debugging some of the core physics 
         3841 code in the Unity engine.]]></description><content:encoded><![CDATA[<p class="">I came across some odd behavior when debugging some of the core physics code in the Unity engine. If we start with a still rigid body, say: </p>
         3842 
         3843 
         3844 
         3845 
         3846 
         3847 
         3848 
         3849 
         3850 
         3851 
         3852 
         3853 
         3854 
         3855 
         3856 
         3857 
         3858 
         3859 
         3860 
         3861 
         3862 
         3863   
         3864   
         3865 
         3866 
         3867 
         3868 <pre class="language-c#"><code>body.rotation:  Quaternion(0.671550453, 0.243898988, 0.07047556, 0.696108162)
         3869 body.angularVelocity: Vector3(0, 0, 0) </code></pre>
         3870 
         3871 
         3872   <p class="">and then directly set the body’s angular velocity to a new value:</p>
         3873 
         3874 
         3875 
         3876 
         3877 
         3878 
         3879 
         3880 
         3881 
         3882 
         3883 
         3884 
         3885 
         3886 
         3887 
         3888 
         3889 
         3890 
         3891 
         3892 
         3893 
         3894   
         3895   
         3896 
         3897 
         3898 
         3899 <pre class="language-csharp"><code>body.angularVelocity = new Vector3(-1.21769, -0.8254209, 0.107366793);</code></pre>
         3900 
         3901 
         3902   <p class="">The final rotation of the body after one physics step is always <strong>slightly less</strong> than it should be. If you run the math, the correct rotation after a time step of <code>1/72</code> seconds is:</p>
         3903 
         3904 
         3905 
         3906 
         3907 
         3908 
         3909 
         3910 
         3911 
         3912 
         3913 
         3914 
         3915 
         3916 
         3917 
         3918 
         3919 
         3920 
         3921 
         3922 
         3923 
         3924   
         3925   
         3926 
         3927 
         3928 
         3929 <pre class="language-csharp"><code>body.rotation: Quaternion(0.6714375, 0.243848488, 0.07051581, 0.696230769) // Mathematically correct, using axis-angle
         3930 body.rotation: Quaternion(0.665043, 0.24099271, 0.07277777, 0.7030958) // What Unity returns </code></pre>
         3931 
         3932 
         3933   <p class="">In this case, this is only difference of <code>1.151</code> degrees, but when the angular velocity is high, say <code>(36, 10, 11)</code>, the final Unity value can be off by 30 degrees! That’s a very noticeable amount!</p><p class=""><br></p><p class="">But.. why? </p><p class=""><br></p><p class="">I lucked out on this one. I’ve been delving into the dark secrets of quaternions for a totally separate project, and a couple months back I stumbled on an approximation for applying angular velocities to quaternions, mentioned in <a href="https://gamedev.stackexchange.com/questions/108920/applying-angular-velocity-to-quaternion">this StackOverflow post</a>:</p>
         3934 
         3935 
         3936 
         3937 
         3938 
         3939 
         3940 
         3941 
         3942 
         3943 
         3944 
         3945 
         3946 
         3947 
         3948 
         3949 
         3950 
         3951 
         3952 
         3953 
         3954 
         3955   
         3956   
         3957 
         3958 
         3959 
         3960 
         3961 
         3962 
         3963   <p class="">As it turns out, this is <strong>precisely</strong> the approximation used by PhysX for applying a rigid body’s angular velocity. If we run the numbers, we get the results we expect:</p>
         3964 
         3965 
         3966 
         3967 
         3968 
         3969 
         3970 
         3971 
         3972 
         3973 
         3974 
         3975 
         3976 
         3977 
         3978 
         3979 
         3980 
         3981 
         3982 
         3983 
         3984 
         3985   
         3986   
         3987 
         3988 
         3989 
         3990 <pre class="language-csharp"><code>body.rotation = Quaternion(0.665043354, 0.240992859, 0.0727777,  0.7030955) // Approximated formula
         3991 body.rotation = Quaternion(0.665043,    0.24099271,  0.07277777, 0.7030958) // What Unity returns
         3992 </code></pre>
         3993 
         3994 
         3995 
         3996   <p class="">Now <em>that’s </em>clean. We’re within a factor of floating point rounding! Much better. </p><p data-rte-preserve-empty="true" class=""></p><p class="">Does this matter? It does in our case. For our object grabbing code, we need to calculate the precise angular velocity to apply to a body, such that it will rotate to the orientation of the hand in the next time step. This inaccuracy made objects feel just slightly bouncy, rather than snapped to the hand (particularly noticeable under strong rotations of the wrist). </p><p data-rte-preserve-empty="true" class=""></p><p class="">I love little micro-discoveries like this. It’s one step closer to truly understanding how your game engine is put together.</p><p class=""><br>-JA<br></p><p class=""><br><br></p>]]></content:encoded></item><item><title>Fast Subsurface Scattering for the Unity URP</title><category>Technical</category><category>Article</category><dc:creator>John Austin</dc:creator><pubDate>Sat, 02 May 2020 21:34:51 +0000</pubDate><link>https://johnaustin.io/articles/2020/fast-subsurface-scattering-for-the-unity-urp</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:5eada7b5d255432802916352</guid><description><![CDATA[<p class="">We’ve been working on a project targeting the Oculus Quest, which comes with some tricky constraints. The device is essentially a beefy Android phone, but must render a 2880 × 1600 display at 72fps. Compared to a standard 1080p/30fps console game, that’s a factor of 5.3x more pixels that must be shaded per second, and on mobile-grade hardware.</p><p class="">Because of this, everything we do graphically must be extraordinarily lightweight. Our project’s target style has a softness to it, and so I’ve been looking at fast ways to add Subsurface Scattering to our materials. SS interacts with the scene lighting, so it must be written as a modification to the render pipeline itself. Luckily Unity’s URP is open source, so we can modify it right in the project!</p><p class="">If you’re just here for the code, take a look at the following branch <a href="https://github.com/AStrangerGravity/Graphics/pull/1">here</a>, which has the changes to the URP mentioned in this article.</p><h2>Subsurface Scattering in a Forward Renderer</h2><p class="">Most SS <a href="http://advances.realtimerendering.com/s2018/Efficient%20screen%20space%20subsurface%20scattering%20Siggraph%202018.pdf">implementations</a> make use of deferred rendering, adding a depth-aware blur directly onto the light buffer. We can’t afford <em>any</em> separate post-processing passes on the Oculus Quest, much less a full deferred pipeline. Instead, we can look towards how older games simulated subsurface scattering. I went with the approach mentioned in <a href="http://www.cim.mcgill.ca/~derek/files/jgt_wrap.pdf">this</a> 2011 paper: a modified wrapped shading model. By default, wrapped lighting adds energy, so I additionally included a normalization term, which is included at the bottom of the paper.</p>
         3997 
         3998 
         3999 
         4000 
         4001 
         4002 
         4003 
         4004 
         4005 
         4006 
         4007 
         4008 
         4009 
         4010 
         4011 
         4012 
         4013 
         4014 
         4015 
         4016 
         4017 
         4018   
         4019   
         4020 
         4021 
         4022 
         4023 
         4024 
         4025 
         4026 
         4027 
         4028 
         4029 
         4030 
         4031 
         4032 
         4033 
         4034 
         4035 
         4036 
         4037 
         4038 
         4039 
         4040 
         4041 
         4042 
         4043 
         4044 
         4045 
         4046 
         4047 
         4048 
         4049 
         4050 
         4051 
         4052 
         4053 
         4054 
         4055 
         4056 
         4057 
         4058 
         4059 
         4060 
         4061 
         4062 
         4063 
         4064 
         4065 
         4066   
         4067 
         4068     
         4069   
         4070     
         4071 
         4072       
         4073 
         4074       
         4075         <figure class="
         4076               sqs-block-image-figure
         4077               intrinsic
         4078             "
         4079         >
         4080           
         4081         
         4082         
         4083 
         4084         
         4085           
         4086             
         4087           
         4088             
         4089                 
         4090                 
         4091                 
         4092                 
         4093                 
         4094                 
         4095                 
         4096                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449666440-2OB3C5D298T0EV56TDM1/image-asset.png" data-image-dimensions="1080x1080" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449666440-2OB3C5D298T0EV56TDM1/image-asset.png?format=1000w" width="1080" height="1080" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449666440-2OB3C5D298T0EV56TDM1/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449666440-2OB3C5D298T0EV56TDM1/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449666440-2OB3C5D298T0EV56TDM1/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449666440-2OB3C5D298T0EV56TDM1/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449666440-2OB3C5D298T0EV56TDM1/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449666440-2OB3C5D298T0EV56TDM1/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449666440-2OB3C5D298T0EV56TDM1/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         4097 
         4098             
         4099           
         4100         
         4101           
         4102         
         4103 
         4104         
         4105           
         4106           <figcaption class="image-caption-wrapper">
         4107             <p class="">Ground-truth Blender render</p>
         4108           </figcaption>
         4109         
         4110       
         4111         </figure>
         4112       
         4113 
         4114     
         4115   
         4116 
         4117 
         4118   
         4119 
         4120 
         4121 
         4122 
         4123 
         4124 
         4125 
         4126 
         4127 
         4128 
         4129 
         4130 
         4131 
         4132 
         4133 
         4134 
         4135 
         4136 
         4137 
         4138 
         4139 
         4140 
         4141 
         4142 
         4143 
         4144 
         4145 
         4146 
         4147 
         4148 
         4149 
         4150 
         4151 
         4152 
         4153 
         4154 
         4155 
         4156 
         4157 
         4158 
         4159 
         4160 
         4161 
         4162 
         4163 
         4164   
         4165 
         4166     
         4167   
         4168     
         4169 
         4170       
         4171 
         4172       
         4173         <figure class="
         4174               sqs-block-image-figure
         4175               intrinsic
         4176             "
         4177         >
         4178           
         4179         
         4180         
         4181 
         4182         
         4183           
         4184             
         4185           
         4186             
         4187                 
         4188                 
         4189                 
         4190                 
         4191                 
         4192                 
         4193                 
         4194                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449673655-H4KORZ6BDH3OOQ8B6IGW/image-asset.png" data-image-dimensions="1080x1080" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449673655-H4KORZ6BDH3OOQ8B6IGW/image-asset.png?format=1000w" width="1080" height="1080" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449673655-H4KORZ6BDH3OOQ8B6IGW/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449673655-H4KORZ6BDH3OOQ8B6IGW/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449673655-H4KORZ6BDH3OOQ8B6IGW/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449673655-H4KORZ6BDH3OOQ8B6IGW/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449673655-H4KORZ6BDH3OOQ8B6IGW/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449673655-H4KORZ6BDH3OOQ8B6IGW/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588449673655-H4KORZ6BDH3OOQ8B6IGW/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         4195 
         4196             
         4197           
         4198         
         4199           
         4200         
         4201 
         4202         
         4203           
         4204           <figcaption class="image-caption-wrapper">
         4205             <p class="">My Unity URP with wrapped lighting</p>
         4206           </figcaption>
         4207         
         4208       
         4209         </figure>
         4210       
         4211 
         4212     
         4213   
         4214 
         4215 
         4216   
         4217 
         4218 
         4219 
         4220 
         4221 
         4222   <p class="">Wrapped lighting is a non-physical approach, and so there are a few different algorithms, each with slightly different results. The paper includes some comparisons:</p>
         4223 
         4224 
         4225 
         4226 
         4227 
         4228 
         4229 
         4230 
         4231 
         4232 
         4233 
         4234 
         4235 
         4236 
         4237 
         4238 
         4239 
         4240 
         4241 
         4242 
         4243 
         4244   
         4245   
         4246 
         4247 
         4248 
         4249 
         4250 
         4251 
         4252 
         4253 
         4254 
         4255 
         4256 
         4257 
         4258 
         4259 
         4260 
         4261 
         4262 
         4263 
         4264 
         4265 
         4266 
         4267 
         4268 
         4269 
         4270 
         4271 
         4272 
         4273 
         4274 
         4275 
         4276 
         4277 
         4278 
         4279 
         4280 
         4281 
         4282 
         4283 
         4284 
         4285 
         4286 
         4287 
         4288 
         4289 
         4290 
         4291 
         4292   
         4293 
         4294     
         4295   
         4296     
         4297 
         4298       
         4299 
         4300       
         4301         <figure class="
         4302               sqs-block-image-figure
         4303               intrinsic
         4304             "
         4305         >
         4306           
         4307         
         4308         
         4309 
         4310         
         4311           
         4312             
         4313           
         4314             
         4315                 
         4316                 
         4317                 
         4318                 
         4319                 
         4320                 
         4321                 
         4322                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588450499414-CRLKXI2SL54NN4CIO50R/image-asset.png" data-image-dimensions="1423x462" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588450499414-CRLKXI2SL54NN4CIO50R/image-asset.png?format=1000w" width="1423" height="462" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588450499414-CRLKXI2SL54NN4CIO50R/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588450499414-CRLKXI2SL54NN4CIO50R/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588450499414-CRLKXI2SL54NN4CIO50R/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588450499414-CRLKXI2SL54NN4CIO50R/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588450499414-CRLKXI2SL54NN4CIO50R/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588450499414-CRLKXI2SL54NN4CIO50R/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588450499414-CRLKXI2SL54NN4CIO50R/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         4323 
         4324             
         4325           
         4326         
         4327           
         4328         
         4329 
         4330         
         4331       
         4332         </figure>
         4333       
         4334 
         4335     
         4336   
         4337 
         4338 
         4339   
         4340 
         4341 
         4342 
         4343 
         4344 
         4345   <p class="">I preferred the look of Valve’s approach and it also matches more closely to a normal diffuse lighting in non-wrapped areas.  It uses the following formula, theta being the angle of the surface towards the light:</p>
         4346 
         4347 
         4348 
         4349 
         4350 
         4351 
         4352 
         4353 
         4354 
         4355 
         4356 
         4357 
         4358 
         4359 
         4360 
         4361 
         4362 
         4363 
         4364 
         4365 
         4366 
         4367   
         4368   
         4369 
         4370 
         4371 
         4372 <span class="latex">$$f(\theta)=0.25(\cos (\theta)+1)^{2}$$</span>
         4373 
         4374 
         4375   <p class="">The paper helpfully provides a generalized version to control the distance of the wrapping. </p>
         4376 
         4377 
         4378 
         4379 
         4380 
         4381 
         4382 
         4383 
         4384 
         4385 
         4386 
         4387 
         4388 
         4389 
         4390 
         4391 
         4392 
         4393 
         4394 
         4395 
         4396 
         4397   
         4398   
         4399 
         4400 
         4401 
         4402 <span class="latex">$$f(\theta, a)=\left\{\begin{array}{ll}
         4403 ((\cos \theta+a) /(1+a))^{1+a} & , \text { if } \theta \leq \theta_{m} \\
         4404 0 & , \text { otherwise }
         4405 \end{array}\right.$$
         4406 </script>
         4407 
         4408 
         4409   <p class="">We can implement this in the URP with a simple lighting function. I’ve added a ‘subsurface color’ parameter to the Lit shader. Additionally, I’ve left in <code>wrapped_valve</code> and <code>wrapped_simple</code> as two alternative wrapping functions you can play with.</p>
         4410 
         4411 
         4412 
         4413 
         4414 
         4415 
         4416 
         4417 
         4418 
         4419 
         4420 
         4421 
         4422 
         4423 
         4424 
         4425 
         4426 
         4427 
         4428 
         4429 
         4430 
         4431   
         4432   
         4433 
         4434 
         4435 
         4436 <pre><code class="language-hlsl">// Calculates the subsurface light radiating out from the current fragment. This is a simple approximation using wrapped lighting.
         4437 // Note: This does not use distance attenuation, as it is intented to be used with a sun light.
         4438 // Note: This does not subtract out cast shadows (light.shadowAttenuation), as it is intended to be used on non-shadowed objects. (for now)
         4439 half3 LightingSubsurface(Light light, half3 normalWS, half3 subsurfaceColor, half subsurfaceRadius) {
         4440     // Calculate normalized wrapped lighting. This spreads the light without adding energy.
         4441     // This is a normal lambertian lighting calculation (using N dot L), but warping NdotL
         4442     // to wrap the light further around an object.
         4443     //
         4444     // A normalization term is applied to make sure we do not add energy.
         4445     // http://www.cim.mcgill.ca/~derek/files/jgt_wrap.pdf
         4446 
         4447     half NdotL = dot(normalWS, light.direction);
         4448     half alpha = subsurfaceRadius;
         4449     half theta_m = acos(-alpha); // boundary of the lighting function
         4450 
         4451     half theta = max(0, NdotL + alpha) - alpha;
         4452     half normalization_jgt = (2 + alpha) / (2 * (1 + alpha));
         4453     half wrapped_jgt = (pow(((theta + alpha) / (1 + alpha)), 1 + alpha)) * normalization_jgt;
         4454 
         4455     half wrapped_valve = 0.25 * (NdotL + 1) * (NdotL + 1);
         4456     half wrapped_simple = (NdotL + alpha) / (1 + alpha);
         4457 
         4458     half3 subsurface_radiance = light.color * subsurfaceColor * wrapped_jgt;
         4459 
         4460     return subsurface_radiance;
         4461 }</code></pre>
         4462 
         4463 
         4464 
         4465 
         4466 
         4467 
         4468 
         4469 
         4470 
         4471 
         4472 
         4473 
         4474 
         4475 
         4476 
         4477 
         4478 
         4479 
         4480 
         4481 
         4482 
         4483 
         4484 
         4485 
         4486 
         4487 
         4488 
         4489 
         4490 
         4491 
         4492 
         4493 
         4494 
         4495 
         4496 
         4497 
         4498 
         4499 
         4500 
         4501 
         4502 
         4503 
         4504   
         4505 
         4506     
         4507   
         4508     
         4509 
         4510       
         4511 
         4512       
         4513         <figure class="
         4514               sqs-block-image-figure
         4515               intrinsic
         4516             "
         4517         >
         4518           
         4519         
         4520         
         4521 
         4522         
         4523           
         4524             
         4525           
         4526             
         4527                 
         4528                 
         4529                 
         4530                 
         4531                 
         4532                 
         4533                 
         4534                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588452658248-EDB3F9M2L1O46TQIF0WB/image-asset.png" data-image-dimensions="546x415" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588452658248-EDB3F9M2L1O46TQIF0WB/image-asset.png?format=1000w" width="546" height="415" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588452658248-EDB3F9M2L1O46TQIF0WB/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588452658248-EDB3F9M2L1O46TQIF0WB/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588452658248-EDB3F9M2L1O46TQIF0WB/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588452658248-EDB3F9M2L1O46TQIF0WB/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588452658248-EDB3F9M2L1O46TQIF0WB/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588452658248-EDB3F9M2L1O46TQIF0WB/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588452658248-EDB3F9M2L1O46TQIF0WB/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         4535 
         4536             
         4537           
         4538         
         4539           
         4540         
         4541 
         4542         
         4543           
         4544           <figcaption class="image-caption-wrapper">
         4545             <p class="">JGT wrapped (and colored) lighting</p>
         4546           </figcaption>
         4547         
         4548       
         4549         </figure>
         4550       
         4551 
         4552     
         4553   
         4554 
         4555 
         4556   
         4557 
         4558 
         4559 
         4560 
         4561 
         4562   <p class="">With just this wrapped lighting, we do get a softer look, but it’s quite a far cry from the target. As it happens, this model <em>is</em> correct, but only if all of the light scatters into the object. However, the key subsurface scattering look (as seen in the above Blender render) comes from the <em>mixing</em> of scattered light and normal PBR diffuse lighting. Most objects will only scatter some portion of the light through the object, with the rest bouncing off as specular or diffuse.</p><p class="">We can simulate this by blending the subsurface lighting with the normal URP lighting routine. Blending rather than adding makes sure we don’t add energy:</p>
         4563 
         4564 
         4565 
         4566 
         4567 
         4568 
         4569 
         4570 
         4571 
         4572 
         4573 
         4574 
         4575 
         4576 
         4577 
         4578 
         4579 
         4580 
         4581 
         4582 
         4583 
         4584   
         4585   
         4586 
         4587 
         4588 
         4589 <pre><code class="language-hlsl">half3 mainLightContribution = LightingPhysicallyBased(brdfData, mainLight, inputData.normalWS, inputData.viewDirectionWS);
         4590 half3 subsurfaceContribution = LightingSubsurface(mainLight, inputData.normalWS, _SubsurfaceColor, _SubsurfaceRadius);
         4591 
         4592 // '_SubsurfaceScattering' controls the portion of the direct light that scatters within the object.
         4593 // When 1, all light is scattered within the object, so the full contribution of color comes from the subsurface.
         4594 // When .5, some light is scattered within, picking up the subsurface color, and is added to the normal reflectance of the surface.
         4595 color += mainLightContribution * (1-_SubsurfaceScattering);
         4596 color += subsurfaceContribution * (_SubsurfaceScattering);</code></pre>
         4597 
         4598 
         4599 
         4600 
         4601 
         4602 
         4603 
         4604 
         4605 
         4606 
         4607 
         4608 
         4609 
         4610 
         4611 
         4612 
         4613 
         4614 
         4615 
         4616 
         4617 
         4618 
         4619 
         4620 
         4621 
         4622 
         4623 
         4624 
         4625 
         4626 
         4627 
         4628 
         4629 
         4630 
         4631 
         4632 
         4633 
         4634 
         4635 
         4636 
         4637 
         4638 
         4639   
         4640 
         4641     
         4642   
         4643     
         4644 
         4645       
         4646 
         4647       
         4648         <figure class="
         4649               sqs-block-image-figure
         4650               intrinsic
         4651             "
         4652         >
         4653           
         4654         
         4655         
         4656 
         4657         
         4658           
         4659             
         4660           
         4661             
         4662                 
         4663                 
         4664                 
         4665                 
         4666                 
         4667                 
         4668                 
         4669                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453209430-JBZJYPKUISDSVUB9SVLW/image-asset.png" data-image-dimensions="1080x1080" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453209430-JBZJYPKUISDSVUB9SVLW/image-asset.png?format=1000w" width="1080" height="1080" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453209430-JBZJYPKUISDSVUB9SVLW/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453209430-JBZJYPKUISDSVUB9SVLW/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453209430-JBZJYPKUISDSVUB9SVLW/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453209430-JBZJYPKUISDSVUB9SVLW/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453209430-JBZJYPKUISDSVUB9SVLW/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453209430-JBZJYPKUISDSVUB9SVLW/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453209430-JBZJYPKUISDSVUB9SVLW/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         4670 
         4671             
         4672           
         4673         
         4674           
         4675         
         4676 
         4677         
         4678       
         4679         </figure>
         4680       
         4681 
         4682     
         4683   
         4684 
         4685 
         4686   
         4687 
         4688 
         4689 
         4690 
         4691 
         4692 
         4693 
         4694 
         4695 
         4696 
         4697 
         4698 
         4699 
         4700 
         4701 
         4702 
         4703 
         4704 
         4705 
         4706 
         4707 
         4708 
         4709 
         4710 
         4711 
         4712 
         4713 
         4714 
         4715 
         4716 
         4717 
         4718 
         4719 
         4720 
         4721 
         4722 
         4723 
         4724 
         4725 
         4726 
         4727 
         4728 
         4729 
         4730 
         4731 
         4732   
         4733 
         4734     
         4735   
         4736     
         4737 
         4738       
         4739 
         4740       
         4741         <figure class="
         4742               sqs-block-image-figure
         4743               intrinsic
         4744             "
         4745         >
         4746           
         4747         
         4748         
         4749 
         4750         
         4751           
         4752             
         4753           
         4754             
         4755                 
         4756                 
         4757                 
         4758                 
         4759                 
         4760                 
         4761                 
         4762                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453241779-PSOM87GI4AIVP7EQMFUX/image-asset.png" data-image-dimensions="1080x1080" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453241779-PSOM87GI4AIVP7EQMFUX/image-asset.png?format=1000w" width="1080" height="1080" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453241779-PSOM87GI4AIVP7EQMFUX/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453241779-PSOM87GI4AIVP7EQMFUX/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453241779-PSOM87GI4AIVP7EQMFUX/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453241779-PSOM87GI4AIVP7EQMFUX/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453241779-PSOM87GI4AIVP7EQMFUX/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453241779-PSOM87GI4AIVP7EQMFUX/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1588453241779-PSOM87GI4AIVP7EQMFUX/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         4763 
         4764             
         4765           
         4766         
         4767           
         4768         
         4769 
         4770         
         4771       
         4772         </figure>
         4773       
         4774 
         4775     
         4776   
         4777 
         4778 
         4779   
         4780 
         4781 
         4782 
         4783 
         4784 
         4785   <p class="">This is the final look, on a ball and suzanne. Pretty close to the blender render! As it turns out, wrapped lighting is quite a good approximation for subsurface scattering on a sphere. On a more complicated mesh, not so much, but I think it still looks pretty good. </p><p class="">If you’re interested in taking a look at the code, I’ve hosted our branch with subsurface scattering on <a href="https://github.com/AStrangerGravity/Graphics/pull/1">Github</a>. Note that for our project, we only need to support a single sun light, and so I’ve only implemented this effect for the directional light. However, it should be fairly easy to add similar support for point lights.</p><p class="">One final note. This is just one part of a full subsurface scattering model, simulating the scattering of light along the surface of an object (bleeding). However, many objects exhibit <em>translucency</em>, where you can see the light <em>through</em> an object. A great technique for this from the Frostbite Engine has already been described by <a href="https://www.alanzucconi.com/2017/08/30/fast-subsurface-scattering-1/">Alan Zucconi</a>.</p><p class="">- JA</p><p data-rte-preserve-empty="true" class=""></p><p class=""><br><br><br></p><p class=""><br><br><br></p>]]></description></item><item><title>Fast, Automated Builds For Unity</title><category>Technical</category><category>Article</category><dc:creator>John Austin</dc:creator><pubDate>Sat, 25 Apr 2020 21:08:42 +0000</pubDate><link>https://johnaustin.io/articles/2020/automated-unity-builds-at-a-stranger-gravity</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:5e922cf92121991619dbee76</guid><description><![CDATA[<p class="">We’ve recently been putting work into quick automated builds for our Unity projects at A Stranger Gravity. Our builds, which run Unity tests and build a Windows standalone, are now down to 2 minutes, using a combination of Docker, Google Cloud, and Azure Pipelines. </p><p class="">This kind of work involves stumbling through arcane documentation and using services that <strong>really</strong> don’t want you to have 20 Gb of data flying around for every build. I thought it would be helpful to consolidate information into something like an automated build bible. Hopefully someone in the future will find this helpful as they tackle this process themselves.</p>
         4786 
         4787 
         4788 
         4789 
         4790 
         4791 
         4792 
         4793 
         4794 
         4795 
         4796 
         4797 
         4798 
         4799 
         4800 
         4801 
         4802 
         4803 
         4804 
         4805 
         4806 
         4807   
         4808   
         4809 
         4810 
         4811 
         4812 
         4813 
         4814 
         4815 
         4816 
         4817 
         4818 
         4819 
         4820 
         4821 
         4822 
         4823 
         4824 
         4825 
         4826 
         4827 
         4828 
         4829 
         4830 
         4831 
         4832 
         4833 
         4834 
         4835 
         4836 
         4837 
         4838 
         4839 
         4840 
         4841 
         4842 
         4843 
         4844 
         4845 
         4846 
         4847 
         4848 
         4849 
         4850 
         4851 
         4852 
         4853 
         4854 
         4855   
         4856 
         4857     
         4858   
         4859     
         4860 
         4861       
         4862 
         4863       
         4864         <figure class="
         4865               sqs-block-image-figure
         4866               intrinsic
         4867             "
         4868         >
         4869           
         4870         
         4871         
         4872 
         4873         
         4874           
         4875             
         4876           
         4877             
         4878                 
         4879                 
         4880                 
         4881                 
         4882                 
         4883                 
         4884                 
         4885                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1587848296384-3FLTA4EOR0TYJT5VYE0I/chrome_ChBOCqSbsE.png" data-image-dimensions="1472x842" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1587848296384-3FLTA4EOR0TYJT5VYE0I/chrome_ChBOCqSbsE.png?format=1000w" width="1472" height="842" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1587848296384-3FLTA4EOR0TYJT5VYE0I/chrome_ChBOCqSbsE.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1587848296384-3FLTA4EOR0TYJT5VYE0I/chrome_ChBOCqSbsE.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1587848296384-3FLTA4EOR0TYJT5VYE0I/chrome_ChBOCqSbsE.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1587848296384-3FLTA4EOR0TYJT5VYE0I/chrome_ChBOCqSbsE.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1587848296384-3FLTA4EOR0TYJT5VYE0I/chrome_ChBOCqSbsE.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1587848296384-3FLTA4EOR0TYJT5VYE0I/chrome_ChBOCqSbsE.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1587848296384-3FLTA4EOR0TYJT5VYE0I/chrome_ChBOCqSbsE.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         4886 
         4887             
         4888           
         4889         
         4890           
         4891         
         4892 
         4893         
         4894       
         4895         </figure>
         4896       
         4897 
         4898     
         4899   
         4900 
         4901 
         4902   
         4903 
         4904 
         4905 
         4906 
         4907 
         4908   <h3><br>Table of Contents:</h3><ol data-rte-list="default"><li><p class="">High Level Workflow</p></li><li><p class="">Why..</p><ol data-rte-list="default"><li><p class="">Why Docker?</p></li><li><p class="">Why Azure and GCP?</p></li></ol></li><li><p class="">Quirks</p><ol data-rte-list="default"><li><p class="">Unity Quirks</p></li><li><p class="">Docker Quirks</p></li><li><p class="">Azure Quirks</p></li></ol></li></ol>
         4909 
         4910 
         4911 
         4912 
         4913 
         4914 
         4915 
         4916 
         4917 
         4918 
         4919 
         4920 
         4921 
         4922 
         4923 
         4924 
         4925 
         4926 
         4927 
         4928 
         4929 
         4930   
         4931   
         4932 
         4933 
         4934 
         4935 <hr />
         4936 
         4937 
         4938   <h1>The Workflow</h1><p class="">At a high level, our workflow is similar to traditional software development. We build within <a href="https://www.docker.com/resources/what-container">Docker</a> images on <a href="https://cloud.google.com/compute">Google Cloud</a> instances, with a agents managed by <a href="https://azure.microsoft.com/en-us/services/devops/pipelines/">Azure Pipelines</a>.</p><ol data-rte-list="default"><li><p class="">Builds trigger with Azure Pipelines from Git commits made to <code>master</code> and by Github PR checks.</p></li><li><p class="">A Linux-box on Google Cloud Platform performs the build, running the Azure agent.</p></li><li><p class="">Builds execute within a custom Docker image, with the Library folder pre-populated.</p></li></ol><p class="">Our “Quick Check” build runs in 5 minutes, executing tests and making a Windows executable.</p>
         4939 
         4940 
         4941 
         4942 
         4943 
         4944 
         4945 
         4946 
         4947 
         4948 
         4949 
         4950 
         4951 
         4952 
         4953 
         4954 
         4955 
         4956 
         4957 
         4958 
         4959 
         4960   
         4961   
         4962 
         4963 
         4964 
         4965 <hr />
         4966 
         4967 
         4968   <h2>Why Docker?</h2><p class="">We use docker because it makes it easy, and <em>very quick</em> to boot up a clean environment for our builds. In the past I’ve maintained build servers that maintain state between builds, and inevitably these servers drift into a bad state. Docker gives us the peace of mind that our builds are clean. If your change works on the build server, then it’s a local issue, end of story.</p><p class="">Docker makes it incredibly easy to debug issues. You can simply boot up and exact replica of the build machine on your local computer to debug. You can very quickly boot up a different cloud instance if you need extra build capacity, or you want to try out a more powerful machine.</p><p class="">Lastly, Docker is extremely fast. It uses clever caching to “boot” a new machine environment in just seconds, even if the docker image is many Gb of data. This means we can load a clean machine for every build, essentially for free. </p><p class="">If you’re not familiar with docker, I’d suggest reading a bit about it. It’s not a common tool in the game development sphere, but is incredibly useful.</p><h2>Why Azure and Google Cloud?</h2><p class="">A CI like Azure means that all of our build artifacts and logs are easily accessible on the web. You don’t have to figure out how to run a Jenkins instance securely on the public web — it just works. Azure is not particularly special, but we chose it primarily because of the strong .NET support (Unity) and that it had a reliable backend from a large company. Azure additionally had a plentiful free tier for getting started.</p><p class="">As for the build agents themselves, you can get away with using the hosted agents, but you’ll need self-hosting for any meaningful performance. Azure agents, like other CI services, are limited to roughly 10gb of disk space — nowhere near enough for a Unity project. The machines they run on are also quite weak.</p><p class="">We use a GCP instance setup with a <a href="https://cloud.google.com/local-ssd">Local SSD</a> to run our builds. It’s a bit pricey. We had a bunch of GCP credits sitting around from an old venture, and it was a good way to make use of them. An easy secondary option is just to set up a build machine at home. Any machine can be a build agent. A cloud instance is nice, however, because:</p><ol data-rte-list="default"><li><p class="">Server maintenance is real. Things break, machines don’t connect, etc. We’re a remote company and having a reliable instance in the cloud is a big plus.</p></li><li><p class="">Easier upgrades. We can swap disks / images / wipe the whole machine in minutes. This keeps us closer to a clean slate for our builds.</p></li><li><p class="">Power costs money! A personal server isn’t free — power costs can be quite high if you’re not careful. </p></li></ol><p class="">We’ve tried different instance types. Unity importing is largely dependent on disk speed and CPU performance, so the more cores you can afford, the better. As a note, the “High CPU” instances on GCP are not actually faster for single-threaded performance. They do offer better multi-threaded performance, which is only useful for parts of the Unity import process and shader compilation. We’ve found that a high-end personal machine will often beat out some of the beefiest cloud instances. </p>
         4969 
         4970 
         4971 
         4972 
         4973 
         4974 
         4975 
         4976 
         4977 
         4978 
         4979 
         4980 
         4981 
         4982 
         4983 
         4984 
         4985 
         4986 
         4987 
         4988 
         4989 
         4990   
         4991   
         4992 
         4993 
         4994 
         4995 <hr />
         4996 
         4997 
         4998   <p class="">With these systems setup, we execute a fairly simple pipeline, invoking the Unity editor from the command line. There are a lot of gotchas, however with this process:</p><h1>Unity Build Quirks:</h1><p class=""><strong>Mono doesn’t support 64bit Android.<br></strong>Mono doesn’t support building a standalone player on 64 bit ARM. This is just a limitation of Mono, which we use to avoid IL2CPP build times (for quick debug builds). </p><p class=""><strong>Cloud Instances don’t have displays.<br></strong>Unity expects a display by default. You must pass <code>-nographics</code> to Unity to avoid hitting issues on headless servers. Note that in the past, this flag <em>did not </em>work on Linux before 2019.3, and we had to use <code>xvfb-run&nbsp;--auto-servernum&nbsp;--server-args='-screen&nbsp;0&nbsp;640x480x24'&nbsp;$UNITY_EXE</code> as a workaround, which runs the editor with a fake display. As of 4/2020, using <code>xvfb</code> is still necessary when running our Unity tests. </p><p class=""><strong>Getting Unity to log output to stdout on Linux.<br></strong>Supposedly, you can pass the argument <code>-logFile</code> with no logfile specified and Unity will output the logfile to stdout. This hasn’t ever worked for us, and we’re not sure why. It may have been deprecated as of 2019.3. There are two solutions. If you’re running the build as root user, you can simply pass <code>-logFile /dev/stdout</code>. AZP agents are not root users, so instead we setup a unix pipe, and run Unity in the background with <code>&amp;</code>: </p>
         4999 
         5000 
         5001 
         5002 
         5003 
         5004 
         5005 
         5006 
         5007 
         5008 
         5009 
         5010 
         5011 
         5012 
         5013 
         5014 
         5015 
         5016 
         5017 
         5018 
         5019 
         5020   
         5021   
         5022 
         5023 
         5024 
         5025 <pre data-prompt="$" class="language-bash command-line"><code>mkfifo unity_output
         5026 /path/to/Unity -logFile unity_output (.. other arguments) &
         5027 cat unity_output   # blocks until the pipe is closed by Unity!
         5028 </code></pre>
         5029 
         5030 
         5031 
         5032   <p class="">Unity must be able to open the logfile as write/read. On our system, /dev/stdout was only permissioned for write.</p><p class=""><strong>Disable the assembly updater.<br></strong>By default Unity runs the assembly updater, which tries to update out-of-date code and assemblies. This can block a build if it hits something. For a CI service, it’s better to just fail the build and alert the user. Pass the argument <code>-disable-assembly-updater</code>  to unity.</p><p class=""><strong>Burst does not cross compile.<br></strong>We run all of our builds in Linux, but the new Burst compiler <a href="https://forum.unity.com/threads/burst-does-not-support-cross-compilation-between-windows-mac-or-linux.686143/">doesn’t cross compile</a>. Supposedly you can disable burst with the <code>--burst-disable-compilation</code> Unity flag. This has never worked for us. Instead we run an extra script in our C# build script to manually change the burst settings json file, just before building. </p><p class=""><strong>Unity hangs on ‘Cleanup Mono’ after build (Unsolved)<br></strong>Sometimes, Unity will hang at the end of a standalone build. The last entry of the log file is “cleanup mono”. This is after the build has succeeded. For some reason, Unity fails to exit gracefully and just hangs. We’ve been unable to find a graceful way to handle this case. For our standalone builds we simply kill the process after a successful build:</p><p class=""><code>Process.GetCurrentProcess().Kill();</code></p><p class="">It’s messy, but it gets the job done. Because we’re running within Docker, we don’t have to worry about corrupting the state of the editor. </p><h2>Docker Quirks</h2><p class="">Unity really wasn’t built to run on Docker. There’s no official support. That said, in a Linux docker image, Unity executes fine, once you work around some strange quirks. That said, for Windows release builds you <em>will</em> need a non-docker, Windows instance. Here’s two big issues we’ve faced:</p><p class=""><strong>Unity doesn’t run in a Windows Docker container! <br></strong>Inside a Windows Docker container, Unity.exe simply <a href="https://forum.unity.com/threads/unity-in-a-windows-container.645238/">fails to start</a>, with no output. This is regardless of the the arguments you pass it, activation, etc. It just doesn’t seem like this is supported, although some people claim to have gotten it working. Windows Docker containers are also, unfortunately, a bit of a second-class citizen for Docker in general. Docker is heavily Linux focused.</p><p class=""><strong>Windows Release builds can’t run on Linux.<br></strong>This is because the new <a href="https://forum.unity.com/threads/burst-does-not-support-cross-compilation-between-windows-mac-or-linux.686143/">Burst compiler can’t cross compile</a>. This means a windows release build <em>must</em> be made on a Windows machine. If Unity functioned within a Windows Docker image, we could run that machine through docker. Unfortunately the combination of these two issues means that we’re stuck with:</p><ol data-rte-list="default"><li><p class="">Most development and release builds run on Linux in a Docker Image</p></li><li><p class="">The Windows release builds are built locally.</p></li></ol><p class="">In the future we may use a Windows box on GCP to build these release builds, but as all developers have a Windows machine, there’s not a big need.</p><h2>Azure CI Quirks</h2><p class=""><strong>Build Agents don’t run as root.<br></strong>Most CI services run their build agents as root, except Azure. Worse, the default Ubuntu docker image no longer includes <code>sudo</code> as an installed tool. We had to install sudo with a <code>RUN</code> command in our custom Docker image. </p><p class=""><strong>The Cache task is slow.<br></strong>We initially tried caching LFS and the Library folder using Azure Pipelines caching. Unfortunately when these folders are many Gb of data, uploading and downloading those caches became the dominant factor in our builds. Instead we embed a stale version the LFS and Library folders directly into our docker image, and then only download / import the changes for every build.</p><p class=""><strong>Set </strong><code>workspace: clean</code><strong> for self-hosted agents<br></strong>Azure doesn’t set this flag by default on a self-hosted agent. Make sure to set it or your builds won’t be cleaned between runs!</p><p class=""><br></p><p class=""><em>Reach out to me with questions on </em><a href="https://twitter.com/kleptine"><em>Twitter</em></a><em>.</em></p>]]></description></item><item><title>Unity Construction Scripts</title><category>Technical</category><category>Article</category><dc:creator>John Austin</dc:creator><pubDate>Mon, 16 Mar 2020 02:46:43 +0000</pubDate><link>https://johnaustin.io/articles/2020/unity-construction-scripts</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:5e6edcd78afded74f55dd52c</guid><description><![CDATA[<p class="">Unreal Engine has a convenient feature known as “Construction Scripts”: simple scripts that run when an Actor is modified, letting you quickly procedurally generate an object and see the results immediately in the editor.</p><p class="">Unity has a similar feature, <code>ExecuteInEditMode</code>, but it comes with a number of downsides:</p><ol data-rte-list="default"><li><p class="">Changes made to objects in the scene are saved to the scene file, generating nasty merge conflicts when two people modify the same scene, even if they don’t touch the generated objects.</p></li><li><p class=""><code>ExecuteInEditMode</code> can be dangerous — it’s easy to accidentally delete / modify the rest of the scene unintentionally.</p></li><li><p class="">For one-off objects, <code>ExecuteInEditMode</code> requires a ton of boilerplate, with careful coding.</p></li></ol><p data-rte-preserve-empty="true" class=""></p><p class=""><a href="https://gist.github.com/Kleptine/ac4b7db7714003f7968f4c532f0dc82d">Unity Construction Scripts</a> is a simple system which fixes these issues, and makes it quick to write small one-off procedural objects. On top of this, a ConstructionScript is entirely Editor-only. All changes are baked into the scene file just before building the standalone player.</p><p class="">Documentation is provided in the code for now, but feel free to reach out on <a href="http://twitter.com/kleptine">Twitter</a> if you have questions. </p><p class=""> </p>]]></description></item><item><title>Tragedy in the Rise of Giants</title><category>Essay</category><dc:creator>John Austin</dc:creator><pubDate>Wed, 04 Dec 2019 19:31:48 +0000</pubDate><link>https://johnaustin.io/articles/2019/tragedy-in-the-rise-and-fall-of-giants</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:5de7df871ccae03c969cabdf</guid><description><![CDATA[<figure class="
         5033               sqs-block-image-figure
         5034               intrinsic
         5035             "
         5036         >
         5037           
         5038         
         5039         
         5040 
         5041         
         5042           
         5043             
         5044           
         5045             
         5046                 
         5047                 
         5048                 
         5049                 
         5050                 
         5051                 
         5052                 
         5053                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489791109-NBBZC2BLSDTPTCQGOLKD/unnamed.jpg" data-image-dimensions="660x257" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489791109-NBBZC2BLSDTPTCQGOLKD/unnamed.jpg?format=1000w" width="660" height="257" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489791109-NBBZC2BLSDTPTCQGOLKD/unnamed.jpg?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489791109-NBBZC2BLSDTPTCQGOLKD/unnamed.jpg?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489791109-NBBZC2BLSDTPTCQGOLKD/unnamed.jpg?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489791109-NBBZC2BLSDTPTCQGOLKD/unnamed.jpg?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489791109-NBBZC2BLSDTPTCQGOLKD/unnamed.jpg?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489791109-NBBZC2BLSDTPTCQGOLKD/unnamed.jpg?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489791109-NBBZC2BLSDTPTCQGOLKD/unnamed.jpg?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         5054 
         5055             
         5056           
         5057         
         5058           
         5059         
         5060 
         5061         
         5062       
         5063         </figure>
         5064       
         5065 
         5066     
         5067   
         5068 
         5069 
         5070   
         5071 
         5072 
         5073 
         5074 
         5075 
         5076   <p class="">I first worked at Google back in 2012. Thinking back to the way the company was run then, it seems like an entirely different era. It was a place of radical transparency, where any employee could stand up at a weekly TGIF meeting, and have an earnest conversation directly with the founders. It <em>was </em>just a company, but it really felt like a company with heart. This year has been a bombshell for Google and now with Larry and Sergey finally <a href="https://www.npr.org/2019/12/03/784570156/google-founders-brin-page-step-down-pichai-takes-over-as-alphabet-ceo">leaving</a>, it inspires the question: what happened? In 2008, both had a <a href="https://www.webcitation.org/6BYbNOfxM?url=http%3A%2F%2Fmoney.cnn.com%2F2008%2F01%2F18%2Fnews%2Fcompanies%2Fgoogle.fortune%2Findex.htm">plan to work at Google until 2024</a>. What takes a company with everything going for it, and drags it down into a place with employee protests, cut benefits, and secretive government deals?</p><p class="">The most common answer I hear is “greed”, but I think the real answer is a bit more subtle. Normal people don’t wake up in the morning and decide they’d like to take a controversial government contract. All of Google’s businesses are already growing at a breakneck pace. My opinion? Google’s decline is <em>systematic</em>, not intentional. So what happened? The stock market and rapid expansion. Let me explain:</p><p data-rte-preserve-empty="true" class=""></p><p class="">Here’s a fundamental secret to how the stock market works: Stock prices aren’t driven by the <em>current </em>value of the company, they are driven by the company’s <em>future</em> value. </p><p class="">Let’s say Google is poised to grow 25% every year, for the next 10 years. Investors are smart: a long term growth like that is a great stock to purchase! Naturally, as investors buy in, this raises the price of the <em>current</em> shares. That future growth is ‘priced in’ to the stock’s current price. But here’s the odd thing: if Google simply makes good on their promise to grow, the stock price would stay flat! Investors already know that Google stock is good for a 25% return, so the demand for the stock stays flat, and so does the price.</p><p class="">This quirk means that if a company would like their stock price to continue to rise, not only do they have to grow,<em> they have to grow at an increasing rate. </em>At their earnings report it isn’t enough to be successful, they have to continuously be more successful than anyone thought they could be. </p><p class="">Let that point sink in for a moment. It’s a fundamental driving force of the stock market. </p>
         5077 
         5078 
         5079 
         5080 
         5081 
         5082 
         5083 
         5084 
         5085 
         5086 
         5087 
         5088 
         5089 
         5090 
         5091 
         5092 
         5093 
         5094 
         5095 
         5096 
         5097 
         5098   
         5099   
         5100 
         5101 
         5102 
         5103 
         5104 
         5105 
         5106 
         5107 
         5108 
         5109 
         5110 
         5111 
         5112 
         5113 
         5114 
         5115 
         5116 
         5117 
         5118 
         5119 
         5120 
         5121 
         5122 
         5123 
         5124 
         5125 
         5126 
         5127 
         5128 
         5129 
         5130 
         5131 
         5132 
         5133 
         5134 
         5135 
         5136 
         5137 
         5138 
         5139 
         5140 
         5141 
         5142 
         5143 
         5144 
         5145 
         5146   
         5147 
         5148     
         5149   
         5150     
         5151 
         5152       
         5153 
         5154       
         5155         <figure class="
         5156               sqs-block-image-figure
         5157               intrinsic
         5158             "
         5159         >
         5160           
         5161         
         5162         
         5163 
         5164         
         5165           
         5166             
         5167           
         5168             
         5169                 
         5170                 
         5171                 
         5172                 
         5173                 
         5174                 
         5175                 
         5176                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489904514-LB8HFIJ3L5ATI8RXTMUC/chrome_QHspam5IvS.png" data-image-dimensions="613x221" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489904514-LB8HFIJ3L5ATI8RXTMUC/chrome_QHspam5IvS.png?format=1000w" width="613" height="221" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489904514-LB8HFIJ3L5ATI8RXTMUC/chrome_QHspam5IvS.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489904514-LB8HFIJ3L5ATI8RXTMUC/chrome_QHspam5IvS.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489904514-LB8HFIJ3L5ATI8RXTMUC/chrome_QHspam5IvS.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489904514-LB8HFIJ3L5ATI8RXTMUC/chrome_QHspam5IvS.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489904514-LB8HFIJ3L5ATI8RXTMUC/chrome_QHspam5IvS.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489904514-LB8HFIJ3L5ATI8RXTMUC/chrome_QHspam5IvS.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489904514-LB8HFIJ3L5ATI8RXTMUC/chrome_QHspam5IvS.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         5177 
         5178             
         5179           
         5180         
         5181           
         5182         
         5183 
         5184         
         5185       
         5186         </figure>
         5187       
         5188 
         5189     
         5190   
         5191 
         5192 
         5193   
         5194 
         5195 
         5196 
         5197 
         5198 
         5199   <p class="">So how does this affect Google? If they want their stock price to increase, they must expand <em>exponentially</em>. In 2018, Alphabet added nearly 20,000 employees to the company. In 2011, the entirety of Google <em>was </em>20,000 employees [<a href="http://techland.time.com/2011/01/21/the-eric-schmidt-era-google-2001-vs-google-2011/">1</a>]. Every year, the company adds another Google. </p><p class="">With this kind of insane expansion, it’s no wonder the company is having culture problems. You can’t maintain a culture in that kind of environment. People need time to adapt and assimilate and learn how a company works. Google will be lucky if they can even get these people hired. At 20,000 employees you are running a company — you can be a bit personal with HR and management. At 100,000, you’re running an empire. Your HR department needs to be the size of a small army. Policy becomes a necessity over human interaction — humans are fallible, and at these numbers the chances of failure are too high. </p>
         5200 
         5201 
         5202 
         5203 
         5204 
         5205 
         5206 
         5207 
         5208 
         5209 
         5210 
         5211 
         5212 
         5213 
         5214 
         5215 
         5216 
         5217 
         5218 
         5219 
         5220 
         5221   
         5222   
         5223 
         5224 
         5225 
         5226 &nbsp;
         5227 
         5228 
         5229 
         5230 
         5231 
         5232 
         5233 
         5234 
         5235 
         5236 
         5237 
         5238 
         5239 
         5240 
         5241 
         5242 
         5243 
         5244 
         5245 
         5246 
         5247 
         5248 
         5249 
         5250 
         5251 
         5252 
         5253 
         5254 
         5255 
         5256 
         5257 
         5258 
         5259 
         5260 
         5261 
         5262 
         5263 
         5264 
         5265 
         5266 
         5267 
         5268 
         5269   
         5270 
         5271     
         5272   
         5273     
         5274 
         5275       
         5276 
         5277       
         5278         <figure class="
         5279               sqs-block-image-figure
         5280               intrinsic
         5281             "
         5282         >
         5283           
         5284         
         5285         
         5286 
         5287         
         5288           
         5289             
         5290           
         5291             
         5292                 
         5293                 
         5294                 
         5295                 
         5296                 
         5297                 
         5298                 
         5299                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489453364-5T72PTY8KB14YIA3HTCG/1_y18S9SWzF4sDdG1gOIHCrA.png" data-image-dimensions="976x486" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489453364-5T72PTY8KB14YIA3HTCG/1_y18S9SWzF4sDdG1gOIHCrA.png?format=1000w" width="976" height="486" sizes="(max-width: 640px) 100vw, (max-width: 767px) 66.66666666666666vw, 66.66666666666666vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489453364-5T72PTY8KB14YIA3HTCG/1_y18S9SWzF4sDdG1gOIHCrA.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489453364-5T72PTY8KB14YIA3HTCG/1_y18S9SWzF4sDdG1gOIHCrA.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489453364-5T72PTY8KB14YIA3HTCG/1_y18S9SWzF4sDdG1gOIHCrA.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489453364-5T72PTY8KB14YIA3HTCG/1_y18S9SWzF4sDdG1gOIHCrA.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489453364-5T72PTY8KB14YIA3HTCG/1_y18S9SWzF4sDdG1gOIHCrA.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489453364-5T72PTY8KB14YIA3HTCG/1_y18S9SWzF4sDdG1gOIHCrA.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1575489453364-5T72PTY8KB14YIA3HTCG/1_y18S9SWzF4sDdG1gOIHCrA.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         5300 
         5301             
         5302           
         5303         
         5304           
         5305         
         5306 
         5307         
         5308           
         5309           <figcaption class="image-caption-wrapper">
         5310             <p class="">Employee growth by years after incorporation. [<a href="https://medium.com/gabor/timeline-employee-count-growth-for-microsoft-yahoo-google-and-facebook-9ede22a37824">source</a>]</p>
         5311           </figcaption>
         5312         
         5313       
         5314         </figure>
         5315       
         5316 
         5317     
         5318   
         5319 
         5320 
         5321   
         5322 
         5323 
         5324 &nbsp;
         5325 
         5326 
         5327   <p class="">This expansion affects their deal-making as well. The ads product might be growing at 10%, but if they don’t push it to 12%, or (god forbid) the grown rate drops to 9%, they’ll be in hot water. It’s in these situations that lucrative government contracts start to become pretty tempting. Sure, those drones might cause harm, but maybe if <em>we’re</em> doing it, we can do it ethically. Better us than someone else, right? “We’ll just take one to ensure the stability of our stock price”.</p><p class="">—</p><p class="">At this point, you might say: “well, fuck the stock price”. Here lies the tragedy. When you are the size of Google, your stock price starts to have major affects on the world. The total value of Google’s stock is <a href="https://www.macrotrends.net/stocks/charts/GOOGL/alphabet/market-cap">$900 billion</a>. You stop expanding, your stock price drops 15%, and suddenly you’ve erased $135 billion from the US economy. That money doesn’t just come from the pockets of the wealthy. It comes from the retirement funds of blue collar workers, investment funds of governments, and municipalities. You directly hurt all of the employees and small investors with money tied up in Google stock. It’s not a dip that will recover, like so many stock market crashes. It only recovers if you grow. </p><p class="">That doesn’t mean Google is making ethical choices — they’re not — but I don’t envy being in the shoes of those decisions. Exponential growth is a runaway train. Someone has to figure out how to stop it. </p><p class=""><br></p><p class=""><br></p><p class=""><br></p>]]></description></item><item><title>Texture Atlasing using Houdini</title><category>Article</category><dc:creator>John Austin</dc:creator><pubDate>Tue, 22 Oct 2019 13:42:35 +0000</pubDate><link>https://johnaustin.io/articles/2019/building-a-texture-atlas-tool-in-houdini</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:5daf01acf04e4a32186df563</guid><description><![CDATA[<p class="">Houdini really is the swiss-army knife of 3D asset pipelines. Recently a friend asked around on twitter: </p>
         5328 
         5329 
         5330 
         5331 
         5332 
         5333 
         5334 
         5335 
         5336 
         5337 
         5338 
         5339 
         5340 
         5341 
         5342 
         5343 
         5344 
         5345 
         5346 
         5347 
         5348 
         5349   
         5350   
         5351 
         5352 
         5353 
         5354 &nbsp;<blockquote data-dnt="true" data-theme="light" data-link-color="#E81C4F" class="twitter-tweet"><p lang="en" dir="ltr">I&#39;d think in the year of ahh-lawwd two thousand and nineteen there would be a straightforward solution out there.... hey, take these half-dozen meshes and their associated maps and poop out UV-corrected meshes and a set of mongo-atlassed maps.</p>&mdash; Eric A Anderson (@edoublea) <a href="https://twitter.com/edoublea/status/1186489202818441217?ref_src=twsrc%5Etfw">October 22, 2019</a></blockquote> &nbsp;
         5355 
         5356 
         5357   <p class="">Houdini to the rescue! Let’s start with a few simple shapes. We’ll unwrap them individually in the simplest way possible, and then apply a simple texture to each:</p>
         5358 
         5359 
         5360 
         5361 
         5362 
         5363 
         5364 
         5365 
         5366 
         5367 
         5368 
         5369 
         5370 
         5371 
         5372 
         5373 
         5374 
         5375 
         5376 
         5377 
         5378 
         5379   
         5380   
         5381 
         5382 
         5383 
         5384 
         5385 
         5386 
         5387 
         5388 
         5389 
         5390 
         5391 
         5392 
         5393 
         5394 
         5395 
         5396 
         5397 
         5398 
         5399 
         5400 
         5401 
         5402 
         5403 
         5404 
         5405 
         5406 
         5407 
         5408 
         5409 
         5410 
         5411 
         5412 
         5413 
         5414 
         5415 
         5416 
         5417 
         5418 
         5419 
         5420 
         5421 
         5422 
         5423 
         5424 
         5425 
         5426 
         5427   
         5428 
         5429     
         5430   
         5431     
         5432 
         5433       
         5434 
         5435       
         5436         <figure class="
         5437               sqs-block-image-figure
         5438               intrinsic
         5439             "
         5440         >
         5441           
         5442         
         5443         
         5444 
         5445         
         5446           
         5447             
         5448           
         5449             
         5450                 
         5451                 
         5452                 
         5453                 
         5454                 
         5455                 
         5456                 
         5457                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571750913185-7DAI2B76AVBD0DU2QOFP/image-asset.jpeg" data-image-dimensions="1665x1271" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571750913185-7DAI2B76AVBD0DU2QOFP/image-asset.jpeg?format=1000w" width="1665" height="1271" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571750913185-7DAI2B76AVBD0DU2QOFP/image-asset.jpeg?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571750913185-7DAI2B76AVBD0DU2QOFP/image-asset.jpeg?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571750913185-7DAI2B76AVBD0DU2QOFP/image-asset.jpeg?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571750913185-7DAI2B76AVBD0DU2QOFP/image-asset.jpeg?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571750913185-7DAI2B76AVBD0DU2QOFP/image-asset.jpeg?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571750913185-7DAI2B76AVBD0DU2QOFP/image-asset.jpeg?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571750913185-7DAI2B76AVBD0DU2QOFP/image-asset.jpeg?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         5458 
         5459             
         5460           
         5461         
         5462           
         5463         
         5464 
         5465         
         5466       
         5467         </figure>
         5468       
         5469 
         5470     
         5471   
         5472 
         5473 
         5474   
         5475 
         5476 
         5477 
         5478 
         5479 
         5480 
         5481 
         5482 
         5483 
         5484 
         5485 
         5486 
         5487 
         5488 
         5489 
         5490 
         5491 
         5492 
         5493 
         5494 
         5495 
         5496 
         5497 
         5498 
         5499 
         5500 
         5501 
         5502 
         5503 
         5504 
         5505 
         5506 
         5507 
         5508 
         5509 
         5510 
         5511 
         5512 
         5513 
         5514 
         5515 
         5516 
         5517 
         5518 
         5519 
         5520   
         5521 
         5522     
         5523   
         5524     
         5525 
         5526       
         5527 
         5528       
         5529         <figure class="
         5530               sqs-block-image-figure
         5531               intrinsic
         5532             "
         5533         >
         5534           
         5535         
         5536         
         5537 
         5538         
         5539           
         5540             
         5541           
         5542             
         5543                 
         5544                 
         5545                 
         5546                 
         5547                 
         5548                 
         5549                 
         5550                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751083541-MNSUUFVOE44WF6L6K2TJ/houdini_2019-10-22_06-31-13.png" data-image-dimensions="2037x768" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751083541-MNSUUFVOE44WF6L6K2TJ/houdini_2019-10-22_06-31-13.png?format=1000w" width="2037" height="768" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751083541-MNSUUFVOE44WF6L6K2TJ/houdini_2019-10-22_06-31-13.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751083541-MNSUUFVOE44WF6L6K2TJ/houdini_2019-10-22_06-31-13.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751083541-MNSUUFVOE44WF6L6K2TJ/houdini_2019-10-22_06-31-13.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751083541-MNSUUFVOE44WF6L6K2TJ/houdini_2019-10-22_06-31-13.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751083541-MNSUUFVOE44WF6L6K2TJ/houdini_2019-10-22_06-31-13.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751083541-MNSUUFVOE44WF6L6K2TJ/houdini_2019-10-22_06-31-13.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751083541-MNSUUFVOE44WF6L6K2TJ/houdini_2019-10-22_06-31-13.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         5551 
         5552             
         5553           
         5554         
         5555           
         5556         
         5557 
         5558         
         5559           
         5560           <figcaption class="image-caption-wrapper">
         5561             <p class="">Three object with 3 separate texture maps.</p>
         5562           </figcaption>
         5563         
         5564       
         5565         </figure>
         5566       
         5567 
         5568     
         5569   
         5570 
         5571 
         5572   
         5573 
         5574 
         5575 
         5576 
         5577 
         5578   <p class="">Now, we can have Houdini merge the individual UV sets into a single UV space with the UV-Layout node:</p>
         5579 
         5580 
         5581 
         5582 
         5583 
         5584 
         5585 
         5586 
         5587 
         5588 
         5589 
         5590 
         5591 
         5592 
         5593 
         5594 
         5595 
         5596 
         5597 
         5598 
         5599 
         5600   
         5601   
         5602 
         5603 
         5604 
         5605 
         5606 
         5607 
         5608 
         5609 
         5610 
         5611 
         5612 
         5613 
         5614 
         5615 
         5616 
         5617 
         5618 
         5619 
         5620 
         5621 
         5622 
         5623 
         5624 
         5625 
         5626 
         5627 
         5628 
         5629 
         5630 
         5631 
         5632 
         5633 
         5634 
         5635 
         5636 
         5637 
         5638 
         5639 
         5640 
         5641 
         5642 
         5643 
         5644 
         5645 
         5646 
         5647 
         5648   
         5649 
         5650     
         5651   
         5652     
         5653 
         5654       
         5655 
         5656       
         5657         <figure class="
         5658               sqs-block-image-figure
         5659               intrinsic
         5660             "
         5661         >
         5662           
         5663         
         5664         
         5665 
         5666         
         5667           
         5668             
         5669           
         5670             
         5671                 
         5672                 
         5673                 
         5674                 
         5675                 
         5676                 
         5677                 
         5678                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751154140-ANU1PIDN0LCWYGBXWDBL/houdini_2019-10-22_06-32-27.png" data-image-dimensions="2035x770" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751154140-ANU1PIDN0LCWYGBXWDBL/houdini_2019-10-22_06-32-27.png?format=1000w" width="2035" height="770" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751154140-ANU1PIDN0LCWYGBXWDBL/houdini_2019-10-22_06-32-27.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751154140-ANU1PIDN0LCWYGBXWDBL/houdini_2019-10-22_06-32-27.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751154140-ANU1PIDN0LCWYGBXWDBL/houdini_2019-10-22_06-32-27.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751154140-ANU1PIDN0LCWYGBXWDBL/houdini_2019-10-22_06-32-27.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751154140-ANU1PIDN0LCWYGBXWDBL/houdini_2019-10-22_06-32-27.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751154140-ANU1PIDN0LCWYGBXWDBL/houdini_2019-10-22_06-32-27.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751154140-ANU1PIDN0LCWYGBXWDBL/houdini_2019-10-22_06-32-27.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         5679 
         5680             
         5681           
         5682         
         5683           
         5684         
         5685 
         5686         
         5687       
         5688         </figure>
         5689       
         5690 
         5691     
         5692   
         5693 
         5694 
         5695   
         5696 
         5697 
         5698 
         5699 
         5700 
         5701   <p class="">Now that the UVs are setup, we just need to transfer the original textures from the individual objects into a single packed texture in the new UV space. Luckily, there’s a simple node to do that in the Houdini Game Development Toolkit: GameDev Maps Baker. The Maps Baker takes a ‘High Resolution’ geometry and a ‘Low Resolution’ geometry, and transfers the textures from one to the other. In our case, the high resolution and low resolution geometries are exactly the same, except for the uv layout.</p><p class="">Note that the use of the Quick Material nodes for initial texturing of each object is crucial, as this allows us to specify the ‘diffuse’ channel in the baker and have it track down the correct texture information.</p><p class="">The other important setting is to choose “Nearest Surface” on the <em>Tracing Mode</em> option in the baker. Because our meshes are exactly the same, this transfers the texture perfectly, rather than casting rays to find the closest points.</p>
         5702 
         5703 
         5704 
         5705 
         5706 
         5707 
         5708 
         5709 
         5710 
         5711 
         5712 
         5713 
         5714 
         5715 
         5716 
         5717 
         5718 
         5719 
         5720 
         5721 
         5722 
         5723   
         5724   
         5725 
         5726 
         5727 
         5728 
         5729 
         5730 
         5731 
         5732 
         5733 
         5734 
         5735 
         5736 
         5737 
         5738 
         5739 
         5740 
         5741 
         5742 
         5743 
         5744 
         5745 
         5746 
         5747 
         5748 
         5749 
         5750 
         5751 
         5752 
         5753 
         5754 
         5755 
         5756 
         5757 
         5758 
         5759 
         5760 
         5761 
         5762 
         5763 
         5764 
         5765 
         5766 
         5767 
         5768 
         5769 
         5770 
         5771   
         5772 
         5773     
         5774   
         5775     
         5776 
         5777       
         5778 
         5779       
         5780         <figure class="
         5781               sqs-block-image-figure
         5782               intrinsic
         5783             "
         5784         >
         5785           
         5786         
         5787         
         5788 
         5789         
         5790           
         5791             
         5792           
         5793             
         5794                 
         5795                 
         5796                 
         5797                 
         5798                 
         5799                 
         5800                 
         5801                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751439934-EESJ5NMPIYBFS6XI72ZX/image-asset.png" data-image-dimensions="2038x771" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751439934-EESJ5NMPIYBFS6XI72ZX/image-asset.png?format=1000w" width="2038" height="771" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751439934-EESJ5NMPIYBFS6XI72ZX/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751439934-EESJ5NMPIYBFS6XI72ZX/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751439934-EESJ5NMPIYBFS6XI72ZX/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751439934-EESJ5NMPIYBFS6XI72ZX/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751439934-EESJ5NMPIYBFS6XI72ZX/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751439934-EESJ5NMPIYBFS6XI72ZX/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751439934-EESJ5NMPIYBFS6XI72ZX/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         5802 
         5803             
         5804           
         5805         
         5806           
         5807         
         5808 
         5809         
         5810           
         5811           <figcaption class="image-caption-wrapper">
         5812             <p class="">The same three objects using a single texture map.</p>
         5813           </figcaption>
         5814         
         5815       
         5816         </figure>
         5817       
         5818 
         5819     
         5820   
         5821 
         5822 
         5823   
         5824 
         5825 
         5826 
         5827 
         5828 
         5829   <p class="">After pressing ‘bake’ in the node, we get the following texture output:</p>
         5830 
         5831 
         5832 
         5833 
         5834 
         5835 
         5836 
         5837 
         5838 
         5839 
         5840 
         5841 
         5842 
         5843 
         5844 
         5845 
         5846 
         5847 
         5848 
         5849 
         5850 
         5851   
         5852   
         5853 
         5854 
         5855 
         5856 &nbsp;
         5857 
         5858 
         5859 
         5860 
         5861 
         5862 
         5863 
         5864 
         5865 
         5866 
         5867 
         5868 
         5869 
         5870 
         5871 
         5872 
         5873 
         5874 
         5875 
         5876 
         5877 
         5878 
         5879 
         5880 
         5881 
         5882 
         5883 
         5884 
         5885 
         5886 
         5887 
         5888 
         5889 
         5890 
         5891 
         5892 
         5893 
         5894 
         5895 
         5896 
         5897 
         5898 
         5899   
         5900 
         5901     
         5902   
         5903     
         5904 
         5905       
         5906 
         5907       
         5908         <figure class="
         5909               sqs-block-image-figure
         5910               intrinsic
         5911             "
         5912         >
         5913           
         5914         
         5915         
         5916 
         5917         
         5918           
         5919             
         5920           
         5921             
         5922                 
         5923                 
         5924                 
         5925                 
         5926                 
         5927                 
         5928                 
         5929                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751543996-HZFZB513T9NKTSRR2IWV/image-asset.png" data-image-dimensions="2048x2048" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751543996-HZFZB513T9NKTSRR2IWV/image-asset.png?format=1000w" width="2048" height="2048" sizes="(max-width: 640px) 100vw, (max-width: 767px) 50vw, 50vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751543996-HZFZB513T9NKTSRR2IWV/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751543996-HZFZB513T9NKTSRR2IWV/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751543996-HZFZB513T9NKTSRR2IWV/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751543996-HZFZB513T9NKTSRR2IWV/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751543996-HZFZB513T9NKTSRR2IWV/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751543996-HZFZB513T9NKTSRR2IWV/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751543996-HZFZB513T9NKTSRR2IWV/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         5930 
         5931             
         5932           
         5933         
         5934           
         5935         
         5936 
         5937         
         5938           
         5939           <figcaption class="image-caption-wrapper">
         5940             <p class="">The new 2048x2048 texture atlas matching the unified UV sets.</p>
         5941           </figcaption>
         5942         
         5943       
         5944         </figure>
         5945       
         5946 
         5947     
         5948   
         5949 
         5950 
         5951   
         5952 
         5953 
         5954 &nbsp;
         5955 
         5956 
         5957   <p class="">Easy peasy. The baker has options for a variety of different PBR maps beyond diffuse, and, incredibly, is built entirely with Houdini COP nodes, meaning you can dive into it and tweak it to your liking. Paul Ambrosiussen gives a great talk <a href="https://www.youtube.com/watch?v=B8HqBuH9xew">here</a> on the Maps Baker.</p><p class="">Hit me up on <a href="http://twitter.com/kleptine">Twitter </a>with questions.</p>]]></description><media:content type="image/png" url="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1571751727067-V635183FEO5XOSW5K7NU/different_geos_uv_packing_diffuse.png?format=1500w" medium="image" isDefault="true" width="1500" height="1500"><media:title type="plain">Texture Atlasing using Houdini</media:title></media:content></item><item><title>Fix your (Unity) Timestep!</title><category>Article</category><dc:creator>John Austin</dc:creator><pubDate>Sun, 12 May 2019 20:10:12 +0000</pubDate><link>https://johnaustin.io/articles/2019/fix-your-unity-timestep</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:5cd87daa7398a700018cd3dc</guid><description><![CDATA[<p class="">The preeminent article on game timesteps is <a href="https://web.archive.org/web/20210224113810/https://gafferongames.com/post/fix_your_timestep/">Gaffer On Games: “Fix Your Timestep!”</a>.<a href="#margin">Cached version, as the article occasionally goes down.</a> The article gives a great amount of context for implementing a game engine time step in a couple of different ways. Unfortunately, while this is useful, it is aimed at those writing their own engine, not using an existing one. </p><p class="">On top of that, there is surprisingly little information regarding the precise behavior of the Unity time step. The best articles are in the <a href="https://docs.unity3d.com/Manual/TimeFrameManagement.html">manuals</a>, but they stop at a simple description and don’t go into any subtle detail. </p><p class="">Thus, this article attempts to collate the varying information on the Unity time step in one place and add objective testing to illuminate its behavior in the corner cases. So without further ado!</p><h2>The Unity Timestep</h2><p class="">In this article, we will need to talk about different types of time:</p><p class=""><strong>wall time: </strong>The amount of time passing in the real world on your wall clock — Time.realtimeSinceStartup</p><p class=""><strong>game time: </strong>The amount of time that is perceived to pass within the game — Time.unscaledTime</p><p class="">Under heavy load (see below), <strong>game-time</strong> may slow down, and may not match one-to-one with <strong>wall-time</strong>. </p>
         5958 
         5959 
         5960 
         5961 
         5962 
         5963 
         5964 
         5965 
         5966 
         5967 
         5968 
         5969 
         5970 
         5971 
         5972 
         5973 
         5974 
         5975 
         5976 
         5977 
         5978 
         5979   
         5980   
         5981 
         5982 
         5983 
         5984 <hr />
         5985 
         5986 
         5987   <p class="">Unity uses a two-part timestep, as described in the Gaffer article as “Free the physics”.  In Unity, this means that there are two main update passes: FixedUpdate and Update. </p><p class="">The <strong>FixedUpdate </strong>pass steps forward in increments of 0.02 <em>game-time</em> seconds<a href="#margin">The default is 0.02 seconds. You can change the fixed timestep amount by setting Time.fixedDeltaTime — even at runtime!</a>, regardless of rendering or <strong>any</strong> other performance factors. The key here is that FixedUpdate is reliable. If 0.02 seconds of game time has passed, it is guaranteed that the FixedUpdate has run during that period. Note that this does not guarantee FixedUpdate will be run every 0.02 <em>wall-clock</em> seconds! This is only the case when game-time and wall-time are in sync.</p><p class="">The <strong>Update</strong> pass steps forward in leaps and bounds. It generally correct to think of this pass as the “render update”, as it runs exactly once per frame render. It steps forward in a dynamic number of <em>game-time </em>seconds. Unity attempts to run this Update pass at the given Application.targetFrameRate. This is generally 60 FPS, but is specialized depending on the platform and V-Sync rate.</p><p class="">Depending on the circumstances, multiple FixedUpdates may run between two Updates (the game is render-blocked), or multiple Updates may run between two FixedUpdates (rendering is faster than the FixedUpdate step). </p><p class="">By default on desktop, Unity runs the FixedUpdate at 50 FPS and the Update at 60 FPS (the VSync rate). As for the reasoning behind this choice, it seems that even the folks at Unity have <a href="https://twitter.com/aras_p/status/1072227369405046792">long forgotten</a>. But consider this. These numbers also happen to be values Gaffer uses for the example at the end of the article! Coincidence? We can only imagine.</p><h2>Measuring Update/FixedUpdate</h2><p class="">Lets test some of the above assumptions. We’ll use the fantastic <a href="https://assetstore.unity.com/packages/tools/utilities/squiggle-21970">Squiggle</a> asset to log graphs of each of our tests.</p><p class="">To start, let’s run Unity under a simple scene and the default settings. The following graph shows FixedUpdate in blue, then Update in red.  At the bottom is one second of wall-clock time.</p>
         5988 
         5989 
         5990 
         5991 
         5992 
         5993 
         5994 
         5995 
         5996 
         5997 
         5998 
         5999 
         6000 
         6001 
         6002 
         6003 
         6004 
         6005 
         6006 
         6007 
         6008 
         6009   
         6010   
         6011 
         6012 
         6013 
         6014 
         6015 
         6016 
         6017 
         6018 
         6019 
         6020 
         6021 
         6022 
         6023 
         6024 
         6025 
         6026 
         6027 
         6028 
         6029 
         6030 
         6031 
         6032 
         6033 
         6034 
         6035 
         6036 
         6037 
         6038 
         6039 
         6040 
         6041 
         6042 
         6043 
         6044 
         6045 
         6046 
         6047 
         6048 
         6049 
         6050 
         6051 
         6052 
         6053 
         6054 
         6055 
         6056 
         6057   
         6058 
         6059     
         6060   
         6061     
         6062 
         6063       
         6064 
         6065       
         6066         <figure class="
         6067               sqs-block-image-figure
         6068               intrinsic
         6069             "
         6070         >
         6071           
         6072         
         6073         
         6074 
         6075         
         6076           
         6077             
         6078           
         6079             
         6080                 
         6081                 
         6082                 
         6083                 
         6084                 
         6085                 
         6086                 
         6087                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557710867640-FBO6FKWEV9WVIBVD76PU/2019-05-12_18-27-06.png" data-image-dimensions="564x231" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557710867640-FBO6FKWEV9WVIBVD76PU/2019-05-12_18-27-06.png?format=1000w" width="564" height="231" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557710867640-FBO6FKWEV9WVIBVD76PU/2019-05-12_18-27-06.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557710867640-FBO6FKWEV9WVIBVD76PU/2019-05-12_18-27-06.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557710867640-FBO6FKWEV9WVIBVD76PU/2019-05-12_18-27-06.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557710867640-FBO6FKWEV9WVIBVD76PU/2019-05-12_18-27-06.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557710867640-FBO6FKWEV9WVIBVD76PU/2019-05-12_18-27-06.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557710867640-FBO6FKWEV9WVIBVD76PU/2019-05-12_18-27-06.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557710867640-FBO6FKWEV9WVIBVD76PU/2019-05-12_18-27-06.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         6088 
         6089             
         6090           
         6091         
         6092           
         6093         
         6094 
         6095         
         6096           
         6097           <figcaption class="image-caption-wrapper">
         6098             <p class="">Blue: FixedUpdate, Red: Update, Grey: 1 Second Wall-Clock</p>
         6099           </figcaption>
         6100         
         6101       
         6102         </figure>
         6103       
         6104 
         6105     
         6106   
         6107 
         6108 
         6109   
         6110 
         6111 
         6112 
         6113 
         6114 
         6115   <p class="">Over the course of a single wall-time second, 52 FixedUpdates and 62 Updates passed. If game-time and wall-time were <em>perfectly</em> matched, we would expect 50 FixedUpdates and 60 Updates. However, the real world is a messy place, and instead Unity game-time is always catching up and overshooting the wall clock. We over-counted this second, but we’ll under count in some later second to balance it out. In practice, this minor fluctuation in game-time vs. wall-time is imperceptible. </p><p class="">Notice something interesting about the FixedUpdate graph. They seem to be grouped in smaller batches of updates. Within each batch, 20ms of <em>game-time</em> passes for each, and 17ms <em>wall-time</em> passes. Then, there is a gap where 20ms of <em>game-time </em>passes and 35ms of <em>wall-time </em>passes, before another batch gets run. Unity seems to be trying to run the slower FixedUpdate in lockstep with the faster Update until it must pause to maintain 50FPS. </p><p class="">On the other hand, Update is perfectly smooth. We can thank V-Sync for this. My display is set to 60Hz, and as such, Unity performs an Update whenever the display is ready for the next frame. </p><p class="">So what does this mean for stutter? In this setup, now and then <strong>two</strong> frames will be rendered for a single FixedUpdate pass. If the render frames are being sent at 60Hz, you’d see a single frame stutter every 5-8 frames. If you’re doing processing in FixedUpdate, without interpolating,<a href="#margin">Interpolation can fix this stutter to some degree, but has its own set of visual artifacts.</a> you’ll see this fairly consistent stutter on playback. This is not great, and is a big reason developers in Unity feel pressured to do their processing in Update. </p><p class="">You can see this clearly in the following graph. Here, each FixedUpdate is assigned a color. The Update is colored with the FixedUpdate that preceded it.</p>
         6116 
         6117 
         6118 
         6119 
         6120 
         6121 
         6122 
         6123 
         6124 
         6125 
         6126 
         6127 
         6128 
         6129 
         6130 
         6131 
         6132 
         6133 
         6134 
         6135 
         6136 
         6137   
         6138   
         6139 
         6140 
         6141 
         6142 
         6143 
         6144 
         6145 
         6146 
         6147 
         6148 
         6149 
         6150 
         6151 
         6152 
         6153 
         6154 
         6155 
         6156 
         6157 
         6158 
         6159 
         6160 
         6161 
         6162 
         6163 
         6164 
         6165 
         6166 
         6167 
         6168 
         6169 
         6170 
         6171 
         6172 
         6173 
         6174 
         6175 
         6176 
         6177 
         6178 
         6179 
         6180 
         6181 
         6182 
         6183 
         6184 
         6185   
         6186 
         6187     
         6188   
         6189     
         6190 
         6191       
         6192 
         6193       
         6194         <figure class="
         6195               sqs-block-image-figure
         6196               intrinsic
         6197             "
         6198         >
         6199           
         6200         
         6201         
         6202 
         6203         
         6204           
         6205             
         6206           
         6207             
         6208                 
         6209                 
         6210                 
         6211                 
         6212                 
         6213                 
         6214                 
         6215                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763576179-A92HVNC3H703C1CMGI3D/2019-05-13_09-04-49.png" data-image-dimensions="695x233" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763576179-A92HVNC3H703C1CMGI3D/2019-05-13_09-04-49.png?format=1000w" width="695" height="233" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763576179-A92HVNC3H703C1CMGI3D/2019-05-13_09-04-49.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763576179-A92HVNC3H703C1CMGI3D/2019-05-13_09-04-49.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763576179-A92HVNC3H703C1CMGI3D/2019-05-13_09-04-49.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763576179-A92HVNC3H703C1CMGI3D/2019-05-13_09-04-49.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763576179-A92HVNC3H703C1CMGI3D/2019-05-13_09-04-49.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763576179-A92HVNC3H703C1CMGI3D/2019-05-13_09-04-49.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763576179-A92HVNC3H703C1CMGI3D/2019-05-13_09-04-49.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         6216 
         6217             
         6218           
         6219         
         6220           
         6221         
         6222 
         6223         
         6224       
         6225         </figure>
         6226       
         6227 
         6228     
         6229   
         6230 
         6231 
         6232   
         6233 
         6234 
         6235 
         6236 
         6237 
         6238   <p class="">Look closely and you’ll notice duplicates in the Update track. Every 5-8 frames, two Updates render for a single FixedUpdate. </p><p class="">Luckily, though, Unity exposes the FixedUpdate time-step to developers. What if we try running the FixedUpdate at 60FPS instead of 50FPS? </p>
         6239 
         6240 
         6241 
         6242 
         6243 
         6244 
         6245 
         6246 
         6247 
         6248 
         6249 
         6250 
         6251 
         6252 
         6253 
         6254 
         6255 
         6256 
         6257 
         6258 
         6259 
         6260   
         6261   
         6262 
         6263 
         6264 
         6265 
         6266 
         6267 
         6268 
         6269 
         6270 
         6271 
         6272 
         6273 
         6274 
         6275 
         6276 
         6277 
         6278 
         6279 
         6280 
         6281 
         6282 
         6283 
         6284 
         6285 
         6286 
         6287 
         6288 
         6289 
         6290 
         6291 
         6292 
         6293 
         6294 
         6295 
         6296 
         6297 
         6298 
         6299 
         6300 
         6301 
         6302 
         6303 
         6304 
         6305 
         6306 
         6307 
         6308   
         6309 
         6310     
         6311   
         6312     
         6313 
         6314       
         6315 
         6316       
         6317         <figure class="
         6318               sqs-block-image-figure
         6319               intrinsic
         6320             "
         6321         >
         6322           
         6323         
         6324         
         6325 
         6326         
         6327           
         6328             
         6329           
         6330             
         6331                 
         6332                 
         6333                 
         6334                 
         6335                 
         6336                 
         6337                 
         6338                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763039989-JUJNBM96FYB20S0OXO63/2019-05-13_08-56-39.png" data-image-dimensions="663x234" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763039989-JUJNBM96FYB20S0OXO63/2019-05-13_08-56-39.png?format=1000w" width="663" height="234" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763039989-JUJNBM96FYB20S0OXO63/2019-05-13_08-56-39.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763039989-JUJNBM96FYB20S0OXO63/2019-05-13_08-56-39.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763039989-JUJNBM96FYB20S0OXO63/2019-05-13_08-56-39.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763039989-JUJNBM96FYB20S0OXO63/2019-05-13_08-56-39.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763039989-JUJNBM96FYB20S0OXO63/2019-05-13_08-56-39.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763039989-JUJNBM96FYB20S0OXO63/2019-05-13_08-56-39.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557763039989-JUJNBM96FYB20S0OXO63/2019-05-13_08-56-39.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         6339 
         6340             
         6341           
         6342         
         6343           
         6344         
         6345 
         6346         
         6347           
         6348           <figcaption class="image-caption-wrapper">
         6349             <p class="">FixedUpdate timestep at 60FPS, Update timestep at 60FPS</p>
         6350           </figcaption>
         6351         
         6352       
         6353         </figure>
         6354       
         6355 
         6356     
         6357   
         6358 
         6359 
         6360   
         6361 
         6362 
         6363 
         6364 
         6365 
         6366   <p class="">Much better!</p><p class="">Fundamentally, there’s no way to run a 60 FPS Update and a 50 FPS FixedUpdate without incurring <strong>some</strong> artifacts. This is a sampling problem, and you have to pick your poison. The best option is to do your best to keep your FixedUpdate running close to your render timestep. Failing that, interpolation may be worth investigating.</p><p class="">If you take one thing away from this article, I would consider setting your Time.fixedDeltaTime to 1/60f. </p><h2>A Heavy Load in FixedUpdate</h2><p class="">Up to this point we’ve been dealing with Unity at its most performant. However, this is rarely the case in production. We’ve said that the FixedUpdate is <strong>guaranteed </strong>to run every 0.02 game-time seconds. What happens when the FixedUpdate pass takes longer than 0.02 wall-time seconds to run? In this case, by the time a single FixedUpdate runs, even more wall-time seconds have passed and it will need to run again! It will never catch up. Instead, Unity chooses to just slow down the overall <em>game-time</em> itself.</p><p class="">The following setup spends 33ms in every FixedUpdate, causing the game to take only 30 FixedUpdates per second. Thus, game-time progresses forward 0.6 seconds for every 1 wall-time second<a href="#margin"> (30 FPS / 50 FPS) = 0.6</a>.</p>
         6367 
         6368 
         6369 
         6370 
         6371 
         6372 
         6373 
         6374 
         6375 
         6376 
         6377 
         6378 
         6379 
         6380 
         6381 
         6382 
         6383 
         6384 
         6385 
         6386 
         6387 
         6388   
         6389   
         6390 
         6391 
         6392 
         6393 
         6394 
         6395 
         6396 
         6397 
         6398 
         6399 
         6400 
         6401 
         6402 
         6403 
         6404 
         6405 
         6406 
         6407 
         6408 
         6409 
         6410 
         6411 
         6412 
         6413 
         6414 
         6415 
         6416 
         6417 
         6418 
         6419 
         6420 
         6421 
         6422 
         6423 
         6424 
         6425 
         6426 
         6427 
         6428 
         6429 
         6430 
         6431 
         6432 
         6433 
         6434 
         6435 
         6436   
         6437 
         6438     
         6439   
         6440     
         6441 
         6442       
         6443 
         6444       
         6445         <figure class="
         6446               sqs-block-image-figure
         6447               intrinsic
         6448             "
         6449         >
         6450           
         6451         
         6452         
         6453 
         6454         
         6455           
         6456             
         6457           
         6458             
         6459                 
         6460                 
         6461                 
         6462                 
         6463                 
         6464                 
         6465                 
         6466                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557846510375-VCPJUB4SRX2TCZ22A3XM/Unity_2019-05-14_08-08-15.png" data-image-dimensions="687x233" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557846510375-VCPJUB4SRX2TCZ22A3XM/Unity_2019-05-14_08-08-15.png?format=1000w" width="687" height="233" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557846510375-VCPJUB4SRX2TCZ22A3XM/Unity_2019-05-14_08-08-15.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557846510375-VCPJUB4SRX2TCZ22A3XM/Unity_2019-05-14_08-08-15.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557846510375-VCPJUB4SRX2TCZ22A3XM/Unity_2019-05-14_08-08-15.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557846510375-VCPJUB4SRX2TCZ22A3XM/Unity_2019-05-14_08-08-15.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557846510375-VCPJUB4SRX2TCZ22A3XM/Unity_2019-05-14_08-08-15.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557846510375-VCPJUB4SRX2TCZ22A3XM/Unity_2019-05-14_08-08-15.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557846510375-VCPJUB4SRX2TCZ22A3XM/Unity_2019-05-14_08-08-15.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         6467 
         6468             
         6469           
         6470         
         6471           
         6472         
         6473 
         6474         
         6475       
         6476         </figure>
         6477       
         6478 
         6479     
         6480   
         6481 
         6482 
         6483   
         6484 
         6485 
         6486 
         6487 
         6488 
         6489   <p class="">Notice here that Update is also lowered in frequency, even though the Update pass (and rendering) has no load! Even though the FixedUpdate is updating at 30 FPS, the game is only being rendered at a measly 6 FPS. You might expect that rendering would run once every FixedUpdate, but that’s not what happens. The reason behind this is due to Unity’s ‘catch-up’ mechanism. </p><p class="">When the Update perceives that game-time is running behind wall-time, it will run the FixedUpdate as fast as possible until it catches back up. This is sensible when your game is largely render-blocked (as FixedUpdates will be relatively quick). However, in this case, because the FixedUpdate is too slow to run at frame-rate, Unity would end up running FixedUpdates forever, doomed to never catch up.</p><p class="">Instead, Unity provides a ‘bail out’ setting: Time.maximumDeltaTime. After a certain amount of time spent of “catching up” on FixedUpdates, it will bail out to finish the frame before running any more. By default this value is set to 0.1 seconds (wall-time). This explains the 5 FixedUpdates between each render. If we like, we can lower this value down, such that it always bails out after one FixedUpdate. Here is the same scene with a setting of 0.02.</p>
         6490 
         6491 
         6492 
         6493 
         6494 
         6495 
         6496 
         6497 
         6498 
         6499 
         6500 
         6501 
         6502 
         6503 
         6504 
         6505 
         6506 
         6507 
         6508 
         6509 
         6510 
         6511   
         6512   
         6513 
         6514 
         6515 
         6516 
         6517 
         6518 
         6519 
         6520 
         6521 
         6522 
         6523 
         6524 
         6525 
         6526 
         6527 
         6528 
         6529 
         6530 
         6531 
         6532 
         6533 
         6534 
         6535 
         6536 
         6537 
         6538 
         6539 
         6540 
         6541 
         6542 
         6543 
         6544 
         6545 
         6546 
         6547 
         6548 
         6549 
         6550 
         6551 
         6552 
         6553 
         6554 
         6555 
         6556 
         6557 
         6558 
         6559   
         6560 
         6561     
         6562   
         6563     
         6564 
         6565       
         6566 
         6567       
         6568         <figure class="
         6569               sqs-block-image-figure
         6570               intrinsic
         6571             "
         6572         >
         6573           
         6574         
         6575         
         6576 
         6577         
         6578           
         6579             
         6580           
         6581             
         6582                 
         6583                 
         6584                 
         6585                 
         6586                 
         6587                 
         6588                 
         6589                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557848938866-61PCHX2VMFAQHMWEQOZ3/Unity_2019-05-14_08-48-52.png" data-image-dimensions="637x232" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557848938866-61PCHX2VMFAQHMWEQOZ3/Unity_2019-05-14_08-48-52.png?format=1000w" width="637" height="232" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557848938866-61PCHX2VMFAQHMWEQOZ3/Unity_2019-05-14_08-48-52.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557848938866-61PCHX2VMFAQHMWEQOZ3/Unity_2019-05-14_08-48-52.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557848938866-61PCHX2VMFAQHMWEQOZ3/Unity_2019-05-14_08-48-52.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557848938866-61PCHX2VMFAQHMWEQOZ3/Unity_2019-05-14_08-48-52.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557848938866-61PCHX2VMFAQHMWEQOZ3/Unity_2019-05-14_08-48-52.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557848938866-61PCHX2VMFAQHMWEQOZ3/Unity_2019-05-14_08-48-52.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557848938866-61PCHX2VMFAQHMWEQOZ3/Unity_2019-05-14_08-48-52.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         6590 
         6591             
         6592           
         6593         
         6594           
         6595         
         6596 
         6597         
         6598       
         6599         </figure>
         6600       
         6601 
         6602     
         6603   
         6604 
         6605 
         6606   
         6607 
         6608 
         6609 
         6610 
         6611 
         6612   <p class=""><br>Much smoother. Now there’s one Update for every FixedUpdate (no sampling issues). Both run at 30 FPS. Unfortunately, tuning this value is tricky and depends on the game. Set it too high and you can see lower frame-rates than necessary. If you set it too low, the game could get bottlenecked rendering all of those extra frames (ie. Unity isn’t able to skip rendering to help catch up). </p><p class="">One more thing. In this last case, because the game is running slower than real-time, Unity is passing a Time.deltaTime of 0.02 game-time seconds to the Update function. This is a different value than the actual wall-clock frame time! Each Update pass actually spans about 0.033 seconds, but because the game as a whole has been slowed down, the deltaTime is scaled down as well. Try your best not to think of Update’s deltaTime as “the time between frames”. In many situations that’s not correct — more correctly it is the <em>game-time</em> between frames.</p><p class="">Slowing down is probably the best thing that Unity can do in this situation. Without dynamically changing the FixedUpdate timestep, there’s just no way to make the game do 0.033 seconds of work in 0.02 seconds. The lesson here is to be careful about the amount of work placed in the FixedUpdate (ie. Physics, Game State logic). A physics heavy scene will slow down the game, regardless of the ability of Unity to render at a dynamic frame rate.</p><h2>A Heavy Load in Update</h2><p class="">What if, instead, we are render-blocked or blocked on heavy animation / skinning on the CPU? In this case, the Update function will be slow. Here, I have set the Update function to run at 10 FPS with a 100ms second load. </p>
         6613 
         6614 
         6615 
         6616 
         6617 
         6618 
         6619 
         6620 
         6621 
         6622 
         6623 
         6624 
         6625 
         6626 
         6627 
         6628 
         6629 
         6630 
         6631 
         6632 
         6633 
         6634   
         6635   
         6636 
         6637 
         6638 
         6639 
         6640 
         6641 
         6642 
         6643 
         6644 
         6645 
         6646 
         6647 
         6648 
         6649 
         6650 
         6651 
         6652 
         6653 
         6654 
         6655 
         6656 
         6657 
         6658 
         6659 
         6660 
         6661 
         6662 
         6663 
         6664 
         6665 
         6666 
         6667 
         6668 
         6669 
         6670 
         6671 
         6672 
         6673 
         6674 
         6675 
         6676 
         6677 
         6678 
         6679 
         6680 
         6681 
         6682   
         6683 
         6684     
         6685   
         6686     
         6687 
         6688       
         6689 
         6690       
         6691         <figure class="
         6692               sqs-block-image-figure
         6693               intrinsic
         6694             "
         6695         >
         6696           
         6697         
         6698         
         6699 
         6700         
         6701           
         6702             
         6703           
         6704             
         6705                 
         6706                 
         6707                 
         6708                 
         6709                 
         6710                 
         6711                 
         6712                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557841828984-U1H69PMABRCBS92NV16J/Unity_2019-05-14_06-49-06.png" data-image-dimensions="540x232" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557841828984-U1H69PMABRCBS92NV16J/Unity_2019-05-14_06-49-06.png?format=1000w" width="540" height="232" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557841828984-U1H69PMABRCBS92NV16J/Unity_2019-05-14_06-49-06.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557841828984-U1H69PMABRCBS92NV16J/Unity_2019-05-14_06-49-06.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557841828984-U1H69PMABRCBS92NV16J/Unity_2019-05-14_06-49-06.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557841828984-U1H69PMABRCBS92NV16J/Unity_2019-05-14_06-49-06.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557841828984-U1H69PMABRCBS92NV16J/Unity_2019-05-14_06-49-06.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557841828984-U1H69PMABRCBS92NV16J/Unity_2019-05-14_06-49-06.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557841828984-U1H69PMABRCBS92NV16J/Unity_2019-05-14_06-49-06.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         6713 
         6714             
         6715           
         6716         
         6717           
         6718         
         6719 
         6720         
         6721       
         6722         </figure>
         6723       
         6724 
         6725     
         6726   
         6727 
         6728 
         6729   
         6730 
         6731 
         6732 
         6733 
         6734 
         6735   <p class="">This result is a bit odd. We can count 10 Update passes for every wall-time second — this checks out. However, at first glance it seems like the FixedUpdate is only being run 10 times a second as well. If this were true, then the game physics would be proceeding at 1/5th the speed! If we zoom in, the reality becomes more clear. </p>
         6736 
         6737 
         6738 
         6739 
         6740 
         6741 
         6742 
         6743 
         6744 
         6745 
         6746 
         6747 
         6748 
         6749 
         6750 
         6751 
         6752 
         6753 
         6754 
         6755 
         6756 
         6757   
         6758   
         6759 
         6760 
         6761 
         6762 
         6763 
         6764 
         6765 
         6766 
         6767 
         6768 
         6769 
         6770 
         6771 
         6772 
         6773 
         6774 
         6775 
         6776 
         6777 
         6778 
         6779 
         6780 
         6781 
         6782 
         6783 
         6784 
         6785 
         6786 
         6787 
         6788 
         6789 
         6790 
         6791 
         6792 
         6793 
         6794 
         6795 
         6796 
         6797 
         6798 
         6799 
         6800 
         6801 
         6802 
         6803 
         6804 
         6805   
         6806 
         6807     
         6808   
         6809     
         6810 
         6811       
         6812 
         6813       
         6814         <figure class="
         6815               sqs-block-image-figure
         6816               intrinsic
         6817             "
         6818         >
         6819           
         6820         
         6821         
         6822 
         6823         
         6824           
         6825             
         6826           
         6827             
         6828                 
         6829                 
         6830                 
         6831                 
         6832                 
         6833                 
         6834                 
         6835                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557842161763-KOG3HTDRE0HOYCYCUULK/Unity_2019-05-14_06-55-49.png" data-image-dimensions="649x149" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557842161763-KOG3HTDRE0HOYCYCUULK/Unity_2019-05-14_06-55-49.png?format=1000w" width="649" height="149" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557842161763-KOG3HTDRE0HOYCYCUULK/Unity_2019-05-14_06-55-49.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557842161763-KOG3HTDRE0HOYCYCUULK/Unity_2019-05-14_06-55-49.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557842161763-KOG3HTDRE0HOYCYCUULK/Unity_2019-05-14_06-55-49.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557842161763-KOG3HTDRE0HOYCYCUULK/Unity_2019-05-14_06-55-49.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557842161763-KOG3HTDRE0HOYCYCUULK/Unity_2019-05-14_06-55-49.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557842161763-KOG3HTDRE0HOYCYCUULK/Unity_2019-05-14_06-55-49.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1557842161763-KOG3HTDRE0HOYCYCUULK/Unity_2019-05-14_06-55-49.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         6836 
         6837             
         6838           
         6839         
         6840           
         6841         
         6842 
         6843         
         6844       
         6845         </figure>
         6846       
         6847 
         6848     
         6849   
         6850 
         6851 
         6852   
         6853 
         6854 
         6855 
         6856 
         6857 
         6858   <p class="">Because our FixedUpdate is so fast, after every 100ms frame, the next 5 FixedUpdates execute immediately, before starting on another heavy Update pass. In this situation, the game-time <em>doesn’t</em> slow down, relative to wall-clock time. Instead, game time is shrunken and expanded! </p><p class="">Imagine if you read the wall-time value of Time.realtimeSinceStartup in the FixedUpdate — it would be extremely incorrect. This is a strong reason to keep wall-clock time out of your game calculations. Unity plays around with the way game-time flows depending on the load. You want your game to simulate nicely even when game-time is being stretched.</p><h2>Conclusion</h2><p class="">Let’s take stock:</p><ul data-rte-list="default"><li><p class="">Game-time can drastically differ from wall-clock time, so avoid using the latter. </p></li><li><p class="">Make use of Time.fixedDeltaTime and Time.maximumDeltaTime to tune the specific performance characteristics of your game. These can all be changed at runtime, too!</p></li><li><p class="">Stuttering can occur even with an empty load on the engine. </p></li><li><p class="">Be careful of sampling issues. Debug your frame timesteps to see what is actually happening.</p></li></ul><p class="">Further, I showed the behavior of the timestep in isolated cases, but in reality all of these situations overlap. A single long Update could trigger a series of longer FixedUpdate steps, causing the Update to slow down, etc. It’s one big feedback loop. Hopefully, though, this gives you some intuition behind it.</p><p class="">Thanks for reading! Drop me a line on <a href="http://twitter.com/Kleptine">Twitter</a> if you have questions or if you have information to add. I’d love to make this article a one-stop-shop for timestep information. Additionally, if you think I’ve gotten something wrong, please let me know! </p><h2>FAQ</h2><p class="">Finally, let’s answer a few frequent questions. Perhaps you’ve come here from Google just for these (I suggest reading the above article for context).</p><h3><a href="#anchor">#</a> Help! My game is stuttering!</h3><p class="">The first thing to do is figure out <strong>why</strong> your game is stuttering. Stutter can be a result of mismatched timesteps (see first section) or could be from a variety of different performance issues (your update or fixed update are too long and Unity stutters to compensate in other ways). Drop a few console lines, or graph out the data on when your frames are rendered, and then see what looks wrong compared to the graphs above. </p><h3><a href="#anchor">#</a> Is FixedUpdate always called every 0.02 seconds?</h3><p class="">No, FixedUpdate will run once for every 0.02 <em>game-time</em> seconds, but precisely when in real time this happens is unspecified and can vary <em>drastically. </em>See above: “A Heavy Load in FixedUpdate”.</p><h3><a href="#anchor">#</a> How does the Unity timestep respond to low framerates?</h3><p class="">Depending on the source of the slowness, it will do different things. For slow FixedUpdates, it slows down the overall game-time (the game will run for 0.5 seconds in the span of one real time second). In some cases, the rendering rate can be drastically cut, even if the problem is your FixedUpdate speed. </p><p class="">For slow Updates, FixedUpdates get crunched together during the “catch up” phase between the longer Updates. Otherwise, generally the the Update dynamic timestep just gets longer.</p><h3><a href="#anchor">#</a> What is the value of Time.deltaTime in the Update function?</h3><p class="">You can generally think of it as the difference in <em>game-time </em>between frames being rendered to the screen. When at performance, this is the same as the wall time between frames. However, if the game slows down because of FixedUpdate lag, Time.deltaTime will shrink, even if the render frame rate remains the same! Weird!</p><h3><a href="#anchor">#</a> Should I put my code in FixedUpdate or Update?</h3><p class="">This is a subtle question, with no correct answer, unfortunately. Code in FixedUpdate means your game is deterministic, but FixedUpdate can’t adapt to varying framerates. Under load, your game will <em>slow down. </em>You must be much more careful that FixedUpdate can run at display frame rate. </p><p class="">If you favor determinism and systematic stability, go for FixedUpdate. If you don’t want to worry about it too terribly much, go with the default Unity approach of putting physics-related code in FixedUpdate and the rest in Update.</p><p class="">I wrote up my opinions in detail in a Reddit comment with <a href="https://www.reddit.com/r/gamedev/comments/bu0l6d/fix_your_unity_timestep_everything_you_need_to/ep5jrq1/">two different takes on the matter.</a></p><h3><a href="#anchor">#</a> Does physics have to run in FixedUpdate?</h3><p class="">Unity provides the ability to <a href="https://docs.unity3d.com/ScriptReference/Physics-autoSimulation.html">remove the physics update</a> from FixedUpdate, letting you<a href="https://docs.unity3d.com/ScriptReference/Physics.Simulate.html"> manually step the physics engine</a> on your own time. This is great, but probably should be used with caution. It is a non-standard approach, and may play oddly with plugins and other engine features.</p><p class="">On top of this, Unity now supports <a href="https://blogs.unity3d.com/2018/11/12/physics-changes-in-unity-2018-3-beta/">the ability</a> to have separate physics worlds. This lets you simulate an entirely separate physics system outside of the game loop. </p><p class="">If you’re reading this from the future, you may also be interested in Unity’s new <a href="https://blogs.unity3d.com/2019/03/19/announcing-unity-and-havok-physics-for-dots/">Havok and DOTS Physics</a> systems.</p><h3><a href="#anchor">#</a> Where can I learn more about time steps?</h3><p class="">This article is built upon the shoulders of others. Chief among them is the <a href="http://vodacek.zvb.cz/archiv/681.html">Gaffer on Games: Fix Your Timestep</a> article. See Google for a cached link if it goes down. Additional sources, in no particular order:</p><ul data-rte-list="default"><li><p class="">The Unity manual for <a href="https://docs.unity3d.com/Manual/TimeFrameManagement.html">Time Management</a> and <a href="https://docs.unity3d.com/Manual/ExecutionOrder.html">Game Loop Lifecycle</a>.</p></li><li><p class="">The <a href="https://gameprogrammingpatterns.com/game-loop.html">Game Loop</a> entry in Game Programming Patterns — a nice continuation after reading Gaffer</p></li><li><p class=""><a href="http://lspiroengine.com/?p=378">Fixed Time Stepping</a> for the L Spiro engine</p></li><li><p class=""><a href="https://www.gamedev.net/blogs/entry/2265460-fixing-your-timestep-and-evaluating-godot/">A great article on Gamedev.net </a>by lawnjelly</p></li><li><p class="">Scott Sewell’s article on <a href="http://www.kinematicsoup.com/news/2016/8/9/rrypp5tkubynjwxhxjzd42s3o034o8">Achieving Smooth Motion in Unity</a></p></li></ul><p class="">Finally, you can reach out to me on <a href="http://twitter.com/kleptine">Twitter</a> if you have any questions.</p>]]></description></item><item><title>Florence as Interactive Metaphor</title><category>Essay</category><dc:creator>John Austin</dc:creator><pubDate>Sat, 20 Apr 2019 18:01:36 +0000</pubDate><link>https://johnaustin.io/articles/2019/florence-as-interactive-metaphor</link><guid isPermaLink="false">5b1340bcee17595af30b6b97:5b7cbb5f40ec9a4b7342c8a9:5cbb5461038c49000190d28f</guid><description><![CDATA[<figure class="
         6859               sqs-block-image-figure
         6860               intrinsic
         6861             "
         6862         >
         6863           
         6864         
         6865         
         6866 
         6867         
         6868           
         6869             
         6870           
         6871             
         6872                 
         6873                 
         6874                 
         6875                 
         6876                 
         6877                 
         6878                 
         6879                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782791738-AQSFSKH44KRU84GFTV0N/Q4e_nu3RXlHrDOWWOZHz7RRERRnUX4FbNwgRjBpzOmg.jpg" data-image-dimensions="1200x628" data-image-focal-point="0.4938181464174455,0.0" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782791738-AQSFSKH44KRU84GFTV0N/Q4e_nu3RXlHrDOWWOZHz7RRERRnUX4FbNwgRjBpzOmg.jpg?format=1000w" width="1200" height="628" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782791738-AQSFSKH44KRU84GFTV0N/Q4e_nu3RXlHrDOWWOZHz7RRERRnUX4FbNwgRjBpzOmg.jpg?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782791738-AQSFSKH44KRU84GFTV0N/Q4e_nu3RXlHrDOWWOZHz7RRERRnUX4FbNwgRjBpzOmg.jpg?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782791738-AQSFSKH44KRU84GFTV0N/Q4e_nu3RXlHrDOWWOZHz7RRERRnUX4FbNwgRjBpzOmg.jpg?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782791738-AQSFSKH44KRU84GFTV0N/Q4e_nu3RXlHrDOWWOZHz7RRERRnUX4FbNwgRjBpzOmg.jpg?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782791738-AQSFSKH44KRU84GFTV0N/Q4e_nu3RXlHrDOWWOZHz7RRERRnUX4FbNwgRjBpzOmg.jpg?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782791738-AQSFSKH44KRU84GFTV0N/Q4e_nu3RXlHrDOWWOZHz7RRERRnUX4FbNwgRjBpzOmg.jpg?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782791738-AQSFSKH44KRU84GFTV0N/Q4e_nu3RXlHrDOWWOZHz7RRERRnUX4FbNwgRjBpzOmg.jpg?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         6880 
         6881             
         6882           
         6883         
         6884           
         6885         
         6886 
         6887         
         6888       
         6889         </figure>
         6890       
         6891 
         6892     
         6893   
         6894 
         6895 
         6896   
         6897 
         6898 
         6899 
         6900 
         6901 
         6902   <p class="">There is a category of games that I’ve seen growing over the past couple of years. These games occupy multiple genres: they are primarily narrative-driven, but borrow mechanics and structures from many ‘loaner’ genres. These loaner genres can be anything, but often fall in the category of puzzle, action, or rhythm. Notably, however, they rarely achieve any of the goals of their loaner genres. Florence is not a puzzle game: every task can be solved by ‘fiddling’. Neither is it an action game: all timing or response based challenges can be completely ignored and taken at your own pace.</p>
         6903 
         6904 
         6905 
         6906 
         6907 
         6908 
         6909 
         6910 
         6911 
         6912 
         6913 
         6914 
         6915 
         6916 
         6917 
         6918 
         6919 
         6920 
         6921 
         6922 
         6923 
         6924   
         6925   
         6926 
         6927 
         6928 
         6929 
         6930 
         6931 
         6932 
         6933 
         6934 
         6935 
         6936 
         6937 
         6938 
         6939 
         6940 
         6941 
         6942 
         6943 
         6944 
         6945 
         6946 
         6947 
         6948 
         6949 
         6950 
         6951 
         6952 
         6953 
         6954 
         6955 
         6956 
         6957 
         6958 
         6959 
         6960 
         6961 
         6962 
         6963 
         6964 
         6965 
         6966 
         6967 
         6968 
         6969 
         6970 
         6971 
         6972   
         6973 
         6974     
         6975   
         6976     
         6977 
         6978       
         6979 
         6980       
         6981         <figure class="
         6982               sqs-block-image-figure
         6983               intrinsic
         6984             "
         6985         >
         6986           
         6987         
         6988         
         6989 
         6990         
         6991           
         6992             
         6993           
         6994             
         6995                 
         6996                 
         6997                 
         6998                 
         6999                 
         7000                 
         7001                 
         7002                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782641643-80X2VK9W0115CQURNZC8/chrome_2019-04-20_10-49-51.jpg" data-image-dimensions="898x1597" data-image-focal-point="0.5131316489361702,0.8630952380952381" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782641643-80X2VK9W0115CQURNZC8/chrome_2019-04-20_10-49-51.jpg?format=1000w" width="898" height="1597" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782641643-80X2VK9W0115CQURNZC8/chrome_2019-04-20_10-49-51.jpg?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782641643-80X2VK9W0115CQURNZC8/chrome_2019-04-20_10-49-51.jpg?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782641643-80X2VK9W0115CQURNZC8/chrome_2019-04-20_10-49-51.jpg?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782641643-80X2VK9W0115CQURNZC8/chrome_2019-04-20_10-49-51.jpg?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782641643-80X2VK9W0115CQURNZC8/chrome_2019-04-20_10-49-51.jpg?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782641643-80X2VK9W0115CQURNZC8/chrome_2019-04-20_10-49-51.jpg?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782641643-80X2VK9W0115CQURNZC8/chrome_2019-04-20_10-49-51.jpg?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         7003 
         7004             
         7005           
         7006         
         7007           
         7008         
         7009 
         7010         
         7011           
         7012           <figcaption class="image-caption-wrapper">
         7013             <p class="">Florence solves the puzzle of her job.</p>
         7014           </figcaption>
         7015         
         7016       
         7017         </figure>
         7018       
         7019 
         7020     
         7021   
         7022 
         7023 
         7024   
         7025 
         7026 
         7027 
         7028 
         7029 
         7030   <p class="">Instead, these games ‘loan’ mechanics from others. They borrow the bare-bones mechanisms and in doing so borrow a <strong>feeling</strong>.  Take, for example, the spreadsheet ‘puzzle’ in Florence that happens early on in the game. Florence is frustrated and bored by a job that is uninteresting, but must be completed. The interaction presents a system that is unintelligible at first (the rules are unclear), followed by a methodical and fairly uninteresting elimination of information (finding similar numbers). </p><p class="">Both of these feelings (confusion and methodical practice) are strongly present in the puzzle genre. However, Florence does not support these borrowed feelings with the variety of other emotions and structures present in most puzzle games. Normally, the confusion gives way to clarity and analysis, and the tedium shrinks as puzzles become more challenging. By choosing to keep only the initial structures of the puzzle genre, Florence also keeps these initial feelings as well. Metaphorically these are the feelings that map to Florence’s own struggle.</p><p class="">We, as humans, are well conditioned. When we hear music, we are instantly reminded of the feelings (either good or bad) that we have associated with this music in the past. Play someone a song and you drive emotion: a gut memory about a time and place, a loathing for a certain style, or the apathy of mild distaste. Games do this too. When a player is presented with a puzzle structure, this creates emotions. For puzzle lovers, it may be the excitement of seeing a new system to solve. For puzzle loathers it may be the exhaustion and annoyance of having to figure it out. Even non-players have experienced puzzle structures in their lives, and have associated feelings and reactions. </p><p class="">In Florence, the puzzle structures do not represent themselves. They are metaphors for the feelings and associations we’ve built for them. </p><p class="">We tend to label these games as “Narrative Games”, however this dismisses the role of these borrowed mechanics. A game like <em>Florence</em> or <em>What Remains of Edith Finch </em>has few meaningful narrative choices. Rather, it is these <strong>loaner mechanics</strong> that are used to built the core emotion of the game. </p><p class="">It isn’t an “Interactive Narrative”. It is an “Interactive Metaphor”, wrapped with a story. </p>
         7031 
         7032 
         7033 
         7034 
         7035 
         7036 
         7037 
         7038 
         7039 
         7040 
         7041 
         7042 
         7043 
         7044 
         7045 
         7046 
         7047 
         7048 
         7049 
         7050 
         7051 
         7052   
         7053   
         7054 
         7055 
         7056 
         7057 
         7058 
         7059 
         7060 
         7061 
         7062 
         7063 
         7064 
         7065 
         7066 
         7067 
         7068 
         7069 
         7070 
         7071 
         7072 
         7073 
         7074 
         7075 
         7076 
         7077 
         7078 
         7079 
         7080 
         7081 
         7082 
         7083 
         7084 
         7085 
         7086 
         7087 
         7088 
         7089 
         7090 
         7091 
         7092 
         7093 
         7094 
         7095 
         7096 
         7097 
         7098 
         7099 
         7100   
         7101 
         7102     
         7103   
         7104     
         7105 
         7106       
         7107 
         7108       
         7109         <figure class="
         7110               sqs-block-image-figure
         7111               intrinsic
         7112             "
         7113         >
         7114           
         7115         
         7116         
         7117 
         7118         
         7119           
         7120             
         7121           
         7122             
         7123                 
         7124                 
         7125                 
         7126                 
         7127                 
         7128                 
         7129                 
         7130                 <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782982225-QUOSALI28505S7AR8ONQ/image-asset.jpeg" data-image-dimensions="1200x675" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782982225-QUOSALI28505S7AR8ONQ/image-asset.jpeg?format=1000w" width="1200" height="675" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782982225-QUOSALI28505S7AR8ONQ/image-asset.jpeg?format=100w 100w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782982225-QUOSALI28505S7AR8ONQ/image-asset.jpeg?format=300w 300w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782982225-QUOSALI28505S7AR8ONQ/image-asset.jpeg?format=500w 500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782982225-QUOSALI28505S7AR8ONQ/image-asset.jpeg?format=750w 750w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782982225-QUOSALI28505S7AR8ONQ/image-asset.jpeg?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782982225-QUOSALI28505S7AR8ONQ/image-asset.jpeg?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/5b1340bcee17595af30b6b97/1555782982225-QUOSALI28505S7AR8ONQ/image-asset.jpeg?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">
         7131 
         7132             
         7133           
         7134         
         7135           
         7136         
         7137 
         7138         
         7139           
         7140           <figcaption class="image-caption-wrapper">
         7141             <p class="">“Interactive Metaphor”: balancing fantasy and mundanity in <em>What Remains of Edith Finch.</em></p>
         7142           </figcaption>
         7143         
         7144       
         7145