<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="pretty-atom-feed.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Firefly in the Dusk🌙</title>
  <subtitle>DuskEngine and more.</subtitle>
  <link href="https://duskworks.net/feed/feed.xml" rel="self" />
  <link href="https://duskworks.net/" />
  <updated>2026-03-04T00:00:00Z</updated>
  <id>https://duskworks.net/</id>
  <author>
    <name>Firefly in the Dusk🌙</name>
  </author>
  <entry>
    <title>DuskEngine Devlog 4 - Asset Refcounting Edition</title>
    <link href="https://duskworks.net/blog/duskengine-devlog-4/" />
    <updated>2026-03-04T00:00:00Z</updated>
    <id>https://duskworks.net/blog/duskengine-devlog-4/</id>
    <content type="html">&lt;p&gt;A bunch of things happened on the engine side this past month or so, but asset refcounting is by far the biggest and most important of them all so I&#39;ll focus just on it.&lt;/p&gt;
&lt;p&gt;I&#39;ll be working more on memory optimizations going forward because I&#39;m planning to add web support as soon as possible and it would be pretty embarrassing to have, for a tiny scene, a very large bundle size as well as unreasonably large RAM and VRAM usage, even if the engine is in such an early state.&lt;/p&gt;
&lt;p&gt;A first step that was easy to get out of the way, was to make sure assets don&#39;t stick around in RAM long after they&#39;ve been used by whichever systems requested them.&lt;/p&gt;
&lt;p&gt;A while back I made it so the &lt;code&gt;AssetManager&lt;/code&gt; returns &lt;code&gt;AssetHandle&lt;/code&gt;s whenever loading an asset is requested:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AssetManager&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TAsset&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    AssetHandle&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;TAsset&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LoadAsset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AssetId&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; assetId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only the &lt;code&gt;AssetManager&lt;/code&gt; is allowed to create &lt;code&gt;AssetHandle&lt;/code&gt;s from scratch.&lt;/p&gt;
&lt;p&gt;This extra layer of indirection will allow for things like async loading, for example: the asset handle could behave like a sort of &lt;code&gt;Promise&lt;/code&gt;, though I have not yet implemented anything with regards to async loading.&lt;/p&gt;
&lt;p&gt;It also allows for simpler error handling (the asset handle can have an error state set on it, in case loading the asset is impossible) and indeed, refcounting.&lt;/p&gt;
&lt;p&gt;The matter, at first thought, seems fairly simple: each time an asset handle for a particular asset is generated, increment some counter. Each time it gets released, decrease said counter. When the counter reaches zero, the asset can be ejected from whatever cache contains it (when an asset is loaded, it gets added to a cache so subsequent loads return the cached version).&lt;/p&gt;
&lt;p&gt;One thing I&#39;m fairly sure I don&#39;t want, is to start ejecting assets whenever the last &lt;code&gt;AssetHandle&lt;/code&gt;&#39;s destructor gets called. That sounds like a potential recipe for chaos and annoying debugging sessions.&lt;/p&gt;
&lt;p&gt;It might also lead to unnecessary loads for the same asset, for example if in the same frame, system A loads an asset, uses it, then releases the handle, and later on system B also needs it, but since the asset has been released, a full reload is triggered.&lt;/p&gt;
&lt;p&gt;It also means that the memory taken by assets that are truly not needed anymore in that frame, cannot be reused for other things. This is a problem if the size of the assets loaded in that frame, versus the RAM amount of the system, especially if no swap is in place, is out of balance. I won&#39;t treat this as a very likely scenario at the moment, but will keep it in mind as I go.&lt;/p&gt;
&lt;p&gt;So for now, let&#39;s schedule asset cleanup as part of the engine&#39;s main loop, to make things more predictable and easier to change in the future.&lt;/p&gt;
&lt;p&gt;First of all, the refcounting part:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IAssetRefCounter&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;IncreaseRefCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AssetId&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DecreaseRefCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AssetId&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is an interface for &lt;em&gt;something&lt;/em&gt; that will adjust refcounts for assets. &lt;code&gt;AssetHandle&lt;/code&gt;s get a pointer to such an instance when created by the &lt;code&gt;AssetManager&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AssetHandle&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;AssetHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AssetId&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; assetId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; IAssetRefCounter&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; refCounter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mAssetId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;assetId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mRefCounter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;refCounter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        mRefCounter&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IncreaseRefCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;assetId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// copy ctor, assignment operator and a bunch of other things&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// omitted&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AssetHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        mRefCounter&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;DecreaseRefCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mAssetId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, increase refcount on construction, decrease on destruction. I am not going to paste the whole class in here because who knows what issues it currently has and I wouldn&#39;t want to pass them to someone else 🙂&lt;/p&gt;
&lt;p&gt;The concrete refcounter looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AssetRefCounter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token base-clause&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IAssetRefCounter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;AssetRefCounter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// these go through that vector below and alter or insert the&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// refcount for that asset&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;IncreaseRefCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AssetId&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DecreaseRefCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AssetId&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;EraseAllZeroRefCountEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TCallback&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;IterateZeroRefCountEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TCallback cb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; p &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; mAssetAndRefCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;second &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;first&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    std&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;vector&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;std&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;pair&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;AssetId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; mAssetAndRefCount&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;AssetManager&lt;/code&gt; creates one at initialization time.&lt;/p&gt;
&lt;p&gt;The vector of pairs of asset IDs and refcounts is likely not the fastest implementation, but I don&#39;t think it&#39;s going to be an issue anytime soon.&lt;/p&gt;
&lt;p&gt;Then, at the end of each frame, the engine calls &lt;code&gt;AssetManager::OnFrameEnd&lt;/code&gt; which does this:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AssetManager&lt;/span&gt;&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;OnFrameEnd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    mRefCounter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IterateZeroRefCountEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AssetId&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; toRelease&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        mAssetCaches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;EraseFromAnyCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;toRelease&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    mRefCounter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;EraseAllZeroRefCountEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had to go back and fix some sloppy coding here and there, where instead of loading an asset, getting just the info the system needed and storing it for later, I was (re)loading the asset each time that info was needed. This manifested itself as a lot of asset reloads in a short amount of time.&lt;/p&gt;
&lt;p&gt;Some extra functionality that I omitted from the refcounter class is iteration over non-zero refcount assets, which is used to list the assets that have active handles, in the editor, in order to spot any leaks.&lt;/p&gt;
&lt;p&gt;And that&#39;s about it. I think this is a good start to build upon.&lt;/p&gt;
&lt;p&gt;I have a feeling my next memory-related optimization will be to finally compress textures, it&#39;s long overdue 🙂&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>DuskEngine Devlog 3 - Slow December (and January) Edition</title>
    <link href="https://duskworks.net/blog/duskengine-devlog-3/" />
    <updated>2026-01-28T00:00:00Z</updated>
    <id>https://duskworks.net/blog/duskengine-devlog-3/</id>
    <content type="html">&lt;p&gt;December and January were fairly slow when it comes to adding features or improvements to the engine. The few changes that were made were small in effort and size, but they will in time have a large impact.&lt;/p&gt;
