<rss version="2.0">
  <channel>
    <title>Derelict Core</title>
    <link>https://derelict.dotnest.net/blog</link>
    <description><![CDATA[What little is left at the end of the day.]]></description>
    <item>
      <title>Staring at Tiny Windows</title>
      <link>https://derelict.dotnest.net/derelict-core/big-peek</link>
      <description><![CDATA[<h2 id="first-world-problems">First World Problems</h2>
<p>Pixel games are really something else. They look great when sharp but some creators take it a bit too far. If you like them, you have probably met your fair share of titles made in tools (like certain versions of Clickteam Fusion, RPG Maker and Adventure Game Studio) that are less friendly with high res or (ultra)wide monitors in full screen mode. I've seen a lot that either flat-out refuse to go full screen or cause the display to commit sudoku right there as the monitor is trying to display a tiny resolution it just can't comprehend any more. This happens even with relatively modern software! If you want to play something from a few decades back, you better hope there is a console port you can emulate because PC games that want to play at 320x240 are pretty much a lost cause... or are they?</p>
<p>Maybe you've faced the same problem and thought of using the Magnifier tool in Windows while the game is in windowed mode. That's a good start, but centering the screen is extremely fiddly. If the game doesn't capture your mouse, you are doomed to accidentally nudge it and pan the screen way far away from the window you are actually interested in. Also the magnifier's zoom levels are not very fine so you can leave a lot of dead space. What if there is a more civilized tool for the job?</p>
<h2 id="a-niche-solution">A Niche Solution</h2>
<p><div>
  <video  loop muted autoplay src="/media/big-peek/video.mp4" alt="Big Peek! demo" style="width: 100%"></video>
</div>
<div style="text-align:center; width: 100%">
  Introducing <span style="text-decoration: underline"><a href="https://github.com/sarahelsaig/DerelictCore.BigPeek">Big Peek!</a></span>
</div></p>
<p>So it turns out you can access the <a href="https://learn.microsoft.com/en-us/previous-versions/windows/desktop/magapi/magapi-intro">Windows Magnification API</a> from code. It has basically infinite level of precision and you can fix the zoom location. The Magnifier utility just won't trust you with such power!</p>
<p>I made my own utility called <em>Big Peek!</em> (enthusiasm is part of the correct pronunciation). A simple window that can target another window on the same screen. It simply does a pan &amp; zoom to fit around the selected window, centering it as much as possible. Then it stays like that until you hit Ctrl+Win+X. It's not doing anything weird like polling to see if the target is still alive, so if you forgot the combo you are trapped in zoom land. (skill issue) Also it does some non-permanent logging in ts own window, though it only shows the window title and the zoom rectangle (in X11 geometry notation, just to remind myself that I haven't sold my soul to Bill Gates just yet).</p>
<p>If you are interested you can find the Git repository along with the download link <a href="https://github.com/sarahelsaig/DerelictCore.BigPeek">here</a>!</p>
<h2 id="some-background">Some Background</h2>
<p>It's using WPF for the GUI, because that's what I'm most comfortable with on the desktop and this will never be cross-platform so there is no point trying to re-familiarize myself with <a href="https://avaloniaui.net/">Avalonia</a>. Under the hood it's doing a ton of native interop calls with different Windows APIs. Normally when you have to interact with unmanaged DLLs you start to cry, then once you are done with that you try to look up how to do it with <a href="https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke">P/Invoke</a> and start to cry again. If you are a bit more experienced, you might use a library like <a href="https://github.com/Nihlus/AdvancedDLSupport">AdvancedDLSupport</a>. But the truth is, Windows APIs are used by all kinds of people, someone has mapped the whole thing already! I've used <a href="https://github.com/dahall/Vanara">Vanara</a>, specifically <a href="https://www.nuget.org/packages/Vanara.PInvoke.Magnification#readme-body-tab"><code>Vanara.PInvoke.Magnification</code></a> for the zoom feature and <a href="https://www.nuget.org/packages/Vanara.PInvoke.User32#readme-body-tab"><code>Vanara.PInvoke.User32</code></a> for the rest. The upside of using a library like Vanara is that they have already collected the documentation and the constants, something that's all over the place in Microsoft's original C++ documentation. So if you don't like wild goose chases, this is a huge help. Even then, you will find yourself looking over StackOverflow code made in Visual C++ if you try to interact with these libraries.</p>
<p>What was all that API use for anyway? Most importantly, the Magnification API just lets you do a <a href="https://github.com/sarahelsaig/DerelictCore.BigPeek/blob/784ed0a153023c91076b3816dcf8688279a5ff52/DerelictCore.BigPeek/Services/PeekService.cs#L82">single call full screen transform</a> like you are a trustworthy adult or something! But sadly that was the easy part. I also had to register window events to <a href="https://github.com/sarahelsaig/DerelictCore.BigPeek/blob/784ed0a153023c91076b3816dcf8688279a5ff52/DerelictCore.BigPeek/Services/PeekService.cs#L21">pick a window handle with the mouse</a> and had to implement <a href="https://github.com/sarahelsaig/DerelictCore.BigPeek/blob/784ed0a153023c91076b3816dcf8688279a5ff52/DerelictCore.BigPeek/Services/HotkeyService.cs">a whole hotkey service</a> using global keyboard hooks just to register the unzoom hotkey. WPF only lets you register hotkeys within its own window (afaik Winforms is the same), but if you have already selected the Big Peek! window you could just as well unzoom with Alt+F4. It wasn't that harrowing thanks to these NuGet packages, but it still felt like studying occult magic. I was actually surprised when I looked for a solution for global hotkeys in WPF and people just casually suggested using P/invoke, because apparently there is no pre-made library for this. Weird. Anyway, if you need a global hotkey service feel free to yoink the file I've just linked.</p>
<h2 id="note">Note</h2>
<p>I've shown an example above using <a href="https://store.steampowered.com/app/2121980/Void_Stranger/">Void Stranger</a>, it very conveniently starts in a borderless window anyway. This is very rare. Personally I'm not bothered by window titles and borders, but if you want to get rid of them you can always bully the window manager into doing your bidding. One option is <a href="https://github.com/Codeusa/Borderless-Gaming">Borderless Gaming</a>, but it's meant for borderless full screen so it will pull the window to the top right corner. You can also use <a href="https://github.com/AutoHotkey/AutoHotkey">AutoHotKey</a> with the <code>WinSet, Style, -0xC00000, ahk_exe filename.exe</code> command with filename.exe correctly substituted. Hope that helps.</p>
]]></description>
      <pubDate>Sun, 18 Feb 2024 14:01:01 GMT</pubDate>
      <guid isPermaLink="true">https://derelict.dotnest.net/derelict-core/big-peek</guid>
    </item>
    <item>
      <title>Console Gaming on a Laptop</title>
      <link>https://derelict.dotnest.net/derelict-core/console-gaming-on-a-laptop</link>
      <description><![CDATA[<p>No, this isn't about emulators. For sure, we live in a golden age of console emulation where you can emulate almost all consoles up to 2 console generations ago (e.g. PS3 and Xbox 360) and even some newer ones. But what if you don't want to emulate? Maybe you still want to use your original hardware but your TV is toast? Maybe you want to use your PlayStation on the train station? Nothing can stop you.</p>
<p>In these situations you need a video capture device (sometimes called an external capture card). I used the <a href="https://uk.hama.com/00074257/hama-video-recording-stick-usb-plug-hdmi-socket-4k">Hama 4K Video Recording Stick</a> because it's got one of the best reviews for something that's not in the &quot;business expense&quot; price range. I didn't notice any lag while testing it with a Nintendo Switch and Sony PlayStation 2 (using either a separate AV-to-HDMI adapter or an upscaler like the RetroTINK 2K). It actually looks better than sending the same signal directly to a monitor, because you are allowed to upscale without blurring everything.</p>
<p><img class="vue-carousel" src="/media/ps2-capture/00074257abx.png"></p>
<p>Of course these things are made for archival or streaming, so it takes some software abuse to get what we want. I found two workable options.</p>
<h2 id="obs-studio">OBS Studio</h2>
<p>Using <a href="https://obsproject.com/"><strong>OBS Studio</strong></a>. This open source and cross-platform screen-casting app is one of the top software for creating dynamic picture-in-picture scenes, mixing multiple different audio, video and static sources, creating elaborate streaming (or video conferencing) layouts, etc. It will happily consume any video source you throw at it. If you are so inclined you can even set up multiple scenes, e.g. for custom screen borders or to quickly zoom in during cutscenes with 4:3 games that do the old cinematic letterboxing nonsense. At the simplest, you can quickly create a single full screen projection like this:</p>
<ol>
<li>Find the <em>Scenes</em> area at the bottom left. Click the &quot;+&quot; button to add a new scene.</li>
<li>Find the Sources area right of that and click its &quot;+&quot; button to add a <em>Video Capture Device</em>.</li>
<li>Right click on the scene you created and select <em>Fullscreen Projector (Scene)</em>.</li>
</ol>
<p>Bonus points if you also click the <em>Start Virtual Camera</em> button on the bottom right and show up to your next Teams or Zoom meeting as the famous Solid Snake...</p>
<h2 id="html">HTML</h2>
<p>But did you know <strong>HTML5</strong>  can also do the job reasonably well without having to install anything? It just takes some coaxing, because for some reason the browser wants you to tell it what resolution you want... without letting you query the device about what kind of resolutions it supports! I wrote a simple page you can download below with two presets for full HD (1920x1080) and retro (640x480) screens. It lets you display the video from the capture card and gives instructions how to set up the audio for Windows and Linux. Sadly this is necessary because (at least for me) playing the audio through the HTML <code>&lt;video&gt;</code> element was super lagged and completely useless. Not piping the audio through the browser should in theory also reduce latency.</p>
<p><a href="https://derelict.dotnest.net/media/ps2-capture/capture-device-playback.zip">Download <em>capture-device-playback</em> page!</a></p>
<p><img src="/media/ps2-capture/html-page.png"></p>
<p>As an example, here you can see when I was playing Guilty Gear XX #Reload (that's pronounced as &quot;guilty gear excess - sharp reload&quot; for some objectively cool reason). The signal is coming from the PS2 to the side into my laptop running the Arch Linux based EndeavourOS. It's a pretty luggable setup, now you can play anywhere as long as it's anywhere near a power outlet.</p>
<p><img src="/media/ps2-capture/20231223_001310.jpg">
The striking yellow base of the PS2 is <a href="https://www.thingiverse.com/thing:1606616">a vertical stand I found on Thingiverse</a>. And this was my excuse to sneak in the &quot;3D printing&quot; tag...</p>
<p><img src="/media/ps2-capture/20231223_001459.jpg">
I'm running Sol Badguy, because I'm very basic.</p>
<h2 id="the-end">The End</h2>
<p>In the end this was nowhere nearly as clunky as I expected. The whole project is very niche, but I hope it's useful for anyone with these constraints.</p>
]]></description>
      <pubDate>Sat, 23 Dec 2023 04:31:49 GMT</pubDate>
      <guid isPermaLink="true">https://derelict.dotnest.net/derelict-core/console-gaming-on-a-laptop</guid>
    </item>
    <item>
      <title>Designing for the Real World</title>
      <link>https://derelict.dotnest.net/zynq-enclosure</link>
      <description><![CDATA[<p>And now on to a much cooler topic: 3D printing. They are pretty futuristic even when you aren't using them along with space tech. But we are...</p>
<h2 id="computer-aided-design">Computer Aided Design</h2>
<h3 id="where-do-we-start">Where do we Start?</h3>
<p>To get fabricating, you need designs to realize. You can always make fun figures and other artistic stuff in Blender, but what if you need something precise? Enter <a href="https://openscad.org/">openSCAD</a>. This is the most popular &quot;scripting&quot; CAD software (as opposed to visual designers like Autodesk Fusion or AutoCAD that are more popular with makers but harder to learn and less satisfying for programmers). Though <a href="https://github.com/CadQuery/cadquery">some nice alternatives have sprung up in recent times</a>. OpenSCAD is still the king if you intend to collaborate or create models that webapps can consume and customize. Especially because it has a highly compatible <a href="http://openjscad.azurewebsites.net/">Javascript implementation</a>.</p>
<p>This blog isn't an openSCAD tutorial, I'm about the discuss the design process. If it catches your interest, you can check out the very <a href="https://en.wikibooks.org/wiki/OpenSCAD_User_Manual">detailed Wikibook</a> or find tons of tutorials on YouTube. This is another point where openSCAD is leaps and bounds ahead of other scripting CAD solutions.</p>
<p>Today I will talk about making an enclosure for a single board computer. In this case a <a href="https://trenz-electronic.de/">Trenz Electronic</a> board with a <a href="https://www.xilinx.com/products/silicon-devices/soc/zynq-7000.html">Xilinx Zynq-7000 FPGA accelerator</a> I got lent to use for <a href="http://lombiq.com/">Lombiq</a>'s .Net-to-FPGA solution called <a href="https://hastlayer.com/">Hastlayer</a>. This is a dev board for the same tech whose radiation hardened cousins we should be able to use in satellites so it's really breaking new frontiers. Also it's a good opportinity to check out how you can add branding to your creatong without too much hassle.</p>
<p><img src="/media/zynq-enclosure/20210929_043029.jpg"></p>
<h3 id="preparation">Preparation</h3>
<p>They say measure twice, cut once, but it's even better if you don't have to measure physical objects. If you can find good quality top and side photos you are golden. When it comes to solid components you have a good chance to find such pictures on the manufacturer's website or store. Then you only have to measure one dimension of the board to get the physical size of a pixel and work with pixels in your document. (I saved that into a variable called <code>phy_div</code> in this project.)</p>
<p>Though sometimes you can't find such pictures and you have to break out the calipers. This makes me sad, because mine is garbage. So now it's really time to measure twice or even more! If you have one pic and only need to measure the other dimensions not covered by it, then you can multiply your millimeters by <code>phy_div</code> to get pixels. This way you can stay in a single unit system.</p>
<p>I've measured the 2D dimesions of the board, its screw holes and port locations from the top-down picture <a href="https://shop.trenz-electronic.de/en/TE0706-03-TE0706-Carrierboard-for-Trenz-Electronic-Modules-with-4-x-5-cm-Form-Factor">in their webshop</a> and I used calipers to measure the height of the ports and their distance from the main board. This is all the information needed to set the required holes for the enclosure.</p>
<p><img src="/media/zynq-enclosure/TE0706-03_1_600x600%5B1%5D.jpg"></p>
<h3 id="the-principlies-carving-and-gluing">The Principlies: Carving and Gluing</h3>
<p>OpenSCAD subscribes to the idea of making your model from primitive objects like cubes and couponers cylinders. You perform a series of simple operations like translate, scale or rotate on these shapes. Then you can use Boolean operations to carve (difference), glue (union) or to cut away the non-overlapping parts (intersection). Each operation can contain a block of shapes in it and the Boolean operation is applied to them sequentially.</p>
<p><img src="/media/zynq-enclosure/csg.png"></p>
<p>For example, to make a box you can create two centered cubes. Slightly scale up the first, then take the difference of the two.</p>
<pre><code>difference() {
	scale(1.1) cube();
	cube();
}
</code></pre>
<p>This gives you a hollow box. If you also translate the second box up a bit you get a lidless container. All these simple operations add up.</p>
<p><img src="/media/zynq-enclosure/scad-model.png"></p>
<h3 id="what-to-look-out-for">What to Look out for?</h3>
<p>The goal is to create a box with a ridge for a lid, screw posts, and holes for the IO ports. Later also a lid and a funnel/air duct for a fan. It's important to write every measurement into variables. Not just because you may have to use the same twice (eg. we have 2 Ethernet ports with the same width) but also because things have a tendency to get out of hand in terms of complexity. Later you will woder what that number was for. Better to add 2 or 3 variables and have longer code than to worry about what it means later.
You can also use modules, to create reusable bits or simply to organise certain shapes within the model. You can also use modules to cut perfectly interfacing parts by using <code>difference</code> on your stock with the module of the other part (<code>difference() { cube(); my_module(); }</code>). Everything you write outside of the modules is considered your output. If you are working with modules don't forget to actually instantiate them!</p>
<p>If you measured in pixel units, then you must apply <code>scale(1/phy_div) { ... }</code> to your final output to get back into physical coordinates that your printer can understand. You can do this inside the slicer program too, but this is more reliable.</p>
<h3 id="versions">Versions</h3>
<p>Designing things is an iterative process, it's ok to not have it perfect on the first go. In this project my first attempt was just the bottom part, a one-side-open box with port holes and screw posts. I didn't realize that there was an SMD capacitor right next to one of the screw holes that interfered with its post so I couldn't lay the board flat. Things like this happen, you will be glad if you have a soldering iron. Not for any soldering, but the material of the printed plastic is malleable to heat so we can correct these mistakes. If you don't have one, a heated knife can work as well. This trick can also be used to embed nuts into the object - that way the screw is in place more reliably and won't strip its thread over time. The insides of the shapes are mostly empty. The printer uses one of many possible hash-like patterns for internal support, so when you heat up part of the object you can easily push it in and get rid of such obstructions. This is good enough to patch up prototypes. In the next iteration I've already added a block of <code>difference</code> there to avoid this interference.</p>
<p><img src="https://raw.githubusercontent.com/DAud-IcI/3d-printing/main/TE0706%20carrier%20board/1.jpg"></p>
<p>Not all problems are mechanical. My first successful instance used an acrylic sheet as the top cover that can swivel open. This kept the dust out, but it also trapped the heat in! The top got concerningly warm during use. So I found myself using it &quot;open&quot; most of the time which kind of defeats the purpose. So I started working on the next version with active cooling. I had some leftover small 5V centrifugal fans - the kinds 3D printers use to cool their hot end. Normally you don't need to cool this board so this fan is well overqualified for this task even when not running at full volts. I plugged it into the 3.3V pin on the board's exposed GPIO and it ran almost silently with decent speed. Enough to push the warm air out through the gaps around the ports.
To get the fan on the board I had to ditch the swivel opening for a screw-in top with an intake right above the CPU/FPGA module. That's pretty much in the top center so in case of a direct connection the fan wouldn't fit without overhanging which would make it impossible to fasten to the top. I had to design a duct that connects the fan's exit hole with the lid's intake hole. This took a bit of an advanced openSCAD technique that I have only learned while doing this project called <a href="https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Transformations#hull"><code>hull</code></a>. Essentially it takes two or more objects (can be coplanar 2D shapes or and 3D objects including very slightly extruded non-coplanar 2D shapes) and tightly wraps them in tarp. The result is a convex shape connecting the two.</p>
<p><img src="/media/zynq-enclosure/20210911_223824.jpg"></p>
<p>The lid was another thing where the first try didn't work out. These cheap fans have roughly the same shape but they aren't exactly identical so you can't perfectly trust any sample you find online. The trick is to just brute force it during assembly. You can read about that in a bit.</p>
<p>Now the SBC is only mildly warm instead of worryingly toasty. But performance has a cost. I had to replace the appealing &quot;glass&quot; top with a plastic one because I don't have the tools to carve a shape with a lip in acrylic.</p>
<h3 id="branding">Branding</h3>
<p>An interesting consideration, how do you put some drawing or logo on your part? Luckily openSCAD got our back here. You can take any grayscale PNG image and turn it into a surface height map style. Then slightly <code>extrude</code> it, <code>translate</code> and <code>scale</code> to where the target surface is and use <code>union</code> or <code>difference</code> to make it embossed or indented respectively. I did the latter, so I can put some colored filler in the recess.</p>
<pre><code>translate([50, 10, 50])
	scale([1,10,1])
		rotate([90,0,0])    
			linear_extrude( 3.6)
				projection(cut = true)
					translate ([0,0,70])
						surface(file = &quot;hastlayer-logo-tagline-120h.png&quot;, center = false, invert = true);
</code></pre>
<h3 id="open-source">Open Source</h3>
<p>Yeah, the whole code is <a href="https://github.com/DAud-IcI/3d-printing/blob/main/TE0706%20carrier%20board/chassis.scad">here</a>, if you want to check it out. Hope it helps.</p>
<h2 id="computer-aided-manufacture">Computer Aided Manufacture</h2>
<h3 id="printing">Printing</h3>
<p>Aside from the first prototype, each print was made with the highest resolution my Anycubic I3 Meta can handle.This was mainly for the benefit of the branding, but it also makes the holes more precise and the print less &quot;jagged&quot; with layer lines. It took me about 5-6 hours to print the bottom part and about 2-3 for the top part. It's not a quick process but after the first 10 minutes when you can see that the bottom layer properly sticks then you can leave the machine to its devices. It's good to have an enclosed printer to avoid warping from gusts of wind. I did mine using cardboard and styrofoam.</p>
<p>Once everything is printed out I carved off the supports and other imperfections with a hobby knife.</p>
<p><img src="/media/zynq-enclosure/20210909_153913.jpg"></p>
<p><img src="/media/zynq-enclosure/20210909_161824.jpg"></p>
<p>Then did a test fit. This is when you can see if the screw holes, port holes and the general layout is accomodating enough for the non-printed parts and each other. Not too tight though, once everything is good it's time to take it apart once again and see about making it look good.</p>
<h3 id="plans-and-reality">Plans and Reality</h3>
<p>So what do when parts don't match? If both are printed parts then it's on you, fix them and print again. But if one is a specific part you have to accomodate, then you have two choices. Either iterate your printed parts until they match well, or use the material to your advantage and force it!</p>
<p>Both happeened in this project. When I found that the all the port holes were just a little bit lower then expected, that warranted a model adjustment and reprint. When I had that misadventure with the SMD capacitor I melted and fixed that iteration with the soldering iron but adjusted the model for future prints. When I found the fan wasn't exactly like the reference I worked with and one of its crew holes didn't match my measurements I had to use brute force. I covered the unneeded hole up with epoxy putty first. Then put the machine crew into the fan's screw shaft and started blasting with the electronic screw driver. This caused friction at the contact point on the lid until the screw melted itself int the right place. Extremely hot!</p>
<h3 id="work-it">Work it!</h3>
<p>As much as we enslave the machine, the machine enslaves us. Sure, I got a nice and precise part printed, but now I have hours of manual labor ahead of me to make a part I never would've attempted if I didn't have a 3D printer. Pretty cool, yeah?</p>
<p>So 3D printing isn't perfect. We get layer lines due to backlash in the stepper motor as well as wobble during more complicated motions. Yet you want a smooth and fancy looking finish right? Me too. You may ask, is there some kind of material that smooths the PLA print? Yeah, it's called elbow grease! PLA is a relatively cheap material and easy to print with but sure enough there are no shortcuts for a good finish. So once these beautiful messes of prints were done I sat down with a good series to binge and some water and sandpaper on the table. The workflow is like this:</p>
<ol>
<li>Go to some well ventilated area.</li>
<li>Liberally spray the print with primer.</li>
<li>Once dried, bring it back in.</li>
<li>Lightly but quickly move it against the sandpaper until some but not all of the primer is removed.</li>
<li>Repeat.</li>
</ol>
<p>A few hours of Netflix &amp; Sanding got me a nice enough surface on the bottom part where a final layer of paint and then some varnish could be applied. I kinda ran out of elbow grease so the air duct between the fan and the lid gets to retain its layer lines. This was my first time doing this kind of surface work so I didn't dare to go for that complicated geometry.</p>
<p><img src="/media/zynq-enclosure/20210912_195346.jpg"></p>
<p>Another aesthetic touch, I used some epoxy putty I had around to fill the branding indentation. It can easily absorb some model paint without ruining its material properties so it can take on any color. I rubbed it into the indentation and then removed the excess with a wet retired toothbrush. I could've tried to make it flush by crapint the excess with a plastic card but I think this slightly concave surface is a good character accent.</p>
<p><img src="/media/zynq-enclosure/20210929_043133.jpg"></p>
<p>Finally I've stuck some furniture pads at the bottom so it won't be wobbly from standing on screws.</p>
<p><img src="/media/zynq-enclosure/20210912_213002.jpg"></p>
<p>It's done:</p>
<p><img class="vue-carousel" src="/media/zynq-enclosure/20210929_043112.jpg"><img class="vue-carousel" src="/media/zynq-enclosure/20210929_043133.jpg"><img class="vue-carousel" src="/media/zynq-enclosure/20210929_043434.jpg"></p>
]]></description>
      <pubDate>Mon, 04 Dec 2023 14:32:07 GMT</pubDate>
      <guid isPermaLink="true">https://derelict.dotnest.net/zynq-enclosure</guid>
    </item>
    <item>
      <title>Questionable Fun with Source Generators</title>
      <link>https://derelict.dotnest.net/derelict-core/questionable-fun-with-source-generators</link>
      <description><![CDATA[<p><em>psst, if you don't care about the moaning skip to The Project</em></p>
<p>I had some run-ins with the old T4 template system back in Framework. That was kind of <a href="https://www.youtube.com/watch?v=939mLRbc2T0">a mixed bag</a>. On one hand you had complete control over the output so it can be whatever type you want. On the other hand, there was no built-in syntax highlight or code completion for either the target or the meta code, yoinking you all the way back as if you were coding on Notepad. Also even though you could work around it, the template was really made for creating 1 file from 1 template so if you needed multifile it resulted in some leftover garbage (eg. empty files in your Entity Framework tables directory).</p>
<h2 id="source-generators-to-the-rescue">Source Generators to the Rescue?</h2>
<p>Recently I've learned that the analysis features in Roslyn let you create more than just analysers: <em>Source Generators</em>. From a developer experience perspective, this is pretty much a stright improvement over T4. It aleviates some of the pain points (though sadly keeps many), while only adding one limitation. That is, you can only generate source code for an IL langauage that can be compiled by Roslyn, like C#. This is perfectly fine, I don't think I ever needed T4 for anything else.</p>
<p>Now onto the good news:</p>
<ul>
<li>The code generation logic is regular code with the usual syntax highlight and code completion.</li>
<li>One generator can create many files across the whole project that consumes it.</li>
<li>It works off the whole source tree, so all compiled files in your project, not just one designated template file.</li>
<li>Your input code is already parsed.</li>
<li>You can still use non-compilable additional files as reference if you want to, again as many as you want.</li>
</ul>
<p>But to temper expectations:</p>
<ul>
<li>The template code is still not highlighted or parsed.
<ul>
<li>Worse yet, you probably have to work with StringBuilder.</li>
</ul>
</li>
<li>The additional files have to be marked in your project file which is not intuitive and barely documented. (though easy once you know what to do)</li>
<li>You can't edit or override code, not even your own. This is a strictly additive process. (partial classes help here)</li>
<li>The generator has to go into its own project.</li>
</ul>
<h2 id="lots-of-jank">Lots of Jank</h2>
<p>This is one of those barely advertised features. Even though .Net 5 has been out for a while, the most official looking this I could find was a <a href="https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/">Microsoft Blog from the .Net 5 Preview days</a>.</p>
<p>The weirdest idiosincracy is that even though it takes .Net 5, a source generator must be .Net Standard 2.0. That's right, not even the latest 2.1.</p>
<p>There is also a return to everyone's dear old friend, the ḏ̙̳̙̹e͚̘̼͉̯͈ͅṕ̲͎̼͙͉̦ͅe̫̠̺̯̜̦̝n͍͚͕dè̹ͅnc͓̥̟̪̼͈̘͞y̠ ̺͉̕h̫e͎̖l͍͚̗l҉͔̫̝̜̲̩̳. Right, didn't we all just miss it so much? So listen up, this is important:
<strong>If you are on .Net 5 import these exact versions!</strong></p>
<pre><code class="language-xml">    &lt;ItemGroup&gt;
        &lt;PackageReference Include=&quot;Microsoft.CodeAnalysis.CSharp&quot; Version=&quot;3.8.0-4.final&quot; PrivateAssets=&quot;all&quot; /&gt;
        &lt;PackageReference Include=&quot;Microsoft.CodeAnalysis.Analyzers&quot; Version=&quot;3.0.0&quot; PrivateAssets=&quot;all&quot; /&gt;
    &lt;/ItemGroup&gt;
</code></pre>
<p>Updating them to the latest <em>non-prerelease</em> version will break the build becuase it expects dependencies from .Net 6 preview.</p>
<p>If you don't add the magic <code>&lt;MSBuildWarningsAsErrors&gt;CS8785&lt;/MSBuildWarningsAsErrors&gt;</code> to your csproj's PropertyGroup, any code gen error will be rendered as a warning instead of an error. Though your build will still fail because at this point no code is generated.</p>
<p>I personally wasn't able make it work with NuGet dependenceis (eg. Json.Net) inside the source generator project. It failed the build with missing file even if I added the same dependency to the consumer project. This happens from both .Net CLI and IDEs that rely on it (eg Rider, VS Code) - if it works in VS only I don't want to hear about it. In the end I didn't need it for what I was working on, but it's still disheartening. Supposedly there is a way to make it work, but it seemed too much work for what I was doing with this side project.</p>
<p>There is a debug mode if you add <code>Debugger.Launch()</code> into your <code>ISourceGenerator.Execute</code> method, but you've got to set it up.</p>
<h2 id="the-project">The Project</h2>
<p>I wanted to make something that might be actually useful, not just a playground project. (Also apparently the INotifyPropertyChanged has been done to death and it's pretty much the Hello World of this topic, even though I haven't seen it used in the wild yet.) So I went back to the question that put me on the path to this code gen rabbit hole: can we have cleaner constant string concatenation? So I made <a href="https://github.com/DAud-IcI/constant-generator">ConstantsGenerator</a>, a source generator that lets your express path collections in structured form using XML and then recursively builds nested static classes with path constants inside them. This makes things much cleaner when you have those multi-level paths in ASP.Net Core. Yeah I know it sounds like a nonissue, but it's a big cleanup with larger web projects, okay?</p>
<h3 id="usage">Usage</h3>
<p>Include this project using ProjectReference with the attributes you see below. Also Include your files into the AdditionalFiles item group:</p>
<pre><code class="language-xml">    &lt;ItemGroup&gt;
        &lt;ProjectReference Include=&quot;..\ConstantGenerator\ConstantGenerator.csproj&quot; OutputItemType=&quot;Analyzer&quot; ReferenceOutputAssembly=&quot;false&quot; /&gt;
        &lt;AdditionalFiles Include=&quot;*.constants.xml&quot; /&gt;
    &lt;/ItemGroup&gt;
</code></pre>
<h3 id="example">Example</h3>
<p>Say you want custom routes for your ASP.Net application, but also put it in a constants file so you can refer to them in your logic.</p>
<p><em>sample.routes.constants.xml</em></p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
&lt;Management xmlns:g=&quot;https://github.com/DAud-IcI/constant-generator/&quot;&gt;
    &lt;!-- Without this, your class will be in the Constants namespace. That can work if the file names are unique. --&gt;
    &lt;g:Namespace Value=&quot;ConstantGenerator.Sample.Constants.Routes&quot; /&gt;
    
    &lt;!-- This is the default already so you can safely omit it. --&gt;
    &lt;g:Separator Value=&quot;/&quot; /&gt;

    &lt;Users /&gt;
    &lt;Companies /&gt;
    &lt;New&gt;
        &lt;User /&gt;
        &lt;Company /&gt;
    &lt;/New&gt;
    &lt;Unassign&gt;
        &lt;User /&gt;
    &lt;/Unassign&gt;
&lt;/Management&gt;
</code></pre>
<p>The elements with the <code>g</code> namespace are optional configuration items, they are optional, must be direct children of the root and each must have a <code>Value</code> attribute.</p>
<ul>
<li>Namespace: You can specifiy the generated class's namespace. If you omit it, it will use <code>Constants</code>.</li>
<li>Separator: The values in the constants are the full path from the root, with this string used as the separator. The default value is <code>/</code>.</li>
</ul>
<p>It generates this class:</p>
<p><em>SampleRoutes.GeneratedConstant.cs</em></p>
<pre><code class="language-c-sharp">namespace ConstantGenerator.Sample.Constants.Routes
{
    public static class Management
    {
        public const string ThisRoute = &quot;Management&quot;;

        public const string Users = &quot;Management/Users&quot;;
        public const string Companies = &quot;Management/Companies&quot;;

        public static class New
        {
            public const string ThisRoute = &quot;Management/New&quot;;

            public const string User = &quot;Management/New/User&quot;;
            public const string Company = &quot;Management/New/Company&quot;;
        }

        public static class Unassign
        {
            public const string ThisRoute = &quot;Management/Unassign&quot;;

            public const string User = &quot;Management/Unassign/User&quot;;
        }
    }
}
</code></pre>
<p>One advantage of this generated code is cleanliness. If you did this manually you probably need auxiliary variables like this:</p>
<pre><code class="language-c-sharp">        public static class New
        {
            private const string NewPathBase = nameof(Management) + &quot;/&quot; + nameof(New);
            public const string User = NewPathBase + &quot;/&quot; + nameof(User);
            public const string Company = NewPathBase + &quot;/&quot; + nameof(Company);
        }
</code></pre>
<p>Don't get me wrong, <code>nameof</code> is great but just look at all this visual noise! We don't need that with the source generator beucase the name is generated from the same text as the value.</p>
<h2 id="whew-what-a-disastreously-boring-topic">Whew, what a disastreously boring topic</h2>
<p>Yeah, but disastreous is going to be on-brand here even when things actually work out. Enjoy your stay, there aren't any escape pods left on this derelict.</p>
]]></description>
      <pubDate>Mon, 04 Dec 2023 14:31:57 GMT</pubDate>
      <guid isPermaLink="true">https://derelict.dotnest.net/derelict-core/questionable-fun-with-source-generators</guid>
    </item>
  </channel>
</rss>