&lt;h1 id=&quot;upgraded-to-sdl3&quot;&gt;Upgraded to SDL3&lt;/h1&gt;
&lt;p&gt;Upgrading from SDL2 to SDL3 was quite quick and straightforward, especially since the devs made it so compile errors give you hints as to what you need to do to migrate the code.&lt;/p&gt;
&lt;p&gt;The upgrade was done initially as a peace of mind thing, to ensure access to the latest features and to have compatibility with future platforms.&lt;/p&gt;
&lt;p&gt;It turns out, however, that SDL3 has a pen tablet API builtin, and thus I was able to add pen pressure support for terrain painting:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;video src=&quot;https://duskworks.net/img/terrpressure.mp4&quot; controls=&quot;&quot;&gt;
&lt;/video&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;Using pen pressure to adjust the strength of the painting, note the small indicator on the bottom left&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I was looking into what I&#39;d have to do using platform native APIs when I had a random thought: what if SDL3 has something for pen tablets too? And I&#39;m glad I checked because it only took a few lines of code to make it work:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DuskRawInput&lt;/span&gt;&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ProcessPenEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; SDL_Event&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; SDL_EVENT_PEN_AXIS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paxis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;axis &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; SDL_PEN_AXIS_PRESSURE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            mPenPressure &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paxis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code will get more complex as more features get added, but at this point in time, just keeping track of the pressure, and mixing mouse and pen events together, works well enough.&lt;/p&gt;
&lt;h1 id=&quot;added-fmod-support&quot;&gt;Added FMOD support&lt;/h1&gt;
&lt;p&gt;I&#39;ve been curious to try FMOD Studio for a couple of years now and I made the switch from SoLoud to FMOD this month. The switch has large implications when it comes to how audio works in the engine, especially on the gameplay scripting side as it needs to be aware of FMOD Studio events and parameters.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-3/KZ0OMueau9-1403.avif 1403w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-3/KZ0OMueau9-1403.webp 1403w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Screenshot of FMOD Studio&quot; src=&quot;https://duskworks.net/blog/duskengine-devlog-3/content/blog/duskengine-devlog-3/KZ0OMueau9-1403.jpeg&quot; width=&quot;1403&quot; height=&quot;703&quot;&gt;&lt;/picture&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;An event I made that provides background sound for an electric lamp. It uses a parameter called MagicalInterference. In-game, when magic stuff is near the light, this parameter value, which ranges from 0.0 to 1.0, is altered to make the sound more intense&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Management of the raw audio files (.ogg, .wav etc.) is now handled by FMOD Studio, which builds &lt;em&gt;bank files&lt;/em&gt; out of all raw audio files as well as all the events and parameters that were created in the FMOD project — these bank files are now the actual audio assets as far as DuskEngine is concerned. Besides being used at gameplay time, the editor also reads them to build the list of events and parameters, to provide intellisense in Lua:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- it generates such code.&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- strings like &quot;event:/ElectricLampBuzzLoop&quot; come from&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- the bank files. When this script gets executed,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- it automatically queries for the event and param IDs&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- so as to not involve strings when triggering sounds.&lt;/span&gt;
ElectricLampBuzzLoop &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Engine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetAudioEventId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;event:/ElectricLampBuzzLoop&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    Params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        MagicalInterference &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            Id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Engine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetAudioEventParamId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;event:/ElectricLampBuzzLoop&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
                &lt;span class=&quot;token string&quot;&gt;&quot;MagicalInterference&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Scripts can then:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;audioSource&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PlayEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ElectricLampBuzzLoop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- and&lt;/span&gt;
audioSource&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;SetActiveEventParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    ElectricLampBuzzLoop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MagicalInterference&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- and so on.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m excited to see what other cool stuff I&#39;ll be able to do on the audio side in the future. With FMOD Studio it does seem like the sky&#39;s the limit.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;While this is all that&#39;s noteworthy on the engine side, there has been a ton of work on the gameplay side, including the implementation of fun activities like fishing and cooking, as well as skills for said activites, with XP and level ups and all the cool things.&lt;/p&gt;
&lt;p&gt;It&#39;s all (mostly) too placeholder-y to show at the moment, but I&#39;ll post more updates soon.&lt;/p&gt;
&lt;p&gt;An old preview of the cooking activity can be seen &lt;a href=&quot;https://bsky.app/profile/fireflyinthedusk.bsky.social/post/3m7vbxal7ok2b&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;BTW, I uploaded the stove model to OpenGameArt &lt;a href=&quot;https://opengameart.org/content/cast-iron-stove&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
&lt;/p&gt;&lt;div style=&quot;display:flex; justify-content:flex-end;&quot;&gt;
&lt;span style=&quot;font-style: italic&quot;&gt;For any questions or feedback, find me on &lt;a href=&quot;https://bsky.app/profile/fireflyinthedusk.bsky.social&quot;&gt;Bluesky&lt;/a&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div style=&quot;display:flex; justify-content:flex-end;&quot;&gt;
&lt;span style=&quot;font-style: italic&quot;&gt;Firefly in the Dusk&lt;/span&gt;&lt;span&gt;🌙&lt;/span&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>DuskEngine Devlog 2 - Entirely too much navmesh I&#39;ve had enough edition</title>
    <link href="https://duskworks.net/blog/duskengine-devlog-2/" />
    <updated>2025-12-05T00:00:00Z</updated>
    <id>https://duskworks.net/blog/duskengine-devlog-2/</id>
    <content type="html">&lt;p&gt;The previous month no navmeshing happened even though it should have, this month so much happened I&#39;ve had enough. There&#39;s more than just navigation to talk about however, so let&#39;s get to it.&lt;/p&gt;
&lt;h1 id=&quot;navmesh-and-pathfinding&quot;&gt;Navmesh &amp;amp; Pathfinding&lt;/h1&gt;
&lt;p&gt;I started out using Recast for this, and for a while things went approximately well, especially because of Ravbug&#39;s sample which provided a &lt;a href=&quot;https://github.com/RavEngine/RavEngine/blob/master/src/NavMeshComponent.cpp&quot;&gt;nice starting point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In order to have obstacle support you need to use a tiled navmesh rather than a basic one, and after days of trying to figure out which of the bazillion parameters Recast expected weren&#39;t right (the navmesh was always either broken or wrong in some subtle way), or if it was my geometry causing issues, I decided I have better things to do with my time, so I put together a quick grid-based solution instead.&lt;/p&gt;
&lt;p&gt;It needs a lot more work, but it&#39;s a starting point. Characters are able to pathfind anywhere in the scene and I can prototype gameplay.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NavPlane&lt;/code&gt; entities define the areas that are meant to be walkable:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-2/OBOtCZzGOE-1264.avif 1264w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-2/OBOtCZzGOE-1264.webp 1264w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Screenshot&quot; src=&quot;https://duskworks.net/blog/duskengine-devlog-2/content/blog/duskengine-devlog-2/OBOtCZzGOE-1264.jpeg&quot; width=&quot;1264&quot; height=&quot;703&quot;&gt;&lt;/picture&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;In this image the NavPlane normally covers the entire floor, but I made it smaller &#39;cause it&#39;s easier to see&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-2/D1VN0EPTXR-1164.avif 1164w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-2/D1VN0EPTXR-1164.webp 1164w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Screenshot&quot; src=&quot;https://duskworks.net/blog/duskengine-devlog-2/content/blog/duskengine-devlog-2/D1VN0EPTXR-1164.jpeg&quot; width=&quot;1164&quot; height=&quot;1034&quot;&gt;&lt;/picture&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;Entities with the &lt;code&gt;NavCarver&lt;/code&gt; component dig holes into the &lt;code&gt;NavPlane&lt;/code&gt;s&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;NavPlane&lt;/code&gt;s themselves are not grids, there is only one global (infinite) grid, and it&#39;s the parts of it that are covered by navplanes that are considered for pathfinding. Indeed, this means the objects in the scene need to be more or less aligned to this infinite grid, to make efficient use of the space when pathfinding.&lt;/p&gt;
&lt;p&gt;At the end a &lt;code&gt;NavGrid&lt;/code&gt; object gets built - it&#39;s the thing that gets queried for paths and updated for obstacles. It is built by taking all the information from &lt;code&gt;NavPlane&lt;/code&gt;s and putting it together.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-2/gFxLiKIkqj-337.webp 337w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Screenshot&quot; src=&quot;https://duskworks.net/blog/duskengine-devlog-2/content/blog/duskengine-devlog-2/gFxLiKIkqj-337.gif&quot; width=&quot;337&quot; height=&quot;449&quot;&gt;&lt;/picture&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;Dynamic obstacles such as doors are also working&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Sphere is the only obstacle shape supported right now, for the door a box would be better but that will be implemented sometime later.&lt;/p&gt;
&lt;p&gt;I&#39;m not sure if in the future I&#39;ll still be using grids. I know for sure I&#39;ll be forced to add an extra dimension once buildings with multiple floors are required. This will likely happen next year. In that case &lt;code&gt;NavPlane&lt;/code&gt;s would become &lt;code&gt;NavVolume&lt;/code&gt;s or &lt;code&gt;NavCube&lt;/code&gt;s, and rather than a 2D grid I&#39;ll likely deal with 3D voxel space. Agent height would also suddenly matter.&lt;/p&gt;
&lt;p&gt;Dynamic obstacles are important (doors, or placing boxes or other obstacles in front of doors, stuff like that), and grids and their kin make supporting them extremely simple. Navmeshes would require rebuilding the parts that overlap with the obstacles, which is much more expensive than updating some flags on a grid.&lt;/p&gt;
&lt;p&gt;I&#39;m using a basic A* implementation to pathfind in the grid at the moment.&lt;/p&gt;
&lt;p&gt;OK, enough about pathfinding. The rest of the cool new things follow:&lt;/p&gt;
&lt;h1 id=&quot;extra-vegetation-system-features&quot;&gt;Extra vegetation system features&lt;/h1&gt;
&lt;p&gt;DuskEngine features a vegetation system with which you can place vegetation patches in a scene. A vegetation patch is essentially a rectangle in which vegetation (meshes that sway in the &amp;quot;wind&amp;quot; and get pushed away by volumes such as characters) get spawned. You can use a brush to hide or show some of the spawned plants.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;video src=&quot;https://duskworks.net/img/vegepaint.mp4&quot; controls=&quot;&quot;&gt;
&lt;/video&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;It works like this...&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Up until now, grass and flowers were the only use case, so the vegetation system didn&#39;t need much in the way of shadows (it kinda made the grass look very crowded actually).&lt;/p&gt;
&lt;p&gt;I modeled some cattail meshes and also made the system support shadowcasting vegetation. Grass still doesn&#39;t cast any shadows, but cattail needs to because it&#39;s large and looks weird without shadows.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-2/QDUDhzYNwV-1362.avif 1362w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-2/QDUDhzYNwV-1362.webp 1362w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Screenshot&quot; src=&quot;https://duskworks.net/blog/duskengine-devlog-2/content/blog/duskengine-devlog-2/QDUDhzYNwV-1362.jpeg&quot; width=&quot;1362&quot; height=&quot;1042&quot;&gt;&lt;/picture&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;The Cattail&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h1 id=&quot;asset-importing-cooking-baking-made-nicer&quot;&gt;Asset importing/cooking/baking made nicer&lt;/h1&gt;
&lt;p&gt;The following is only relevant when running the engine editor.&lt;/p&gt;
&lt;p&gt;When something wants to load an asset, the asset manager first checks if there&#39;s a pre-baked version of the asset. If there isn&#39;t, or if it&#39;s outdated (because the source file changed since it was baked), it triggers a re-bake.&lt;/p&gt;
&lt;p&gt;Because this is done on-demand, it introduces stuttering as new stuff comes into view, or presents a black screen for a pretty long time when a scene is first loaded, assuming for example that the entire asset cache has been cleared.&lt;/p&gt;
&lt;p&gt;I made the editor first check that everything&#39;s properly pre-baked before even trying to give the engine instance something to do:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-2/AUDk9Y7Fqw-552.webp 552w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Screenshot&quot; src=&quot;https://duskworks.net/blog/duskengine-devlog-2/content/blog/duskengine-devlog-2/AUDk9Y7Fqw-552.gif&quot; width=&quot;552&quot; height=&quot;212&quot;&gt;&lt;/picture&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;I love this dialog, it looks so legit&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Showing this dialog wasn&#39;t the only reason I did it though. A big feature that the asset manager is missing is async asset loading.&lt;/p&gt;
&lt;p&gt;Having this initial reimport step, before anything interesting even starts to happen, gives me a place where I can experiment with async loading without risk of breaking any of the existing functionality.&lt;/p&gt;
&lt;p&gt;After I figure out what works and what doesn&#39;t, I can look into updating parts of the engine to benefit from async loading.&lt;/p&gt;
&lt;p&gt;This will happen sometime in the future, as it&#39;s not necessarily high priority right now.&lt;/p&gt;
&lt;h1 id=&quot;making-lua-gameplay-scripting-a-bit-nicer&quot;&gt;Making Lua gameplay scripting a bit nicer&lt;/h1&gt;
&lt;p&gt;Errors raised in gameplay Lua scripts used to crash the engine entirely, and I used to restart the whole thing after changing a script, in order to get it reloaded (or rebaked 😝).&lt;/p&gt;
&lt;p&gt;Errors are now logged appropriately and the engine stays running. There&#39;s also a file watcher that evicts scripts that changed from the cache, so when they&#39;re next requested, they&#39;re in their latest version.&lt;/p&gt;
&lt;h1 id=&quot;lod-vertex-deduplication&quot;&gt;LOD vertex deduplication&lt;/h1&gt;
&lt;p&gt;LODs have been supported in the engine for a while now, but each LOD level was treated as an entirely separate mesh. This worked fine, but in most cases there is a very large overlap between the vertices in a lower LOD and those in the higher LODs, which means the same vertices get put into VRAM multiple times, which is wasteful.&lt;/p&gt;
&lt;p&gt;So now as part of the mesh bake process, the engine will go through the lower LODs of that particular mesh and merge them with the original, while avoiding duplicated vertices. Essentially, creating one big buffer with verts, and a big buffer containing the concatenated indices for the original and lower LOD meshes.&lt;/p&gt;
&lt;h1 id=&quot;thats-it&quot;&gt;That&#39;s it&lt;/h1&gt;
&lt;p&gt;That&#39;s about it for this month&#39;s devlog. I&#39;m continuing work on pathfinding and steering behaviors for NPCs, among other things which I&#39;ll show on the next entry and on my bsky. See you next month!&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
&lt;/p&gt;&lt;div style=&quot;display:flex; justify-content:flex-end;&quot;&gt;
&lt;span style=&quot;font-style: italic&quot;&gt;For any questions or feedback, find me on &lt;a href=&quot;https://bsky.app/profile/fireflyinthedusk.bsky.social&quot;&gt;Bluesky&lt;/a&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div style=&quot;display:flex; justify-content:flex-end;&quot;&gt;
&lt;span style=&quot;font-style: italic&quot;&gt;Firefly in the Dusk&lt;/span&gt;&lt;span&gt;🌙&lt;/span&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>DuskEngine Devlog 1 - Mostly Physics Edition</title>
    <link href="https://duskworks.net/blog/duskengine-devlog-1/" />
    <updated>2025-11-09T00:00:00Z</updated>
    <id>https://duskworks.net/blog/duskengine-devlog-1/</id>
    <content type="html">&lt;p&gt;In the previous entry I said that I&#39;d be working on navmeshes, but something must have happened because I switched to physics for a while.&lt;/p&gt;
&lt;p&gt;In any case, here are all the new and noteworthy things.&lt;/p&gt;
&lt;h1 id=&quot;primitive-colliders&quot;&gt;Primitive Colliders&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jrouwe/JoltPhysics&quot;&gt;Jolt&lt;/a&gt; has lots of features but the engine needs to have its own functionality that wraps it, which takes some time to write.&lt;/p&gt;
&lt;p&gt;To save on implementation time, up until now, physics objects (which is what DuskEngine calls rigidbodies) were limited to a single collider. Said collider had to be a component on the same entity as the physics object component, and it had to be a mesh collider, either convex or regular.&lt;/p&gt;
&lt;p&gt;As levels are starting to get larger and filled with more complex geometry such as buildings, I&#39;ve added support for primitive colliders such as boxes and spheres, and now I also aggregate all colliders in the hierarchy of an object. This makes it so a physics object can have as many colliders as needed.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-1/fgPChLL63y-800.avif 800w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-1/fgPChLL63y-800.webp 800w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Screenshot&quot; src=&quot;https://duskworks.net/blog/duskengine-devlog-1/content/blog/duskengine-devlog-1/fgPChLL63y-800.jpeg&quot; width=&quot;800&quot; height=&quot;602&quot;&gt;&lt;/picture&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;Three primitive colliders attached to a physics object. The visualization needs some more work...&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The primitive colliders will also come in handy for objects that move, as they&#39;ll be much faster to check for collisions, and I remember somewhere I placed a hack to make convex mesh colliders work in such a context, I&#39;ll finally be able to delete said hack.&lt;/p&gt;
&lt;h1 id=&quot;physics-layers&quot;&gt;Physics Layers&lt;/h1&gt;
&lt;p&gt;Yet another important feature that DuskEngine finally gained.&lt;/p&gt;
&lt;p&gt;There are eight (might tweak this number) available physics layers to assign objects to, and masks can be used when e.g. casting rays or shapes. A layer collision matrix is editable in the project settings, it decides which pairs of layers can collide.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-1/RSuleOAJgh-822.avif 822w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-1/RSuleOAJgh-822.webp 822w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Screenshot&quot; src=&quot;https://duskworks.net/blog/duskengine-devlog-1/content/blog/duskengine-devlog-1/RSuleOAJgh-822.png&quot; width=&quot;822&quot; height=&quot;758&quot;&gt;&lt;/picture&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;This project settings window is also a recent addition&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The reason I added layers now was because I wanted to improve the third person camera, so that the player cannot accidentally push it inside the level geometry.&lt;/p&gt;
&lt;p&gt;Raycasting provided a good starting point (casting rays to see if there&#39;s an obstacle in the way), but it quickly became clear that not every piece of level geometry is equal in this respect. For example, I definitely want to tweak the camera position if there&#39;s a large building in the way, but if it&#39;s a fence, even if most of the player&#39;s character is covered by it, I think the camera could stay in place.&lt;/p&gt;
&lt;p&gt;My current idea to handle this is to have a separate set of colliders exclusively for this purpose, essentially describing the bounds within which the camera can exist. Their parent objects are set on the &lt;code&gt;CameraBounds&lt;/code&gt; layer which does not collide with anything, as it&#39;s only there for raycasting purposes.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-1/ynzCEmyYk4-524.webp 524w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Screenshot&quot; src=&quot;https://duskworks.net/blog/duskengine-devlog-1/content/blog/duskengine-devlog-1/ynzCEmyYk4-524.gif&quot; width=&quot;524&quot; height=&quot;282&quot;&gt;&lt;/picture&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;The camera won&#39;t go through the building&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Just this is insufficient to handle arbitrary level designs however so it&#39;s something I&#39;ll tweak as I go, but this is as good a starting point as any.&lt;/p&gt;
&lt;p&gt;I have also updated some of the existing scripts so they make use of layers. For example, some spell effects that cast shapes to check if they hit anything, now exclusively check the &lt;code&gt;Characters&lt;/code&gt; layer.&lt;/p&gt;
&lt;p&gt;This also reduces the data payload that needs to make it back to Lua, and the amount of work it then has to do when checking whether or not the entities that were hit by the shapecast can take damage or have some other effect applied to them.&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;No navmeshes happened in the past month, but the new features are important additions nonetheless.&lt;/p&gt;
&lt;p&gt;There is a ton of work left to make the editor side of the new features be as user-friendly as possible. Better visualization for the colliders, ensuring that things like invalid scales originating from the editor don&#39;t make it all the way to Jolt, assigning colors to each physics layer, so colliders in different layers are colored differently, an UI letting you decide which layers get rendered at all, and lots more that I probably haven&#39;t discovered yet.&lt;/p&gt;
&lt;p&gt;The next major physics feature that will be required someday is support for trigger-type objects (those that only raise events when they collide with something, and are otherwise pass-through).&lt;/p&gt;
&lt;p&gt;I&#39;ll wait until I have to implement a gameplay feature that needs it, it&#39;s always more fun that way.&lt;/p&gt;
&lt;p&gt;Now back to navmeshes... surely.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
&lt;/p&gt;&lt;div style=&quot;display:flex; justify-content:flex-end;&quot;&gt;
&lt;span style=&quot;font-style: italic&quot;&gt;For any questions or feedback, find me on &lt;a href=&quot;https://bsky.app/profile/fireflyinthedusk.bsky.social&quot;&gt;Bluesky&lt;/a&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div style=&quot;display:flex; justify-content:flex-end;&quot;&gt;
&lt;span style=&quot;font-style: italic&quot;&gt;Firefly in the Dusk&lt;/span&gt;&lt;span&gt;🌙&lt;/span&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>DuskEngine Devlog 0 - Quick Overview</title>
    <link href="https://duskworks.net/blog/duskengine-devlog-0/" />
    <updated>2025-10-17T00:00:00Z</updated>
    <id>https://duskworks.net/blog/duskengine-devlog-0/</id>
    <content type="html">&lt;p&gt;DuskEngine is a game engine that I&#39;m writing for myself. I started development on it about three years ago. I am also currently designing a game that will use it, but I try to have a more generic approach wherever possible; at the end of the day I wouldn&#39;t mind using this engine for more than one game.&lt;/p&gt;
&lt;p&gt;I post about it a lot on my &lt;a href=&quot;https://bsky.app/profile/fireflyinthedusk.bsky.social&quot;&gt;bsky&lt;/a&gt; so head on there to see more videos and screenshots and such.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-0/Rwtnld2Zgf-2178.avif 2178w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-0/Rwtnld2Zgf-2178.webp 2178w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Screenshot&quot; src=&quot;https://duskworks.net/blog/duskengine-devlog-0/content/blog/duskengine-devlog-0/Rwtnld2Zgf-2178.jpeg&quot; width=&quot;2178&quot; height=&quot;1256&quot;&gt;&lt;/picture&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;The development scene in DuskEngine at the time of writing&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2 id=&quot;technical-details&quot;&gt;Technical details&lt;/h2&gt;
&lt;p&gt;I use C++ and the bulk of development happens on Windows. The renderer there uses OpenGL. MacOS is also supported (I ensure the project builds and runs on a weekly basis), but the Metal renderer is very far behind in terms of features (due to a lack of time on my part). But it does have raytraced shadows, so there&#39;s that 😝&lt;/p&gt;
&lt;p&gt;The engine compiled and ran on Linux a few months ago but unlike MacOS, I don&#39;t make Linux builds regularly, but would like to change this eventually.&lt;/p&gt;
&lt;p&gt;I normally think of DuskEngine as &amp;quot;the editor&amp;quot; together with &amp;quot;the engine&amp;quot;. The editor, when you make a build that includes it, kinda sits on top of the engine and represents all the editor tools that you use to make your game. The engine is the thing you turn on which then does its thing.&lt;/p&gt;
&lt;p&gt;The editor uses Dear ImGui for the interface.&lt;/p&gt;
&lt;p&gt;Both the editor and the engine (i.e. play mode) use Lua for scripting purposes. I use &lt;a href=&quot;https://luajit.org/&quot;&gt;LuaJIT&lt;/a&gt; and &lt;a href=&quot;https://github.com/ThePhD/sol2&quot;&gt;sol2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can even write entire editor windows in Lua, as can be seen &lt;a href=&quot;https://bsky.app/profile/fireflyinthedusk.bsky.social/post/3m2mfxudotk2l&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Whirlwind tour of some of the other larger deps:&lt;/p&gt;
&lt;p&gt;I use &lt;a href=&quot;https://github.com/jrouwe/JoltPhysics&quot;&gt;Jolt&lt;/a&gt; for physics.&lt;/p&gt;
&lt;p&gt;And &lt;s&gt;&lt;a href=&quot;https://github.com/jarikomppa/soloud&quot;&gt;SoLoud&lt;/a&gt;&lt;/s&gt; &lt;a href=&quot;https://www.fmod.com/&quot;&gt;FMOD&lt;/a&gt; for audio, see &lt;a href=&quot;https://duskworks.net/blog/duskengine-devlog-3&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/assimp/assimp&quot;&gt;Assimp&lt;/a&gt; for mesh loading.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://uscilab.github.io/cereal/&quot;&gt;Cereal&lt;/a&gt; for binary serialization. There is an asset import (and reimport) functionality, which takes the raw asset files (such as .pngs, or .gltf) and turns them into engine-specific data. Cereal is used to serialize and deserialize this data.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2&lt;/a&gt; for windowing and input.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/g-truc/glm&quot;&gt;GLM&lt;/a&gt; for math.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.boost.org/&quot;&gt;boost&lt;/a&gt; for json parsing and writing, uuid generation and others.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/wolfpld/tracy&quot;&gt;Tracy&lt;/a&gt; for profiling.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/CedricGuillemet/ImGuizmo&quot;&gt;ImGuizmo&lt;/a&gt; for the editor gizmos.&lt;/p&gt;
&lt;p&gt;BTW, unlike the editor UIs which use ImGui, the gameplay UIs use a custom implementation. Gameplay UIs are assets and there&#39;s an editor for them too:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;in-post-video-frame&quot;&gt;
&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-0/oa5pl_5Uyz-1435.avif 1435w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://duskworks.net/blog/duskengine-devlog-0/oa5pl_5Uyz-1435.webp 1435w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Screenshot&quot; src=&quot;https://duskworks.net/blog/duskengine-devlog-0/content/blog/duskengine-devlog-0/oa5pl_5Uyz-1435.jpeg&quot; width=&quot;1435&quot; height=&quot;1108&quot;&gt;&lt;/picture&gt;
&lt;p class=&quot;in-post-image-subtitle&quot;&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;There is a lot of work left to do for the UI side of things, besides the layout, the renderer also needs improvements.&lt;/p&gt;
&lt;p&gt;It is somewhat similar to the one in Unity, using pivot points, anchoring, margins and so on.&lt;/p&gt;
&lt;p&gt;Unlike Unity, the UIs are not objects in the scene. You can have as many UIs as you want, even if they&#39;re not rendered by a camera (you might render them to textures). The scripts that manage those UIs choose when and how they update.&lt;/p&gt;
&lt;p&gt;I will likely refer to Unity again in future articles, because it&#39;s the engine I have the most experience with (I&#39;ve been using it for about 10 years at the time of starting work on DuskEngine, I don&#39;t really use it anymore), so my idea of what an engine should be like is at least in some amount, inspired by it.&lt;/p&gt;
&lt;p&gt;For a fun UI system example, see &lt;a href=&quot;https://bsky.app/profile/fireflyinthedusk.bsky.social/post/3m2zgkuffjs2a&quot;&gt;this mobile phone UI&lt;/a&gt; which gets rendered to a texture that then gets applied to the phone screen. The mobile phone controller script uses raycasting from the mouse cursor position, by converting the ray hit point from the phone screen&#39;s collider&#39;s coordinates into UI (essentially UV) coordinates. Finally, it triggers an update with the generated inputs to simulate the touchscreen.&lt;/p&gt;
&lt;h2 id=&quot;what-i-m-actively-working-on-right-now&quot;&gt;What I&#39;m actively working on right now&lt;/h2&gt;
&lt;p&gt;I want to implement some fancier NPC behaviors and so I&#39;ve added navmesh support via the &lt;a href=&quot;https://github.com/recastnavigation/recastnavigation&quot;&gt;Recast library&lt;/a&gt;. I&#39;m currently making it work all the way from the asset side to gameplay scripting.&lt;/p&gt;
&lt;h2 id=&quot;tech-goals-and-questions-for-the-near-ish-future&quot;&gt;Tech goals and questions for the near(ish) future&lt;/h2&gt;
&lt;p&gt;Occlusion culling needs implementing. Especially important as I&#39;ve started adding more and more buildings to the scenes.&lt;/p&gt;
&lt;p&gt;More renderer features with better visuals are needed.&lt;/p&gt;
&lt;p&gt;I really need to set up some kind of build system, I use a Visual Studio solution and an Xcode project for Windows and Mac development, and whenever I change something in one I need to also change in the other. The versions of some of the dependencies are also different between the two platforms, which will eventually need to be fixed. Setting up something like CMake will allow me to streamline this process and also start making Linux builds periodically, but I&#39;m so not looking forward to learning CMake&#39;s scripting language and since this doesn&#39;t really cause much pain during day to day development, I&#39;m postponing it as much as possible 😭&lt;/p&gt;
&lt;p&gt;Compile times are slow 😞 I am a bit conflicted on how high a priority this should have, because my Mac compiles lightning fast compared to my old Windows laptop, but there is plenty of room for optimizing the compile times. I&#39;ve done a few such optimization passes in the past, with good results. I&#39;m not a fan of all the hacks and workarounds C++ programmers seem to have to do in order to make up for this antiquated build system. I haven&#39;t taken a look at module support in a while, but I doubt Apple has it yet so...&lt;/p&gt;
&lt;p&gt;I need to set up an experiment to see what it&#39;d be like for two or more developers to try to work on the same DuskEngine project, through something like branches and PRs in Git. Everything is text (JSON), but I&#39;m not yet sure how smooth it would be. I know collaboration can be a major PITA, and would like to run into issues and fix them ASAP.&lt;/p&gt;
&lt;p&gt;Another feature is the asset store functionality, which at the moment is more or less a stub, but on which I&#39;d like to continue work sometime in the near future.
This is so that the issue that some assets can come from a server somewhere, and need to support updating when new versions get released, is tackled as soon as possible.&lt;/p&gt;
&lt;p&gt;On a somewhat tangential topic, I&#39;m not yet sure how I&#39;m going to handle having the games download assets while running, like how mobile games do it for example.&lt;/p&gt;
&lt;p&gt;Also, two things I&#39;d like to add in the future is support for web and at least one mobile platform (probably iOS because I already have the Metal renderer). Web is the most important because it&#39;d make sharing builds with the world much easier. Mobile is so I get to see what packaging a game would work like under the constraints of mobile platforms.&lt;/p&gt;
&lt;p&gt;Plus a bunch more features that I&#39;m forgetting about 😄&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
&lt;/p&gt;&lt;div style=&quot;display:flex; justify-content:flex-end;&quot;&gt;
&lt;span style=&quot;font-style: italic&quot;&gt;For any questions or feedback, find me on &lt;a href=&quot;https://bsky.app/profile/fireflyinthedusk.bsky.social&quot;&gt;Bluesky&lt;/a&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div style=&quot;display:flex; justify-content:flex-end;&quot;&gt;
&lt;span style=&quot;font-style: italic&quot;&gt;Firefly in the Dusk&lt;/span&gt;&lt;span&gt;🌙&lt;/span&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My first post</title>
    <link href="https://duskworks.net/blog/helloworld/" />
    <updated>2025-06-13T00:00:00Z</updated>
    <id>https://duskworks.net/blog/helloworld/</id>
    <content type="html">&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;This is the first post here, just to test the functionality. Stay tuned for actual gamedev articles.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
&lt;/p&gt;&lt;div style=&quot;display:flex; justify-content:flex-end;&quot;&gt;
&lt;span style=&quot;font-style: italic&quot;&gt;For any questions or feedback, find me on &lt;a href=&quot;https://bsky.app/profile/fireflyinthedusk.bsky.social&quot;&gt;Bluesky&lt;/a&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div style=&quot;display:flex; justify-content:flex-end;&quot;&gt;
&lt;span style=&quot;font-style: italic&quot;&gt;Firefly in the Dusk&lt;/span&gt;&lt;span&gt;🌙&lt;/span&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
</content>
  </entry>
</feed>