Jekyll2022-07-27T15:45:42+00:00https://randompoison.github.io/feed.xmlrandomPoisonDavid's personal siteContinuous Delivery for the Discerning Game Developer2021-01-06T00:00:00+00:002021-01-06T00:00:00+00:00https://randompoison.github.io/posts/continuous-delivery-for-games<p>Continuous Delivery is a set of software development practices for automating the process of taking a change submitted to source control and making it ready for production. This means fully automating the process of building the product, testing it, and preparing the build for release. At a high level I’d summarize the goal of CD as “Any commit to trunk can go into production”. Effectively what we want to end up with is this:</p>
<object data="../../assets/magic.svg" type="image/svg+xml"></object>
<p>If you’re not used to working on a project that has a CD pipeline setup this can sound somewhat absurd, especially if you’re used to the process of preparing a build for production taking days or weeks to complete. There’s also a common perception in software development that software quality and stability come at a direct cost to development velocity, so the idea that investing heavily in things like test automation, which are generally perceived as efforts to increase quality and stability, can actually speed up development is unintuitive to many people. However, there’s a large body of empirical evidence demonstrating that practices like CD improve both software stability <em>and</em> development velocity, as well as having beneficial knock-on effects like reducing employee burnout.</p>
<p>I’m not going to be going further into the business case for CD here, but for anyone who’s interested in seeing more of the details then I highly recommend checking out the Accelerate State of DevOps reports. The State of DevOps Report is an ongoing research study into how different software development practices (with an emphasis on DevOps) impact software delivery and operational performance. <a href="https://services.google.com/fh/files/misc/state-of-devops-2019.pdf">The latest one is the 2019 report</a>, and demonstrates a clear, causal link between practices like CD and SDO performance.</p>
<p>Broadly speaking, a CD pipeline will have the following steps:</p>
<object data="../../assets/overview.svg" type="image/svg+xml"></object>
<p>I’m going to go through each of these stages and talk about what they require to be fully implemented, and what development practices can be used achieve success in those areas. I’m also going to touch on some additional topics that aren’t <em>strictly</em> a part of CD, but are part of the broader software development process and can be part of a positive feedback loop when combined with a robust CD pipeline.</p>
<h1 id="integrating-changes">Integrating Changes</h1>
<p>Everything starts with a commit: Some change to the underlying source code or data that drives the product. In the CD pipeline, the first step after making the commit is to integrate that change into the mainline codebase for the project. This is a practice called “trunk based development”, where all work is done directly to your project’s trunk branch (or on a short-lived feature branch that’s merged into trunk regularly). This keeps the effort of merging changes into trunk minimal, and avoids cases where large merge conflicts cause delays.</p>
<p>For the most part this practice is standard these days; Even projects that don’t intentionally follow “trunk based development” as a practice use a workflow that is fairly similar. There’s also generally no automation that needs to be setup in order to enable it, since modern version control tools handle the workflow for you. But there’s a key piece of nuance here that I do want to highlight: Even large changes need to be implemented in a trunk-based manner, and (this is the important part) incomplete work should be regularly merged back into your trunk.</p>
<p>When working on relatively small or scope-constrained pieces of work there’s generally no issue following this practice. But things can be more complicated when working on something that’s larger in scope, such as adding a major feature or doing some kind of large-scale refactoring. Even for teams that generally follow trunk-based development, it can be tempting to do large chunks of work on a branch before merging the work into trunk. In order for a CD pipeline to work effectively, it’s important to avoid this and instead follow practices that allow even major changes to be broken down and merged into the mainline piecemeal.</p>
<p>In short: Merging unfinished work is good, actually! We as developers often have an aversion to merging in partially-completed work, especially if what we’re merging in is non-functional. However, the only real issue with merging incomplete work is if:</p>
<ul>
<li>It disrupts development in some way, e.g. by causing build or test failures, or by exposing unfinished work to internal testers.</li>
<li>It disrupts the end user in some way, by being surfaced to users before the work was finished.</li>
</ul>
<p>But with the right development practices, even very large pieces of work can be broken into small pieces and implemented in an iterative way without disrupting development or causing problems for users. Following this approach ensures that build and testing functionality provided by the CD pipeline can provide feedback early and often, and means we can avoid making large, disruptive changes.</p>
<p>There are a lot of approaches that can be taken when it comes to breaking up large changes, and the right one depends heavily on context, but I’ll go over a few now as examples:</p>
<ul>
<li><strong>Avoid exposing new functionality at all</strong> until it’s ready to be integrated in the game. For example, if a new feature is intended to be accessible through a button in a menu, don’t add that button until the feature has been fully implemented. Instead you can add a debug-only way of accessing the feature (e.g. an option in a debug menu or a debug-only keyboard shortcut). This allows the functionality to still be merged into the codebase before it’s done without risking exposing unfinished functionality to players.</li>
<li><strong>Hide changes behind feature flags</strong>. If you can’t fully hide the new functionality, such as in cases where you’re making tweaks to an existing feature, build the system to make the new functionality toggle-able and use feature flags or configuration options to determine when to enable the feature.</li>
<li><strong>Branch by Abstraction</strong>. For large refactoring work you’ll often be completely replacing an existing system with a completely new implementation. In this case, you need a way to continue to use the old implementation while the new one is in development. To do this, build out a layer of abstraction between the functionality that’s being refactored and the code that uses it that will allow you to swap out the underlying implementation. This will allow you to still test out the new implementation while leaving the old implementation in production until you’re ready to remove it. Once the new implementation is done, the old version and the abstraction layer can be removed.</li>
</ul>
<h1 id="build-and-deployment">Build and Deployment</h1>
<p>The next step depends on what type of application you’re working with and what technologies its built with, but will generally some be kind of build or deployment step, possibly a combination of the two. For many projects, some build step is necessary in order to take the raw source code for the project and convert it into a format that can be run (though this may not be necessary if your application is written in an interpreted language like Python or JavaScript). Once the application has been built, there’s usually some kind of deployment step that’s needed in order to make the new build accessible. For server applications this will mean deploying the new build into a development environment. For client applications (i.e. ones that are run by the end user directly) there is likely some steps needed to distribute the build to the people who need it, whether that’s making it available internally to the development team or uploading it to your distribution platform of choice.</p>
<p>As with the integration step it’s pretty common these days for this step to already be automated, at least at a basic level. So what I want to focus on here are the nuances that are important for ensuring that your CD pipeline is working effectively:</p>
<ul>
<li><strong>The entire process needs to be automated</strong> (short of actually releasing the build)!</li>
<li><strong>Run the entire process on every commit!</strong></li>
</ul>
<p>One of the driving philosophies of CD is “if something is painful, do it more often”. While it’s common to have a basic build process run on every commit, it’s also common to only run the full release build pipeline when preparing to actually do a release. As a result, issues in the release build process don’t get caught until the worst possible time: When you’re trying to get a release out. Similarly, it’s common to leave steps like uploading builds to release platforms as manual steps since they are performed infrequently, and the effort of automating them is seen as being more costly than continuing to do it by hand.</p>
<p>But if you fully automate the process and run the full process on every commit, all of the benefits of CD get applied to your release build process as well as your daily dev build process. If an issue comes up with your release builds, you find out as soon as the problem is introduced and can fix it well before it has the potential to cause a delay. This approach also allows you to do away with things like manual release branches, since every commit to trunk will produce a viable release candidate.</p>
<p>I expect that the biggest objection to this approach is that for many products the release pipeline is far too slow to run on every commit: Upwards of an hour, possibly taking several hours for large projects. This is a valid issue, but not an insurmountable one. In the most extreme cases running the entire release build pipeline can take so long that it would take the entire day, which would nullify the benefit of running on every commit. In these cases I recommend running release builds nightly, since that’s still much better than waiting until you’re actually doing a release to run the release build. For less extreme cases, say build times of an hour or so, an alternate approach is to run your builds in multiple stages. I talk about this approach more when talking about test automation.</p>
<h1 id="testing">Testing</h1>
<p>Testing is probably the most critical part of the entire CD pipeline. Having a robust, automated test suite is the key that allows you to be confident that the product works as intended after any given change, allowing you to work quickly with confidence. However, it’s also often the hardest piece to implement effectively. Building out an effective test suite takes a substantial amount of engineering and QA effort over a long period of time, and when starting from scratch it can be hard to know where to start or to see the value that will come from that effort. I’m going to try to dig into some key pieces of the “how” and “why” of automated testing in order to make the prospect less intimidating.</p>
<h2 id="continuous-testing">Continuous Testing</h2>
<p>The first piece that I want to emphasize is Continuous Testing. Traditional, manual testing approaches involve having a human perform tests periodically at various points in time: Engineers will perform ad hoc tests as they implement functionality, and QA testers will perform both ad hoc tests and more rigorous tests based on pre-made test plans. However, there’s a fundamental limit on how frequently and how thoroughly manual tests can be performed. As the scope of a product grows and more functionality is added, the amount of work needed to fully test every piece of functionality grows exponentially. For even relatively small applications it’s simply impossible for the full test suite to be run manually with any degree of frequency. As a result, manual tests tend to be limited to regular smoke tests, with more thorough regression tests being performed only when necessary.</p>
<p>Automated testing makes truly continuous testing possible, since it’s often possible to run the entire test suite after every change. This makes for a huge improvement over manual testing for a number of reasons:</p>
<ul>
<li><strong>Quicker feedback</strong>, since tests are run immediately after a commit without needing to wait for a QA tester to be available. It’s often easy to identify exactly which change introduced a test failure with automated testing.</li>
<li><strong>Finer-grain testing can be run</strong>. Manual testing can only really test the game from the perspective of a player, which means they can only catch when things break in fairly obvious ways. Automated tests can target individual pieces of code directly in a way that simply can’t be done by manual testing.</li>
<li><strong>More consistent results</strong>. Manual testing is always subject to human error, which means test results can be inconsistent.</li>
<li><strong>Easier to check edge cases</strong>. One of the big advantages of automated tests is that they can cover the less common cases that are often missed by the more general smoke testing that manual testers do regularly. Uncommon cases are the most likely to break as a result of day-to-day changes, since they’re often not covered by the ad hoc testing done by engineers and designers. Automated tests can consistently verify such cases after every change.</li>
<li><strong>Improved working conditions for manual QA testers!</strong> I’ll go into this in more detail in a little bit, but one of the biggest advantages of automated testing is how it frees up manual testers. Rather than constantly having to smoke test the game in order to catch regressions, or constantly having to deal with instability and breakage, testers can focus on things like exploratory testing and user experience testing, things that only a human tester can do effectively.</li>
</ul>
<p>In order to setup continuous testing, there’s only really two conditions that need to be met:</p>
<ul>
<li>You must run your suite of automated tests as part of your CD pipeline after every commit.</li>
<li>You must fail the pipeline and reject the build if any tests fail.</li>
</ul>
<p>Even with a fairly small test suite, there’s immense value in running those tests in this way. Whatever pieces are covered by automated tests, no matter how small, will be tested thoroughly and consistently after every change.</p>
<h2 id="test-driven-development">Test-Driven Development</h2>
<p>Of course, the larger and more thorough your test suite the more reliably it will catch bugs as they’re introduced. However, getting to that point can be difficult, especially if you’re looking to add test coverage to an existing project. To help build up test coverage, I recommend following an approach called “Test-Driven Development”. The basic idea is that for any given change you want to make to the project, you write a test for the new expected behavior <em>before</em> actually making the change. The test will fail when you first write it, but will pass once you’ve correctly implemented the change in question. There are a number of benefits to this approach:</p>
<ul>
<li>It gradually builds up test coverage. The effort of building up a test suite is spread over the entire development process, rather than trying to sit down and build an entire test suite all at once.</li>
<li>It tests the tests. When writing a test to cover an existing piece of functionality, it can be hard to tell if the test is actually testing the right thing, and if the test doesn’t fail when the underlying functionality is broken the test isn’t providing any value. With test driven development, you write tests at a point where you know the underlying functionality isn’t working, so if the test doesn’t fail at that point then you know there’s something wrong with the test.</li>
<li>It encourages developers to account for edge cases from the beginning. The act of writing tests encourages you to think about what edge cases need to be covered and how the code should handle those cases. Putting in the time to write tests for those edge cases first means that it’s easier for developers to ensure that they’ve fully handled all those cases when they move on to implementation.</li>
</ul>
<p>I especially like this approach for dealing with bug fixes. When working on a new feature, writing tests ahead of time can be difficult. For a large feature, the sheer number of potential tests to write can be overwhelming and it can be hard to figure out what tests would be the most valuable to write at the start. Plus, if the feature is still being prototyped you might not even know for sure what the expected functionality is, and trying to write tests at that phase of development can be both frustrating and disruptive to the prototyping process. But with bug fixes, you can be 100% confident that every test you write adds immediate value since it’s always covering a bug that we know has come up in practice. It also adds a lot of confidence to the fix, since we have a test that should pass to confirm that the fix worked.</p>
<h2 id="architecting-code-for-testability">Architecting Code for Testability</h2>
<p>One critical thing to keep in mind is that code needs to be written in such a way that it is amenable to testing. In order to be testable, code needs to have the following properties:</p>
<ul>
<li><strong>Deterministic</strong> - The functionality needs to behave the same way given the same inputs and context every time.</li>
<li><strong>Controllable</strong> - Any inputs taken by the code must be fully controllable by the test environment, such that the exact same conditions can be used to run the test every time. If the code depends on external systems in an uncontrollable way, then it introduces ways for the tests to fail inconsistently.</li>
<li><strong>Independent</strong> - The code needs to be able to be run independently from other systems that it would otherwise interact with when running normally. Tests <em>can</em> be written to cover interactions between multiple systems (called “integration tests”), but even then you need to be able to limit the test to only the subsystems in question without needing to pull in other, unrelated systems.</li>
</ul>
<p>These properties aren’t hard to achieve, but it’s also also easy to write code that doesn’t adhere to them if you’re not actively focused on making your code testable.</p>
<p>It’s also worth noting that some kinds of functionality will be easier to setup in this way than others. When it comes to games, code related to the game’s visuals and world state tend to be relatively hard to test, since the game world is an inherent piece of shared state, and is often both an input and an output for a given piece of code. Where possible, it’s helpful to separate “business logic” from “view logic”, such that you can test the underlying functionality separately from the logic for controlling the game’s visuals. Even for more complex game logic, the underlying functionality will have all of the above properties once it’s separated and can be tested on its own.</p>
<h2 id="testing-art-and-data">Testing Art and Data</h2>
<p>At Synapse we build games that are highly data-driven, and changes to the game’s data can be a source of bugs as much as changes to the game’s code. As such, testing the game’s data and ensuring that everything is configured correctly is key to having thorough test coverage. Fortunately, testing data is relatively easy! Data can be loaded in isolation of most of the game systems, and it’s generally relatively simple to write tests for specific properties that the data needs to have. In the best case, you can reuse the game’s code to write tests that directly verify that the input data works as expected when used by the game. But even if your setup doesn’t allow for that, writing separate tests for the data is still fairly easy to do.</p>
<p>Games also have a much heavier emphasis on art assets than many other applications. Fortunately, art assets can be treated fairly similarly to data in terms of automated testing: While we can’t do much to test that the assets look right, there’s often specific requirements for how art assets are configured and added to the project, and <em>those</em> parts can be tested automatically. With engineers, artists, and designers all potentially making tweaks to the project at the same time, it can be especially beneficial to have tests covering art assets and game data, since more people touching a project introduces more places for bugs to be introduced.</p>
<h2 id="verify-changes-before-merging">Verify Changes Before Merging</h2>
<p>One key point I haven’t talked about yet is <em>when</em> to run your automated test suite.</p>
<p>The ideal setup is to perform all tests before changes are merged into your project’s trunk. As I mentioned at the beginning, the goal of CD is that any commit to trunk can go into production. That invariant can’t be maintained if tests are only run after committing to trunk. Instead, the better approach is to make changes to a branch first. Tests are performed on the branch and the change is only merged once tests have passed. For programmers this often involves making a “pull request” or “merge request”, and is tied directly into the code review process such that changes require both manual approval and automated verification before being merged.</p>
<p>Things get trickier once you add artists and designers into the mix, since the standard pull request process used by engineers is cumbersome for changes that don’t need to go through the full code review process. An alternate approach that can be used here is to have artists and designers commit to a separate branch off of trunk. The test suite can be run after each commit, and changes can be merged to trunk automatically if the tests pass.</p>
<p>However, it’s not always going to be possible to run all tests when merging to trunk. Some tests may simply take too long to run to do so after every commit. Similarly, if your project takes a long time to build (as is often the case for games), any tests that need to be run after the build finishes will be slow to run as well. When faced with these cases, the first thing you should always do it try to speed up the tests. If the tests can be simplified in some way that allows them to still catch failures while running quickly, then doing that is your best bet. The more frequently tests are run, the more value they have, so maximizing the set of tests that can be run after every commit is your best bet for having an effective test suite.</p>
<p>But even still, there will always be some tests that simply take to long (or are too flaky) to run after every commit without being disruptive. For these there are two main options:</p>
<ul>
<li><strong>Run tests in multiple stages</strong>. After a commit, first run the quick tests and allow the change to be accepted if those pass. Once the initial tests pass, kick off a second stage for the slower tests. The second stage won’t necessarily run for every commit, rather it runs as fast as it can, starting again with the latest commit after the previous batch finishes. This may mean that multiple commits are bundled into a single test run, but this will still ensure that the tests are being run as quickly as they can be. This works well for tests that take several minutes to run, but are otherwise still reliable.</li>
<li><strong>Run the tests nightly</strong>, or otherwise on an automatic schedule. This approach should generally be your last resort, since tests that are run on a schedule, rather than in response to a change to the project, need to be checked manually and can’t be used to automatically gate changes. However, running tests nightly can be useful in some cases:
<ul>
<li>Tests that take a <em>really</em> long time, on the order of hours.</li>
<li>Tests that can be flaky and may fail even when nothing is wrong. You should be cautious about including tests like this at all, since test failures can be easy to dismiss as random failures even if they’re catching actual bugs. But if you have such tests cases that are genuinely useful, then running them nightly is probably the best approach to avoid random test failures from causing disruption to normal development.</li>
<li>Exploratory tests that are looking for new bugs. Some testing approaches, generally called “fuzz testing” or “gremlin testing”, attempt to interact with the product in semi-random ways in order to discover crashes and other bugs. These tests generally need a long time to run (hours or days) and can fail unpredictably, so it’s only practical to run them overnight and review any issues that they uncovered later.</li>
</ul>
</li>
</ul>
<p>This brings us to the question of how we respond to failures in the CD pipeline. If we’re setup to follow the ideal case of “tests are run before merging to trunk”, then generally the way to handle failure is pretty obvious: Whoever was making the change sees that their change was rejected, they fix whatever caused the tests to fail, then once tests pass again they’re good to merge. For these cases the impact of the test failure is minimal since it hasn’t been merged into trunk and so won’t disrupt development, and it’s clear who exactly needs to address the breakage.</p>
<p>However, for tests that are run as a second stage or after merging, we have the possibility of failure for changes that have already been merged to trunk. In these cases, it’s important make fixing trunk the top priority for the team. This doesn’t mean that every person on the team has to stop what they’re doing until the issue is fixed, but <em>someone</em> needs to immediately focus on fixing it, and anyone else who’s help is needed should prioritize helping. This is part of the reason why running tests before merging is so important: Failures that are caught before merging are far less disruptive than those caught after merging, so catching as many issues as possible as early as possible is key to keeping the development process smooth.</p>
<h2 id="a-note-on-manual-qa">A Note on Manual QA</h2>
<p>Earlier I talked about the advantages of automated testing and the advantages it has over manual testing for certain kinds of testing. But I really want to be clear that automated testing is NOT a substitute for manual testing. Rather, automated testing allows your manual testers to work far more effectively than they could otherwise.</p>
<p>On a project without automated testing, manual QA efforts are a constant uphill battle against breakage and regressions, and most of our testers’ time is spent just making sure the game still works. This is a problem for a few reasons:</p>
<ul>
<li>It’s a huge time sink, since smoke testing and regression testing needs to be done nearly constantly as changes are made to the project.</li>
<li>Delays and disruptions are common, since bugs are constantly being introduced. This means that QA testers rarely have time to focus on other work like writing test plans.</li>
<li>Finalizing a release is difficult and stressful since bugs are often found last minute and there’s a lot of pressure on the QA team to approve a build on time, something which is often entirely out of their control.</li>
</ul>
<p>Automated testing resolves these issues by handling the most rote, tedious forms of testing and establishing a baseline of stability. This removes a lot of the stress that comes from working on an unstable project, and frees testers up for the kinds of work that only manual testers can do:</p>
<ul>
<li><strong>User experience testing</strong>. QA testers can give feedback not just on whether or not something works, but how it’s experienced from a player’s perspective. This means they can identify if things are confusing from a player’s perspective, or things that otherwise don’t line up with how we want player’s to experience the game.</li>
<li><strong>Exploratory testing</strong>. Automated testing can ensure that old bugs never come back, but it’s not really able to find new bugs. Manual testers know how to poke and prod at features in order to find ways to break them, and any bugs they uncover can get added to the automated regression testing suite in order to ensure that those bugs never come back.</li>
<li><strong>Designing test cases</strong>. Manual QA can be included early in the design and implementation phases in order to ensure that edge cases and potential bugs are caught before they ever make it into the game. These efforts then translate directly into building out automated testing for any edge cases that QA identifies.</li>
</ul>
<p>The end result of all this is a more stable game, a better final product, and a much, much happier QA team.</p>
<h1 id="conclusion">Conclusion</h1>
<p>There’s far more information about Continuous Delivery than I can cover in this article, but I hope I’ve provided a reasonable high-level introduction to what a CD pipeline looks like and what work goes into building one. Continuous Delivery as a practice can provide an immense amount of value for a software development team, but it’s sadly under-utilized within the game development world due to some of the unique challenges that game development projects need to contend with. I hope that more game devs begin to utilize this practice to ship higher quality games more quickly than they could otherwise.</p>Continuous Delivery is a set of software development practices for automating the process of taking a change submitted to source control and making it ready for production. This means fully automating the process of building the product, testing it, and preparing the build for release. At a high level I’d summarize the goal of CD as “Any commit to trunk can go into production”. Effectively what we want to end up with is this:The State of the Unity Package Ecosystem2020-07-14T00:00:00+00:002020-07-14T00:00:00+00:00https://randompoison.github.io/posts/the-state-of-unity-packages<p>I’m a Unity developer: Professionally I make mobile games with the Unity game engine. I’ve been working with Unity since 2014, and have seen the engine and the ecosystem around it change a lot over the years. I’m also an avid open source developer, and a big believer in the value of having a community-driven ecosystem around any core technology. Many times in my years working with Unity I’ve tried unsuccessfully to setup reusable, open source libraries for Unity in the way that I would for other ecosystems like JavaScript (via NPM) or Rust, failing inevitably due to some limitation in the tooling available for Unity. In the last year or so things have been changing a lot within the Unity ecosystem, and I’m finding that I’m finally able to do all of the things that I’m used to being able to do when setting up open source libraries.</p>
<p>What follows is my attempt to recount the history of package management in Unity, along with the ways that Unity and its ecosystem have been changing recently to make authoring packages easier.</p>
<h1 id="the-bad-old-days">The Bad Old Days</h1>
<p>I started using Unity right at the tail end of the Unity 4 cycle, just before Unity 5 came out. At that time, Unity’s only tool for sharing assets between Unity projects was <a href="https://docs.unity3d.com/Manual/AssetPackages.html">asset packages</a>. Asset packages are glorified zip archives containing a set of game assets (including code files!) in a pre-defined directory tree. When you import an asset package Unity merges the package’s directory tree into your project’s root <code class="language-plaintext highlighter-rouge">Assets</code> folder, giving you an option to preview which files were going to be imported ahead of time.</p>
<p>This system works well enough as a way to do one-off asset imports, but as you might imagine it’s not a terribly good system for managing more complex dependencies, especially code dependencies. If a new version of the package is released and files or folders were moved in the new version the import process doesn’t handle removing the old versions of the assets, leaving you with duplicates. Ideally a package will keep all of its assets under a single top-level directory so that you only need to delete a single folder before importing the new version. In practice many packages fail to follow this convention. Some packages even store project-specific configuration files within the package’s folder, so if you try to delete the root package folder before upgrading you’ll loose configuration settings that you probably meant to keep. There’s also nothing preventing you from making modifications to code/assets pulled in from these packages, and in practice such modifications are common in Unity projects. This makes upgrading packages doubly difficult because you now have to manage merging your changes with incoming changes.</p>
<p>There were also limited means of distributing such packages. The main place for hosting them was the <a href="https://assetstore.unity.com/">Unity Asset Store</a>, which mainly existed for selling pre-made assets. You could distribute packages there for free, but the interface for accessing and downloading packages as a user was pretty rough. It was also not uncommon to find open source projects on GitHub that provided pre-built asset packages for import. However it was also just as common to find projects on GitHub that <em>didn’t</em> provide a pre-built package, where the recommended way of grabbing the code was to manually copy the contents into your project. For a long time the <a href="http://wiki.unity3d.com/index.php/Main_Page">Unify Community Wiki</a> was also a popular option for smaller code snippets, though you had to manually copy-paste the code into your project which… blech. I get the impression that it sees a lot less usage these days than it once did, though.</p>
<p>So while there was undoubtedly useful utilities out there, and loads of developers trying to do their best with the tools available, I wouldn’t say that Unity really had an <em>ecosystem</em> per se. The tooling available simply made it impossible to share common code dependencies in a way that would allow for large, reusable libraries to be built. Instead, everyone working on a Unity project had their own local copy of the same handful of common dependencies (usually with a couple of bespoke modifications).</p>
<p>Take, for example, JSON parsing. For a long time Unity didn’t have built-in support for JSON serialization, and even now the support it has is very limited and not usable for many games. So instead most projects using JSON in some form end up having to pull in a separate JSON serialization library. The most common solution for a long time was SimpleJSON, which was posted on the <a href="http://wiki.unity3d.com/index.php/SimpleJSON">Unify wiki</a>. Nowadays I see <a href="https://forum.unity.com/threads/minijson-script-for-parsing-json-data.35484/">miniJSON</a> used a lot (Unity even uses within their <a href="https://unity.com/unity-distribution-portal">UDP package</a>). The far more robust <a href="https://www.newtonsoft.com/json">Json.NET</a> was also <a href="https://assetstore.unity.com/packages/tools/input-management/json-net-for-unity-11347">made into a Unity package</a> for the low, low price of $20 (the actual distribution of Json.NET has always been free). Of course, if you’re making a Unity package that itself needs JSON parsing support, you can’t assume that everyone using your package will already have a JSON library in their project (or which one they’ll be using even if they do already have one), so you need to provide your own copy of the JSON parsing library you’re using. I’ve worked on a project that had no fewer than 6 JSON parsing implementations in various places, several of them copies of SimpleJSON! The project I work on currently has 3 copies of miniJSON (pulled in via external packages) in addition to the 2 different JSON libraries we’re using for the game itself!</p>
<h1 id="a-new-hope">A New Hope</h1>
<p>In the 2017.2 release Unity started <a href="https://blogs.unity3d.com/2017/10/12/unity-2017-2-is-now-available/#wakkawakka">adding a new package manager</a>, which became available to users <a href="https://blogs.unity3d.com/2018/05/04/project-management-is-evolving-unity-package-manager-overview/">in the 2018.1 release</a>. In its initial form there wasn’t official support for making custom packages (it was only being used to distribute Unity’s own packages). However at least one clever person was able to <a href="https://gist.github.com/LotteMakesStuff/6e02e0ea303030517a071a1c81eb016e">reverse engineer the package format</a>, making it possible to start experimenting with the package manager early.</p>
<p>With the <a href="https://unity3d.com/unity/whats-new/unity-2018.3.0">2018.3 release</a>, Unity added official support for custom packages, as well as experimental support for distributing packages via Git and custom NPM servers. At this point the functionality was still largely undocumented, but was working well enough to start using in actual projects. Synapse has at least one project on Unity 2018.4 that relies on this functionality and has found that it works well in practice.</p>
<p>Starting in 2019.1 Unity provided <a href="https://docs.unity3d.com/2019.1/Documentation/Manual/CustomPackages.html">official documentation for setting up custom packages</a>, and they’ve continued to flesh out the docs and improve on UPM’s functionality throughout the 2019 release cycle. The big thing that this has enabled is the ability to start breaking out reusable bits of functionality into local packages. For studios like Synapse that have made many games over the years, it’s useful to have common utilities that are reused between games. Historically we’ve been able to share these utilities between games using version control mechanism like SVN externals or Git submodules.</p>
<p>At this point, UPM provides enough functionality that building out an ecosystem of Unity packages is actually a viable prospect. Well, at least in theory. In practice there’s still one major hiccup that needs to be addressed:</p>
<h1 id="hosting-packages">Hosting Packages</h1>
<p>At the time of writing Unity <em>still</em> doesn’t have an official way to host custom UPM packages. The officially-supported ways for pulling in package dependencies are:</p>
<ul>
<li>The local file system, either by dropping the package directly into your project’s <code class="language-plaintext highlighter-rouge">Packages</code> folder or by manually specifying the path to the package on your local filesystem.</li>
<li>Via Git, by specifying the URL of Git repository. This makes posting a package up on GitHub a pretty common way of sharing Unity packages.</li>
<li>Via NPM (of all things). Unity’s docs specifically <a href="https://docs.unity3d.com/Manual/cus-share.html">suggest hosting your own NPM package registry</a>.</li>
</ul>
<p>The last option is, <em>in theory</em>, the best option since it doesn’t force a dependency on Git (since not all projects are already using Git) and doesn’t require users to vendor local copies of the package. However, hosting your own package registry is a hurdle for most developers who just want to share some useful utility code. Some intrepid folks have actually started hosting their packages <a href="https://www.npmjs.com/search?q=unity">on NPM proper</a>, which… is something, I guess.</p>
<p>Fortunately, it was only a matter of time before someone stepped in to provide a common package registry for Unity developers. Enter <a href="https://openupm.com/">OpenUPM</a>: An open source package registry with a built-in build pipeline for automatically deploying packages to the registry. Any UPM package hosted on GitHub can be added to the registry, and OpenUPM will build the package and host it for redistribution. It also provides a <a href="https://openupm.com/docs/#scope-registry-and-command-line-tool">nifty command line tool</a> for adding and updating packages, since the “scoped registry” system for adding external packages can be tedious to update by hand.</p>
<p>OpenUPM is… a bit of a weird project. As far as package registries go, it’s pretty odd to be able to publish other people’s packages. The built-in build pipeline is also somewhat unusual, since you usually publish packages to the registry directly rather than having the registry go out and find the package elsewhere. The need for a separate command line tool also goes against the grain for UPM, where the expected flow for adding/updating packages is to go through the package manager window in the editor.</p>
<p>However I can forgive OpenUPM’s quirks since it’s providing a very important service (and most of those quirks are working around problems that Unity caused in the first place). Being able to easily host Unity packages means that for the first time in Unity’s history it’s actually possible to start building out a more complete package ecosystem! Packages can be published and versioned properly, and packages can reliably depend on other packages without having to manually copy their contents.</p>
<h1 id="testing-open-source-projects">Testing Open Source Projects</h1>
<p>However, if you’re maintaining an open source project of any kind, having build and test automation is pretty critical in order to be able to ensure that the code you’re publishing actually works as intended. Historically this has been a major pain point for Unity projects.</p>
<p>For one thing, running the Unity editor from the command line has always been a struggle. For a long time the command line options were very poorly documented, and the editor would often fail to correctly report errors, leaving you with no feedback as to what failed or why. It was also especially difficult to run Unity in a headless environment, meaning things like Docker were often non-starters.</p>
<p>Worst of all is Unity’s license activation policy. In order to run Unity you need to activate a license. Anyone can activate a free license for personal use, but <a href="https://docs.unity3d.com/Manual/ManualActivationGuide.html">doing so is a manual process process</a>, there’s no automated way of doing so. What’s worse is that license activations are pinned to the machine that you activated the license on, which means that VM-based build systems are basically unusable since each run requires a fresh license activation. If you have a professional Unity license you can activate that more easily from the command line. However professional licenses can only be activated on two machines at a time, which means even if you’re paying the big bucks for a license you can still have at most two concurrent builds! Even once running Unity from the command line became more viable, the need to activate a license has effectively killed every attempt I’ve ever made to setup automated testing for my open source projects (and I’ve tried many times over the last few years).</p>
<p>However, in the last couple of years two projects have popped up that have managed to solve this issue (for the most part). First, a user on GitLab has started <a href="https://gitlab.com/gableroux/unity3d">providing pre-built Docker images with Unity installed</a>. The project also includes instructions for how to activate a Unity personal license <em>from within the Docker container</em>. This effectively works around the need to activate a license per machine, because a given Docker image <em>looks</em> like the same machine to Unity no matter how many times you run it!</p>
<p>Using those Docker images, another person has been able to build out <a href="https://github.com/webbertakken/unity-actions">pre-made actions Unity</a> for GitHub Actions (GitHub’s new CI service). The project provides actions for running tests and building for different platforms, and provides built-in support for activating personal licenses! This cuts the amount of manual work needed to setup test automation down to a minimum, and makes it actually viable to setup automation for open source project. For example, the <a href="https://github.com/kongregate/kongregate-web">kongregate-web</a> package that I maintain is setup to test against two different versions of Unity, and verifies that the code works both in editor and when built for WebGL!</p>
<h1 id="generating-documentation">Generating Documentation</h1>
<p>Another longstanding issue I’ve had in trying to maintain open source Unity packages is difficulty in generating API documentation. C# has <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/">built-in support for doc comments</a>, however I previously hadn’t been able to find a tool that can generate a hostable website for browsing the doc. This makes it hard for users to see what functionality your package provides without digging through source code, which is less than ideal.</p>
<p>But recently I came across <a href="https://dotnet.github.io/docfx/">DocFX</a>, which seems to now be the semi-official documentation generator for .NET, and I was able to <a href="https://kongregate.github.io/kongregate-web/api/">get it working for a Unity package</a> without much issue! DocFX knows how to parse C# source code, and it doesn’t seem to mind that the code isn’t setup with a proper <code class="language-plaintext highlighter-rouge">.csproj</code> file. Just write regular XML comments in your source code, point DocFX at it, and you’re good to go. It’s even pretty easy to <a href="https://github.com/kongregate/kongregate-web/blob/c9e71e4d1bd0913a194177e4e6e18893336700ba/.github/workflows/main.yml#L49">automatically publish the generated docs to a GitHub Pages site</a> using GitHub Actions! Unity seems to be using it to generate the documentation for all of their packages, too, which gives me some confidence that the tool has proven to work reasonably well with Unity projects.</p>
<h1 id="the-not-so-bright-side">The Not-So-Bright Side</h1>
<p>As per usual, not everything is sunshine and roses in the Unity world. UPM is a massive step forward compared to what we had before, but there are still some unfortunate pain points to deal with:</p>
<ul>
<li>The lack of an official package registry is really a massive oversight. Centralized package hosting is usually like half the point of having a package manger in the first place, and treating GitHub as the semi-official solution isn’t ideal for projects that aren’t already using Git. OpenUPM is a stopgap solution, but the way the scoped registry system is setup poses problems for packages that depend on other packages on OpenUPM. Specifically…</li>
<li>A package can’t itself declare scoped registries, so a project pulling in the package needs to also add the scoped registry declarations for the package’s entire dependency tree. This is gradually becoming more of an issue as people continue to publish more packages on OpenUPM that in turn depend on other packages. This is one of the things that make the custom OpenUPM command line tool necessary, since you have to potentially add scoped registry entries for the package’s entire dependency tree.</li>
<li>The package manager UI in the editor doesn’t seem to work well with general purpose registries like OpenUPM. The UI will show packages that are a part of the declared scope for the registry (i.e. where the package name starts with the specified prefix), but for OpenUPM there’s no common scope that all packages are a part of, so the UI doesn’t let you brown or add OpenUPM packages. I can imagine some valid reasons why UPM is setup to work this way, but it highlights the difficulties that come with not having an official package registry.</li>
<li>Dealing with conflicts between package versions in dependencies isn’t great. Your project can only pull in a single canonical version of a package, so if multiple packages depend on different versions of the same package UPM needs to pick a single version to use. Unity can sometimes resolve this automatically by <a href="https://docs.unity3d.com/Manual/upm-conflicts-auto.html">grabbing the highest required version</a>, but also sometimes it can’t and <a href="https://docs.unity3d.com/Manual/upm-conflicts-override.html">you get to deal with it yourself</a>. Honestly I’m not too upset about this one, though; This restriction comes down to how .NET works than anything Unity-specific, and this is a problem that you run into with various package managers so it’s not like this is an entirely solved problem. Still, it’s a pain point that’s only going to increase as inter-dependencies between packages becomes more common.</li>
<li>While I highlighted earlier the ways in which running tests for a Unity project has gotten easier, there’s still more setup when testing a package than is really necessary: For each Unity version that you want to test against you need to have a separate test project setup to test against, including a separate manual license activation for each of those Unity versions. In most cases the package will be self-contained with everything it needs to run its test suite, so these are generally empty projects that exist just so that you can run Unity from the command line. It would be far easier to setup CI for new packages if you could just point Unity at a package and have it run the tests without needing a full project setup, and if you didn’t need to have a license activation when running package tests.</li>
</ul>
<p>At least a couple of these can potentially be addressed by the community by building better tooling. However, some of these can only really be addressed by the improvements to Unity itself.</p>
<h1 id="closing-thoughts">Closing Thoughts</h1>
<p>Things are currently looking much brighter for the Unity ecosystem than they have in the past: With an actual package manager for Unity and an easy way to host those packages it’s much easier to create reusable code than it was previously, the recent improvements to CI setups make it much more viable to maintain an open source Unity package, and the ability to generate readable documentation for packages makes it easier to to use community-provided packages. While I don’t think all of the difficulties around building open source Unity packages are completely behind us, I have hope that it will continue to get easier as more tooling is built by the community.</p>I’m a Unity developer: Professionally I make mobile games with the Unity game engine. I’ve been working with Unity since 2014, and have seen the engine and the ecosystem around it change a lot over the years. I’m also an avid open source developer, and a big believer in the value of having a community-driven ecosystem around any core technology. Many times in my years working with Unity I’ve tried unsuccessfully to setup reusable, open source libraries for Unity in the way that I would for other ecosystems like JavaScript (via NPM) or Rust, failing inevitably due to some limitation in the tooling available for Unity. In the last year or so things have been changing a lot within the Unity ecosystem, and I’m finding that I’m finally able to do all of the things that I’m used to being able to do when setting up open source libraries.Handling Variant Data: A Journey in Three-And-A-Half Parts2020-03-27T00:00:00+00:002020-03-27T00:00:00+00:00https://randompoison.github.io/posts/variant-data<p>This post is an overly-long (and unnecessarily self-indulgent) exploration of handling variant data in a number of different programming languages. While I’m not good at writing introductions that help ease readers into the topic at had, I’ll at least start with some extra context to help others figure out if this article is relevant to their interests:</p>
<ul>
<li>This article specifically discusses <a href="https://docs.microsoft.com/en-us/dotnet/csharp/">C#</a> and JavaScript, though I’ll try to extract conclusions that are applicable to a broader set of languages.</li>
<li>For discussing how to represent data in an interchange format, I’ll be focusing primarily on <a href="https://www.json.org/">JSON</a>.</li>
<li>:crab: <a href="https://www.rust-lang.org/">Rust</a> :crab: is also discussed for comparison purposes. I promise I’ll try to keep it brief.</li>
<li>Architecturally I’ll be focusing on working within a client/server application, however most of the points discussed should apply to non-networked applications.</li>
<li>I’ll be discussing the topic within the context of game development, but the concept is generally applicable for most application domains.</li>
</ul>
<p>Admittedly the specifics in this article are tailored to be relevant to my current work at <a href="http://synapsegames.com/">Synapse Games</a>, but I’ll do my best to keep the discussion general.</p>
<h1 id="prologue">Prologue</h1>
<p>The point of this article is not to discuss what variant data <em>is</em>, rather to discuss how to handle variant data in software development. However, it’s going to be difficult to have that discussion without having a shared understand of what variant data is, so I suppose some introduction is in order.</p>
<p>At a high level, you have variant data whenever a given value can have multiple possible “types” or “shapes” and you need to be able to interpret the value differently based on the “type”. Generally this comes up when dealing with lists (or other collections) of heterogeneous data, where you can’t statically determine the type of each element in the collection.</p>
<p>As a practical example of this, we’ll look at awarding a player items for completing a quest in a hypothetical mobile game. In our example game, the player can earn a number of different types of rewards from completing a quest:</p>
<ul>
<li>⭐ Stars (i.e. soft currency)</li>
<li>💎 Gems (i.e. hard currency)</li>
<li>🧙 New heroes</li>
<li>🛡️ Equipment items</li>
</ul>
<p>Each of these different items is defined slightly differently:</p>
<ul>
<li>⭐ Stars and 💎 Gems both only specify a quantity.</li>
<li>🧙 Heroes specify a unique ID for the hero that has been unlocked. No quantity is specified, since you can only unlock a given hero once!</li>
<li>🛡️ Equipment specifies a unique ID for the item, plus positive integer value for the item’s durability.</li>
</ul>
<p>This setup isn’t a completely realistic example of how you’d want to setup this kind of game, however it presents the following challenges that make it a useful example:</p>
<ul>
<li>There are three different “shapes” for reward items: Just a quantity (⭐ and 💎), just an ID (🧙), or an ID plus an integer (🛡️).</li>
<li>Two of the possible rewards look the same (⭐ and 💎), so we need to make sure we can differentiate between the two at runtime!</li>
<li>Two of the possible reward types share a common field (🧙 and 🛡️ both have an ID field), but do not otherwise have the same shape. In practice, that means that this field may need to be interpreted differently depending on the actual type of the reward (i.e. a hero ID is used to look up the stats for the hero, and the equipment ID is used to look up the stats for the equipment, and the two can’t be interchanged).</li>
</ul>
<p>When a player completes a quest, the server for our hypothetical game needs to be able to do the following:</p>
<ul>
<li>Load the list of rewards from a configuration file somewhere.</li>
<li>Build a runtime representation of the list of rewards such that it can update the players account with the awarded items.</li>
<li>Encode that list in JSON so that it can be sent to the client. This could be as simple as forwarding the same configuration data it originally loaded, or could involve re-serializing its own in-memory representation.</li>
</ul>
<p>In turn, the client must be able to:</p>
<ul>
<li>Decode the rewards JSON into an appropriate runtime representation that it can use.</li>
<li>Display the list of rewards to the player.</li>
</ul>
<p>This leaves us with two main questions:</p>
<ul>
<li>How do we best represent our rewards in our data format of choice so that we can save it to a configuration file and communicate between our client and server code?</li>
<li>How do we best represent our list of rewards at runtime in our language(s) of choice?</li>
</ul>
<p>For the first question, we’ll look at how we can robustly represent this kind of data in JSON since it’s a very common data format and the principles we discuss will be broadly applicable for many other formats. For the latter, the answer depends heavily on what language you’re using and what features it provides to help with this kind of data. As such we’ll discuss two different options: We’ll look at JavaScript to see how variant data can be handled in a highly dynamic language, and C# to see how we can use a stronger type system to enforce correctness when working with variant data.</p>
<h1 id="part-i-json">Part I: JSON</h1>
<p>Before we dig into the nuances of how to represent this data in different programming languages, let’s look at how we can represent a list of rewards in a language-independent way by encoding it in JSON. Both our client and server code ultimately needs to understand this JSON format, so it should be informative to the subsequent discussions.</p>
<p>The dead simplest approach would be to make a list of objects, with each object containing the fields needed for each item:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"quantity"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="w"> </span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"quantity"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">123</span><span class="w"> </span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">111</span><span class="p">,</span><span class="w">
</span><span class="nl">"durability"</span><span class="p">:</span><span class="w"> </span><span class="mi">1000</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>
<p>While this setup contains all of the necessary data for our rewards, we can quickly identify a couple of problems with this approach:</p>
<ul>
<li>⭐ Stars and 💎 Gems are exactly the same in the JSON! That means our code is going to have a hard time distinguishing between the two, risking that we award 💎 when the player should have gotten ⭐ (or vice versa). You could get around this by using a different field names for the two (e.g. “quantity” for ⭐ Stars and “amount” for 💎 Gems), but that often leads to making the data (and the code relating to it) harder to understand.</li>
<li>In order to distinguish between a 🧙 Hero and an 🛡️ Equipment we need to check for the presence of the <code class="language-plaintext highlighter-rouge">durability</code> field. While this is simple to do now, it will quickly become tedious and error-prone if we end up adding more item types in the future.</li>
</ul>
<p>The best way to disambiguate the rewards is to <strong>tag each reward with its type</strong>. For example:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stars"</span><span class="p">,</span><span class="w"> </span><span class="nl">"quantity"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="w"> </span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gems"</span><span class="p">,</span><span class="w"> </span><span class="nl">"quantity"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>
<p>Doing this means we only have to check a single field in order to reliably determine the type of each reward. For JSON specifically, there are at least three different ways we can tag our data:</p>
<ul>
<li>
<p><strong>Internal tagging</strong>, where the tag is done as a field within the object:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stars"</span><span class="p">,</span><span class="w">
</span><span class="nl">"quantity"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div> </div>
<p>This format is the cleanest in terms of readability (as long as you can guarantee that the tag field will always be listed first), but has the drawback that the data itself cannot contain a field with the same name used for the tag field. It’s also worth noting that this approach won’t work if your data wasn’t already represented as an object, e.g. if the data is a string then there’s nowhere to add the tag field.</p>
</li>
<li>
<p><strong>Adjacent tagging</strong>, where the tag and data are adjacent fields within a containing object:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"tag"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stars"</span><span class="p">,</span><span class="w">
</span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"quantity"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div> </div>
<p>This approach avoids the issue of conflicting field names and allows more flexibility in how you represent the data for each reward as compared to the internal tagging approach. For example, a ⭐ Stars reward could also be represented as:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"tag"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stars"</span><span class="p">,</span><span class="w">
</span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div> </div>
<p>Where <code class="language-plaintext highlighter-rouge">data</code> is a numeric value, rather than an object containing a numeric value. This depends somewhat on the capabilities of your programming language, though.</p>
</li>
<li>
<p><strong>External tagging</strong>, where the tag is the key a container object:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"stars"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"quantity"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div> </div>
<p>The main advantage of this approach is that it can enable more efficient deserialization logic as the deserialization code can always determine the expected “type” of the data before reading any of the data itself, however it is arguably the most awkward syntax from a human-readability perspective. It also has the same flexibility in representation for the reward data that the adjacent tagging approach does.</p>
</li>
</ul>
<p>All of these approaches are valid and will solve the issue of ambiguity in your data. In practice which approach you choose will come down to two factors:</p>
<ul>
<li>How important human-readability is for your purposes. It may be worth going with internal tagging if you expect to often be reading (or writing!) the JSON for your data.</li>
<li>What format is best supported by the serialization system used for your language. Different serialization libraries will have different conventions for how they manage this kind of data, and it’s often easiest to stick with the default conventions of the library you’re using.</li>
</ul>
<h1 id="interlude-goals-and-criteria">Interlude: Goals and Criteria</h1>
<p>Before we start looking at how to handle this data in our target programming languages, I want to lay out some criteria for what a “good” system for handling variant data looks like. As we’ll see, there’s many different ways of representing such data at runtime, so we’ll need some way of comparing them against each other.</p>
<ul>
<li><strong>Robustness</strong> - Is the deserialization logic able to reliably handle or reject unexpected data? While this depends on the specifics of your application, it’s generally best to catch invalid input data as early as possible.</li>
<li><strong>Correctness</strong> - When working with variant data in code, does the system catch invalid usage of variant data (e.g. trying to get the <code class="language-plaintext highlighter-rouge">durability</code> field of a ⭐ Stars reward)? Does it ensure that you handle all the possible variants when? Does it make it easy to refactor existing code when you add/remove/change a variant?</li>
<li><strong>Performance</strong> - How much overhead is needed to represent variant data? Variant data almost always has some additional costs as compared to non-variant data, but different approaches will have different performance characteristics.</li>
</ul>
<p>My personal goal is to find a solution that best enforces correctness/robustness while minimizing performance overhead, and the examples I bring up throughout this post will generally trend in the direction of finding more tools to enforce correctness. Where possible I try to bring up opportunities to make different trade offs, or at least point out where further pursuing correctness would have diminishing returns.</p>
<p>It’s also worth noting up front that often times it’s possible to side step needing to handle variant data at all. Looking at our rewards example, we could in theory not put all of our rewards in a single list. Instead, we could make each reward type its own field or list, such that each item in the list is always of a known type:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"stars"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span><span class="w">
</span><span class="nl">"heroes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">123</span><span class="w"> </span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">234</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"equipment"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">111</span><span class="p">,</span><span class="w"> </span><span class="nl">"durability"</span><span class="p">:</span><span class="w"> </span><span class="mi">70</span><span class="w"> </span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">707</span><span class="p">,</span><span class="w"> </span><span class="nl">"durability"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>This completely sidesteps the need to differentiate between different types of reward, since each field only ever contains a reward of a single type!</p>
<p>This is absolutely a reasonable approach, and it may well be a better solution for your use case than dealing with variant data. An alternate solution we’ve used at Synapse is to use a generic system for defining items, such that all items are effectively the same “type”. However, sometimes this simply isn’t an option for what you’re trying to do, sometimes you specifically need to have different types of data/object in a single collection. As such, it’s still helpful to explore the available options for dealing with variant data, even if a non-variant solution is sometimes the better option.</p>
<h1 id="part-ii-javascript-and-dynamic-languages-in-general">Part II: JavaScript (and dynamic languages in general)</h1>
<p>The nice thing about implementing this in JavaScript is that we can represent the data in memory identically to how we represent it in JSON. The bad thing about implementing this in JavaScript is that that’s the only nice thing.</p>
<p>Okay, let me try that again with less snark: For highly dynamic languages, we have both the gift and the curse of having no type system to worry about when dealing with variant data. This means that it’s very easy for us to jam heterogenous data into a collection and start working with it immediately, but unfortunately means that you often don’t have much support at the language-level for handling that data in a robust way. I’m going to be looking at JavaScript specifically because it’s widely used (and it’s the only dynamic language that I know fairly well), but a lot of these solutions will apply to other dynamic languages.</p>
<p>Since JSON is (deliberately) so similar to JS types, it’s very easy to translate one of the tagging solutions described above directly. By <code class="language-plaintext highlighter-rouge">switch</code>ing on the <code class="language-plaintext highlighter-rouge">type</code> field, we can iterate over a list of rewards and handle each reward based on its type. For example, using the internal tagging example from before:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">rewards</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span> <span class="dl">"</span><span class="s2">type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">stars</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">quantity</span><span class="dl">"</span><span class="p">:</span> <span class="mi">100</span> <span class="p">},</span>
<span class="p">{</span> <span class="dl">"</span><span class="s2">type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">gems</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">quantity</span><span class="dl">"</span><span class="p">:</span> <span class="mi">5</span> <span class="p">},</span>
<span class="p">];</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">reward</span> <span class="k">of</span> <span class="nx">rewards</span><span class="p">)</span> <span class="p">{</span>
<span class="k">switch</span> <span class="p">(</span><span class="nx">reward</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">case</span> <span class="dl">"</span><span class="s2">stars</span><span class="dl">"</span><span class="p">:</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Got some stars: </span><span class="dl">"</span><span class="p">,</span> <span class="nx">reward</span><span class="p">.</span><span class="nx">quantity</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="dl">"</span><span class="s2">gems</span><span class="dl">"</span><span class="p">:</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Got some gems: </span><span class="dl">"</span><span class="p">,</span> <span class="nx">reward</span><span class="p">.</span><span class="nx">quantity</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This solution is fairly straightforward and will work basically the same way for any of the tagging styles shown in the previous section. However, when we look at the criteria I laid out above it leaves a lot to be desired:</p>
<ul>
<li>There’s nothing in the language to help you correctly handle all possible variants when <code class="language-plaintext highlighter-rouge">switch</code>ing on the variant tag. You have to remember to list them all, or your code will silently ignore some of your elements.</li>
<li>There’s nothing preventing you from accessing invalid fields on the variant (or fields from the wrong variant) even after you’ve checked the tag.</li>
<li>If you’re using <code class="language-plaintext highlighter-rouge">JSON.parse()</code>, there’s nothing to prevent your code from loading invalid or malformed data. This is generally true with <code class="language-plaintext highlighter-rouge">JSON.parse()</code> (you’ll need to use a separate JSON schema validator like <a href="https://www.npmjs.com/package/ajv">ajv</a> to validate the data), but working with variant data exacerbates the issues that come with silently consuming invalid data: Unless you have a <code class="language-plaintext highlighter-rouge">default</code> case in every <code class="language-plaintext highlighter-rouge">switch</code> block where you check the variant tag, your code will always silently ignore invalid or unknown variants. This can lead to especially subtle bugs that can be difficult to diagnose.</li>
</ul>
<p>That being said, those limitations are pretty general limitations of JavaScript and aren’t specific to working with variant data: There’s nothing stopping you from accessing invalid fields on non-variant data, either, for example. This means that our nice-and-simple approach is also probably about as good as it gets. There are plenty of ways that you could build more infrastructure around this in order to enforce correctness, but doing so adds a lot of overhead in the form of runtime checking. Doing so also goes against most idiomatic usage patterns for JavaScript, since JavaScript APIs often lean into the flexibility the language provides in order to be as permissive as possible, rather than trying to proactively reject invalid data.</p>
<p>Ultimately, there’s not much you need to do when dealing with variant types in a dynamic language. Make sure you structure your data with an explicit tag and you’ll have everything you need to disambiguate objects of different types.</p>
<h1 id="part-iii-c">Part III: C#</h1>
<p>In C#, the obvious way to represent variant data like this is with an enum:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">enum</span> <span class="n">RewardType</span>
<span class="p">{</span>
<span class="n">Stars</span><span class="p">,</span>
<span class="n">Gems</span><span class="p">,</span>
<span class="n">HeroUnlock</span><span class="p">,</span>
<span class="n">Equipment</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>
<p>However, this leaves us with the question of how to handle the data for each variant. A simple solution is to create a single class that contains the data for all the variants, plus a field for the tag so that you can determine which fields are valid:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Reward</span>
<span class="p">{</span>
<span class="k">public</span> <span class="n">RewardType</span> <span class="n">Type</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">long</span> <span class="n">Quantity</span><span class="p">;</span>
<span class="k">public</span> <span class="n">HeroId</span> <span class="n">Hero</span><span class="p">;</span>
<span class="k">public</span> <span class="n">EquipmentId</span> <span class="n">Equipment</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">Durability</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This approach wins out in simplicity, but has a number of drawbacks that make it less than ideal for actual use:</p>
<ul>
<li>There’s nothing that requires you to check the <code class="language-plaintext highlighter-rouge">Type</code> field before accessing any of the fields of the reward.</li>
<li>There’s nothing preventing you from accessing the wrong fields for the current reward type.</li>
<li>There’s nothing obvious indicating which fields are valid for any given reward type. You’ll either need to have comments in the code (and then make sure you check those comments when working with <code class="language-plaintext highlighter-rouge">Reward</code> data) or document those details somewhere else (and then hope you can remember where those docs are).</li>
<li>Every reward uses as much memory as all reward types combined. This is a fairly minor point compared to the other two, but minimizing garbage allocation is often important for consistent performance in games, so it would be good to reduce the memory needed for <code class="language-plaintext highlighter-rouge">Reward</code> objects if possible.</li>
</ul>
<p>The fact that this approach makes it easy to accidentally access invalid fields is problematic, as any field that’s not valid for the current reward type is effectively uninitialized, making it a potential source of bugs.</p>
<p>The better way to represent variant data, in my opinion, is to use an interface (or base class) and downcasting:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IReward</span> <span class="p">{</span> <span class="p">}</span>
<span class="k">public</span> <span class="k">struct</span> <span class="nc">Stars</span> <span class="p">:</span> <span class="n">IReward</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">readonly</span> <span class="kt">long</span> <span class="n">Quantity</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">struct</span> <span class="nc">Equipment</span> <span class="p">:</span> <span class="n">IReward</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">readonly</span> <span class="n">EquipmentId</span> <span class="n">Id</span><span class="p">;</span>
<span class="k">public</span> <span class="k">readonly</span> <span class="kt">int</span> <span class="n">Durability</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// And so on, with a different struct</span>
<span class="c1">// or class for each reward type.</span>
</code></pre></div></div>
<p>When working with an <code class="language-plaintext highlighter-rouge">IReward</code> object, you can take advantage of the pattern matching feature added in C# 7.0 to handle the reward based on its concrete type:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">switch</span> <span class="p">(</span><span class="n">reward</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">case</span> <span class="n">Stars</span> <span class="n">stars</span><span class="p">:</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"Awarded </span><span class="p">{</span><span class="n">stars</span><span class="p">.</span><span class="n">Quantity</span><span class="p">}</span><span class="s"> stars"</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="n">Equipment</span> <span class="n">equipment</span><span class="p">:</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span>
<span class="s">$"Awarded equipment with ID </span><span class="p">{</span><span class="n">equipment</span><span class="p">.</span><span class="n">Id</span><span class="p">}</span><span class="s"> "</span> <span class="p">+</span>
<span class="s">$"and durability </span><span class="p">{</span><span class="n">equipment</span><span class="p">.</span><span class="n">Durability</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="c1">// And so on...</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This approach has a number of advantages over using a single, combined class for all of the reward types:</p>
<ul>
<li>You can’t access any of the fields for any of the rewards variants without first checking the reward type (by downcasting to a concrete type). On the other hand, if there’s data that’s guaranteed to be shared by all reward types (e.g. if there’s always a <code class="language-plaintext highlighter-rouge">quantity</code> field so that more than one of a given item can be given at once), that can be added to the <code class="language-plaintext highlighter-rouge">IReward</code> interface so that it’s accessible without downcasting.</li>
<li>If you accidentally downcast an <code class="language-plaintext highlighter-rouge">IReward</code> object to the wrong type, you’ll either get <code class="language-plaintext highlighter-rouge">null</code> or an exception (depending on what type of casting you did) but you’ll never get an invalid object.</li>
<li>Any given reward type only needs to have the fields that are relevant to it, making the reward data much easier to work with when writing code.</li>
<li>Performance-wise there’s a bit of extra overhead that comes with downcasting, but it also saves a bit of heap space by reducing the size of each allocated reward object.</li>
</ul>
<p>While I like this solution a lot, there are a few things about it that I’m not quite satisfied with:</p>
<ul>
<li>The compiler won’t remind you to handle all possible variants. If you don’t have a case for all variants, your <code class="language-plaintext highlighter-rouge">switch</code> statement will silently do nothing. The <a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/switch-expression">switch expression</a> added in C# 8.0 is a bit better in that it at least throws an exception if none of your cases are executed, but it has other restrictions that mean it can’t always be used (i.e. that it must return a value). And unfortunately, C# doesn’t emit warnings when you fail to handle possible cases <a href="https://stackoverflow.com/a/12531166/6649664">even when working with regular enums</a>.</li>
<li>This approach also doesn’t play well with deserialization conventions. Most serialization libraries for C# use reflection to handle loading data into instances of your classes, <a href="https://www.newtonsoft.com/json">Json.NET</a> being perhaps the most widely used example. When you’re deserializing into a <code class="language-plaintext highlighter-rouge">List<IReward></code>, the serialization system can’t necessarily tell what concrete type should be instantiated for each element in the list. In general this means you’ll need to write some extra glue to tell it what the valid variants are and how to determine the type of each element. <a href="https://skrift.io/articles/archive/bulletproof-interface-deserialization-in-jsonnet/">This article discusses how to handle this kind of data in Json.NET</a>, for example.</li>
</ul>
<p>That said, this approach is a pretty solid solution as far as C# goes. It should also apply nicely to most other languages that support classical inheritance. Even if your language of choice doesn’t have pattern matching, most languages have some kind of speculative downcasting that will allow you to do something similar.</p>
<h1 id="epilogue-rust">Epilogue: Rust</h1>
<p>If you’re curious about how we could further pursue correctness in handling variant data, we can take a look at how this would be handled in the <a href="https://www.rust-lang.org/">Rust programming language</a>. If you’re not familiar, Rust is a relatively new programming language that combines a very strong, expressive type system with the ability to write abstractions with very little performance overhead. This includes first-class support for the kind of variant types that we’ve been looking at!</p>
<p>First, a brief introduction to <a href="https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html">Rust’s enums</a>. Like many languages, Rust supports creating user-defined enumeration types that can be one of several user-defined values. So for the following enum definition:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">enum</span> <span class="n">MyEnum</span> <span class="p">{</span>
<span class="n">Foo</span><span class="p">,</span>
<span class="n">Bar</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>
<p>You can create a value of <code class="language-plaintext highlighter-rouge">MyEnum::Foo</code> or <code class="language-plaintext highlighter-rouge">MyEnum::Bar</code>, any other value for a variable of type <code class="language-plaintext highlighter-rouge">MyEnum</code> is a compiler error:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// Correct way to use `MyEnum`.</span>
<span class="k">let</span> <span class="n">my_enum</span> <span class="o">=</span> <span class="nn">MyEnum</span><span class="p">::</span><span class="n">Foo</span><span class="p">;</span>
<span class="c">// ERROR: Not a valid variant.</span>
<span class="k">let</span> <span class="n">my_enum</span> <span class="o">=</span> <span class="nn">MyEnum</span><span class="p">::</span><span class="n">NotReal</span><span class="p">;</span>
<span class="c">// ERROR: Arbitrary integers aren't</span>
<span class="c">// valid values.</span>
<span class="k">let</span> <span class="n">my_enum</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="c">// ERROR: Integers can't be cast to</span>
<span class="c">// the enum type.</span>
<span class="k">let</span> <span class="n">my_enum</span> <span class="o">=</span> <span class="mi">5</span> <span class="k">as</span> <span class="n">MyEnum</span><span class="p">;</span>
</code></pre></div></div>
<p>Rust then has <code class="language-plaintext highlighter-rouge">match</code> blocks which behave similarly to <code class="language-plaintext highlighter-rouge">switch</code> blocks in other languages:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">match</span> <span class="n">my_enum</span> <span class="p">{</span>
<span class="nn">MyEnum</span><span class="p">::</span><span class="n">Foo</span> <span class="k">=></span> <span class="nd">println!</span><span class="p">(</span><span class="s">"my_enum was Foo"</span><span class="p">),</span>
<span class="nn">MyEnum</span><span class="p">::</span><span class="n">Bar</span> <span class="k">=></span> <span class="nd">println!</span><span class="p">(</span><span class="s">"my_enum was Bar"</span><span class="p">),</span>
<span class="p">}</span>
</code></pre></div></div>
<p>However, unlike most languages, Rust’s enums can also contain data!</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">enum</span> <span class="n">Reward</span> <span class="p">{</span>
<span class="n">Stars</span> <span class="p">{</span> <span class="n">quantity</span><span class="p">:</span> <span class="nb">u32</span> <span class="p">},</span>
<span class="n">Gems</span> <span class="p">{</span> <span class="n">quantity</span><span class="p">:</span> <span class="nb">u32</span> <span class="p">},</span>
<span class="n">Hero</span> <span class="p">{</span> <span class="n">id</span><span class="p">:</span> <span class="n">HeroId</span> <span class="p">},</span>
<span class="n">Equipment</span> <span class="p">{</span>
<span class="n">id</span><span class="p">:</span> <span class="n">EquipmentId</span><span class="p">,</span>
<span class="n">durability</span><span class="p">:</span> <span class="nb">u32</span>
<span class="p">},</span>
<span class="p">}</span>
</code></pre></div></div>
<p>When working with a value of an enum, you can’t directly access any of the fields declared in the variants:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">reward</span> <span class="o">=</span> <span class="nn">Reward</span><span class="p">::</span><span class="n">Stars</span> <span class="p">{</span> <span class="n">quantity</span><span class="p">:</span> <span class="mi">20</span> <span class="p">};</span>
<span class="c">// ERROR: No field `quantity` on type `Reward`.</span>
<span class="k">let</span> <span class="n">quantity</span> <span class="o">=</span> <span class="n">reward</span><span class="py">.quantity</span><span class="p">;</span>
</code></pre></div></div>
<p>Instead, you need to match on the value and handle all of the possible variants. Only within the relevant match arm can you access the fields of any given variant:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">match</span> <span class="n">reward</span> <span class="p">{</span>
<span class="nn">Reward</span><span class="p">::</span><span class="n">Stars</span> <span class="p">{</span> <span class="n">quantity</span> <span class="p">}</span> <span class="k">=></span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"Awarding {} stars"</span><span class="p">,</span> <span class="n">quantity</span><span class="p">),</span>
<span class="nn">Reward</span><span class="p">::</span><span class="n">Gems</span> <span class="p">{</span> <span class="n">quantity</span> <span class="p">}</span> <span class="k">=></span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"Awarding {} gems"</span><span class="p">,</span> <span class="n">quantity</span><span class="p">),</span>
<span class="nn">Reward</span><span class="p">::</span><span class="n">Hero</span> <span class="p">{</span> <span class="n">id</span> <span class="p">}</span> <span class="k">=></span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"Unlocking hero {}"</span><span class="p">,</span> <span class="n">id</span><span class="p">),</span>
<span class="nn">Reward</span><span class="p">::</span><span class="n">Equipment</span> <span class="p">{</span> <span class="n">id</span><span class="p">,</span> <span class="n">durability</span> <span class="p">}</span> <span class="k">=></span> <span class="p">{</span>
<span class="nd">println!</span><span class="p">(</span>
<span class="s">"Awarding equipment {} with durability {}"</span><span class="p">,</span>
<span class="n">id</span><span class="p">,</span>
<span class="n">durability</span><span class="p">,</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This setup, in my opinion, is the ideal way of handling variant data at runtime. It makes it very easy to always correctly handle reward data:</p>
<ul>
<li>You’re statically prevented from accessing the data in the reward until you’ve checked which type of reward it is, and you can only ever access the fields of the correct variant.</li>
<li>If you forget to handle any of the possible cases, you get a compiler error! This also means that if you later add a new reward type, the compiler will makes sure you go back and update all the places in the code base where you’re already checking the type of a reward.</li>
<li>It’s also very efficient: There’s no allocation involved in creating an instance of <code class="language-plaintext highlighter-rouge">Reward</code>, and the size of <code class="language-plaintext highlighter-rouge">Reward</code> is equal to the size of its largest variant plus the size of the discriminant (which will rarely need to be larger than a single byte).</li>
<li>Rust’s de facto serialization library <a href="https://serde.rs/">Serde</a> automatically validates incoming data and rejects any data that can’t be correctly represented at runtime. And because validation happens as part of deserialization, there’s little-to-no performance overhead in doing so!</li>
</ul>
<p>While not many people are using Rust in production, most functional programming languages support something similar (referred to as <a href="https://en.wikipedia.org/wiki/Tagged_union">“sum types”, “algebraic data types”, or “tagged unions”</a>). If you’re using such a language, you’ll likely get similar results to what you’d get from using an <code class="language-plaintext highlighter-rouge">enum</code> in Rust!</p>
<h1 id="conclusion">Conclusion</h1>
<p>Whew! That’s a lot of words on a pretty minor data pattern. While I covered a lot of details across a number of different languages, I think the main takeaways to keep in mind are:</p>
<ul>
<li><strong>Tag your variant data!</strong> Don’t do ad hoc variant detection by checking for the presence of different fields, as that can still fail if you have ambiguous variants.</li>
<li><strong>Take advantage of language features</strong> to make your variant data safer to work with. If you have a type system, don’t just jam all of your variants into a single type that has a bunch of uninitialized fields. If you’re working with a more dynamic language, make sure to still use a tag at runtime!</li>
</ul>
<p>Once you start getting into the specifics of a single application, there’s a lot more nuance you can get into in terms of how to best represent your data and when it’s best to use variant data vs a different approach. But all of that discussion is out of the scope of this article, so I’ll leave it at that!</p>This post is an overly-long (and unnecessarily self-indulgent) exploration of handling variant data in a number of different programming languages. While I’m not good at writing introductions that help ease readers into the topic at had, I’ll at least start with some extra context to help others figure out if this article is relevant to their interests:Sharing C# Code with Unity2019-11-20T00:00:00+00:002019-11-20T00:00:00+00:00https://randompoison.github.io/posts/unity-code-sharing<p>I’ve been doing some investigation into building client/server game architectures with Unity on the front end and a C# server on the back end, specifically pairing an <a href="https://dotnet.microsoft.com/apps/aspnet">ASP.NET</a> server in a traditional .NET environment with a Unity front-end. For games with no realtime gameplay and a desire for high scalability, as is often the case with online mobile games (my industry, for better or worse), it makes sense to build your server with something other than Unity. Traditional .NET development is attractive in this case because it allows you to use the same programming language, C#, to build both your client and server.</p>
<p>In particular, one of the major advantages of using the same programming language for both client and server would be the ability to share common game logic between the two. Doing so has major advantages over having the two codebases be completely isolated:</p>
<ul>
<li>Shared definitions for data objects and serialization logic greatly simplifies communication between client and server, reducing errors while making it easier to evolve your API contract.</li>
<li>Sharing common game logic means that you can do client-side prediction and keep your game experience smooth in the face of a slow network connection. This is useful even for non-realtime games!</li>
<li>Generally reduced development time, since logic written for the server can be reused in the client (and vice versa).</li>
</ul>
<p>You can have a look at the <a href="https://github.com/randomPoison/DotNetGamePrototype">DotNetGamePrototype</a> repository to see the example project I’ve been building as part of this investigation.</p>
<p>I have broken this post up into two parts: A direct description of the technical issues that come with sharing code, along with potential ways to deal with these issues, and then a more subjective evaluation of how this impacts any projects that want to take this approach. If you care primarily about my final conclusions, skip to the second part below.</p>
<h1 id="part-one-technical-issues">Part One: Technical Issues</h1>
<p>At the most basic level, most C# code will be source-compatible between a Unity project and a standalone .NET C# project. That is, if you copy-and-paste the source code from one to the other, it will almost certainly compile and run as expected. There are a few caveats to this, though:</p>
<ul>
<li>Any code outside of the <a href="https://github.com/dotnet/standard">.NET Standard</a> must be present in both environments, i.e. if you reference class <code class="language-plaintext highlighter-rouge">Foo</code>, <code class="language-plaintext highlighter-rouge">Foo</code> must be defined in both projects.</li>
<li><a href="https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support">Not all of the .NET Standard is available to Unity</a>. At the time of writing, Unity supports .NET Standard 2.0 but not 2.1, with <a href="https://forum.unity.com/threads/net-standard-2-1.757007/#post-5047175">no ETA on when support will arrive</a>.</li>
<li>For certain platforms, Unity uses a special C# scripting backend called <a href="https://docs.unity3d.com/Manual/IL2CPP.html">IL2CPP</a>. This backend has additional restrictions on what you can do, and only supports <a href="https://docs.unity3d.com/Manual/ScriptingRestrictions.html">a subset of the .NET Standard</a>. If building for platforms that require IL2CPP (such as iOS and most consoles), any shared code must limit itself to the supported subset of functionality.</li>
</ul>
<p>Meeting these requirements only enables a bare minimum of source compatibility, though. Once you have some C# code that you want to share between projects, you’ll need some method of making that code available in both contexts. There are two main ways to share code between the projects:</p>
<ul>
<li><strong>Define the shared code as both a UPM package and a standalone C# project</strong> (i.e. add a <code class="language-plaintext highlighter-rouge">package.json</code> and a <code class="language-plaintext highlighter-rouge">.csproj</code> file to the directory) and then add the shared code as a direct dependency for both projects.</li>
<li><strong>Only define the shared code as a standalone C# project</strong>. Add it as a direct dependency to the server project, and add the built DLLs to Unity.</li>
</ul>
<h2 id="sharing-code-directly">Sharing Code Directly</h2>
<p>The easiest way to share code is to set it up with the necessary configuration for both a standalone .NET project (i.e. a <code class="language-plaintext highlighter-rouge">.csproj</code> file) and a Unity package (i.e. a <a href="https://docs.unity3d.com/Manual/upm-manifestPkg.html"><code class="language-plaintext highlighter-rouge">package.json</code></a> file and an <a href="https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html">Assembly Definition file</a>). If you keep your client and server in the same repository, you can reference the shared project from both via relative paths. This was how I did it in the <a href="https://github.com/randomPoison/DotNetGamePrototype/blob/fb29ae47501e927044a5afe4f068e438c0bcaed5/DotNetGameClient/Packages/manifest.json#L12">example Unity client</a> and <a href="https://github.com/randomPoison/DotNetGamePrototype/blob/fb29ae47501e927044a5afe4f068e438c0bcaed5/DotNetGameServer/DotNetGame.csproj#L17">example server project</a>, and it took minimal effort to setup.</p>
<p>It’s worth noting that setting up your code as a UPM package isn’t strictly necessary. For example, you could directly embed your shared code directly in your Unity project and then reference it from your server code. The key part of this approach is that it shares the source files directly, rather than pre-building a DLL to import into Unity.</p>
<p>The advantage of this approach over most others is simplicity: It requires no extra tooling, no build steps to copy build results or publish packages. You can modify the shared code from both your server project and your Unity project and the changes will immediately show up in both. If keeping your client and server in the same repository is a viable solution for you, then this is probably the easiest approach.</p>
<p>The drawback of this approach is that you end up polluting your shared code project with Unity-specific details. In addition to the <code class="language-plaintext highlighter-rouge">package.json</code> and assembly definition file, Unity requires that there be a <code class="language-plaintext highlighter-rouge">.meta</code> for every file in the package. Unity generates these meta files for you, but that will require you to open Unity every time you add a new file in order to ensure the meta file is generated correctly. These meta files also clutter the files list in your editor (though, with some extra configuration, you can usually configure it to ignore them).</p>
<p>This drawback is relatively minor, and more one of project cleanliness more than a technical issue. Still, it highlights the fact that this solution is a hack, rather than a well-supported use case for Unity.</p>
<h2 id="exporting-a-dll">Exporting a DLL</h2>
<p>If you want to avoid polluting your shared codebase with Unity-specific configuration and files, you can instead build your shared library as a DLL and import that DLL into your Unity project. So long as you are meeting the constraints listed above, the DLL generated from your project can be loaded into a Unity project without issue, including ones that target IL2CPP platforms.</p>
<p>Exporting your project as a DLL to Unity is fairly simple:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet publish -c Release -o ../UnityProject/Assets/Plugins
</code></pre></div></div>
<p>You’ll have to remember to do this any time you update the shared project, or else setup some kind of automation to do so automatically. Such a solution is theoretically possible (at least, I see no technical blockers), but none exists already as far as I can see.</p>
<p>While this approach involves more built-time work, it has the advantage of enabling you to easily pull NuGet dependencies into your Unity project. You can add a <code class="language-plaintext highlighter-rouge"><CopyLocalLockFileAssemblies></code> element in your <code class="language-plaintext highlighter-rouge">.csproj</code>, making the DLLs for all dependencies immediately available alongside the DLL for your project:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Project</span> <span class="na">Sdk=</span><span class="s">"Microsoft.NET.Sdk"</span><span class="nt">></span>
<span class="nt"><PropertyGroup></span>
<span class="nt"><TargetFramework></span>netstandard2.0<span class="nt"></TargetFramework></span>
<span class="nt"><CopyLocalLockFileAssemblies></span>true<span class="nt"></CopyLocalLockFileAssemblies></span>
<span class="nt"></PropertyGroup></span>
<span class="nt"><ItemGroup></span>
<span class="nt"><PackageReference</span> <span class="na">Include=</span><span class="s">"SomePackage"</span> <span class="na">Version=</span><span class="s">"1.2.3"</span> <span class="nt">/></span>
<span class="nt"></ItemGroup></span>
<span class="nt"></Project></span>
</code></pre></div></div>
<p>When you run <code class="language-plaintext highlighter-rouge">dotnet publish</code>, it will also copy any NuGet packages that you’re using in your shared project into the Unity project. Unfortunately, using NuGet packages within Unity has its own problems which we’ll discuss below.</p>
<h2 id="incompatible-dependency-management">Incompatible Dependency Management</h2>
<p>The above approaches work well when your shared code only depends on the .NET Standard, but things quickly begin to break down when you introduce additional dependencies. This could mean adding a Nuget package as a dependency, adding a dependency on a UPM package, or even just adding another internal shared package that is also referenced by the first shared package.</p>
<p>The key issue is that Unity uses a different dependency management system than the rest of the C# ecosystem. While the .NET ecosystem at large uses <a href="https://docs.microsoft.com/en-us/nuget/what-is-nuget">NuGet</a>, Unity has historically had no proper package management system, opting to manually copy source code (or sometimes pre-built DLLs) directly into each project. Starting with the 2018.3 release Unity has introduced their own <a href="https://docs.unity3d.com/Manual/Packages.html">Unity Package Manager</a> as a more robust solution for dependency management.</p>
<p>While UPM is a massive improvement over the previous (non-)solution, it doesn’t support loading NuGet packages, making interop between the two ecosystems difficult. Unless you’re willing to forego using any code outside of the .NET Standard, you’ll want to be able to add dependencies to your shared package via NuGet. Once you do, though, you’ll have a hard time getting your code to still work in Unity.</p>
<p>If you’re taking the shared package approach described above, Unity won’t pull down your NuGet dependencies and your code won’t compile. There are a couple of community-made tools for pulling down NuGet packages (such as <a href="https://github.com/xoofx/UnityNuGet">UnityNuGet</a> and <a href="https://github.com/GlitchEnzo/NuGetForUnity">NuGetForUnity</a>), but it is unclear if any solution is robust enough to be a reliable solution for projects looking to pull in NuGet packages.</p>
<p>On the other hand, the approach described above for automatically copying NuGet dependencies into the Unity project seems to work well if you have a single locally-maintained C# package, but it doesn’t scale up to a more complex project setup. For example, if you were two have two different projects both depending on <code class="language-plaintext highlighter-rouge">SomePackage</code>, each one would pull a copy of <code class="language-plaintext highlighter-rouge">SomePackage.dll</code> into your Unity project and your project will fail to build due to the duplicate DLLs. There are ways to work around this if there are only a few conflicts, but it’s not clear if any such solution would scale well with a large tree of dependencies.</p>
<h2 id="incompatible-software-ecosystems">Incompatible Software Ecosystems</h2>
<p>Even if you find some solution for sharing NuGet packages with Unity, there are deeper incompatibilities to contend with. As noted previously, Unity only supports a subset of valid C#/.NET code on all platforms. At the most basic, you’ll only be able to use NuGet packages that support .NET Standard 2.0, which not all packages do. Fortunately, it should be possible to avoid including such packages in the first place by specifying <code class="language-plaintext highlighter-rouge">netstandard2.0</code> as the target framework in your shared package’s <code class="language-plaintext highlighter-rouge">.csproj</code>.</p>
<p>Things get more tricky when dealing with the restrictions imposed by IL2CPP and the other platform-specific restrictions that Unity projects need to deal with. According to <a href="https://docs.unity3d.com/Manual/ScriptingRestrictions.html">Unity’s documentation</a>, there are a number of things things that are perfectly valid in regular .NET development that will fail in Unity projects built with IL2CPP:</p>
<ul>
<li>The contents of <code class="language-plaintext highlighter-rouge">System.Reflection.Emit</code> are explicitly not supported on platforms that do not support just-in-time compilation. iOS is the prime example of this, though as I understand it some consoles also have this restriction.</li>
<li>The compiler will aggressively remove any code that is never referenced (i.e. a class that is never instantiated). This interacts badly with reflection-based serialization, where a given class may only ever be instantiated via reflection. In this case you can <a href="https://docs.unity3d.com/Manual/IL2CPP-BytecodeStripping.html">manually tell the compiler to not strip a class</a>, but that can be difficult to do if the missing class is hidden in the internals of a pre-compiled DLL.</li>
<li>Generic virtual methods also interact badly with ahead-of-time compilation. There are <a href="https://docs.unity3d.com/Manual/ScriptingRestrictions.html">hacky workarounds</a> for dealing with this when you know all of the concrete instantiations, but this can again be difficult when the specifics are hidden in a pre-compiled DLL that you’re pulling in from a dependency.</li>
</ul>
<p>Additionally, there are platform-specific restrictions unique to the set of platforms supported by Unity that don’t get taken into account by most (or any) packages published to NuGet. Especially, when publishing to the web you’ll run into various restrictions that no other .NET environment has to deal with:</p>
<ul>
<li>Not all platforms support threads, so any code that relies on threads will fail at runtime.</li>
<li>System resources don’t behave the same on all platforms. On the web, browser sandboxing means that very few system resources are accessible at all. In some cases Unity can fake these for you (as is the case with how Unity fakes the existence of a file system), in other cases those APIs will simply fail at runtime. On mobile and console platforms, you have only limited access to the file system, so a library that attempts to create files in the background (e.g. as a data cache) may fail unexpectedly.</li>
</ul>
<p>In my limited experimentation, I have already run into a couple of cases where these limitations come up: The <a href="https://www.newtonsoft.com/json">Json.NET</a> library and WebSocket handling.</p>
<p>Json.NET is <a href="https://www.nuget.org/stats">by far the most widely used NuGet package</a>, and is the de facto standard for JSON serialization in C# and the .NET ecosystem. It also <a href="https://github.com/JamesNK/Newtonsoft.Json/issues/1440">doesn’t work with Unity</a>. There are <a href="https://github.com/jilleJr/Newtonsoft.Json-for-Unity">multiple</a> <a href="https://assetstore.unity.com/packages/tools/input-management/json-net-for-unity-11347">ports</a> <a href="https://github.com/SaladLab/Json.Net.Unity3D">out there</a> in various states of abandonment or disrepair, but none of them are available via NuGet so they can’t be shared with a non-Unity C# library. In order to use Json.NET in your shared code, you have to setup a system where it is pulled in via NuGet when used in your server code, and then pulled in by a different method in your Unity project. This is doable, but if you use the setup described above to pull NuGet dependencies into your Unity project automatically you’re going to run into conflicts fast. This could be possibly be fixed if the upstream library were setup to better support Unity, but there’s been no indication that the maintainer is interested in taking on that work. This solution also only works because Json.NET is popular enough to have community-maintained forks that work with Unity, you likely won’t have the same luck with smaller libraries.</p>
<p>In the case of WebSockets, it’s actually <em>impossible</em> to provide a NuGet package that supports Unity on all platforms. When running in a browser, you can’t open a socket directly. Instead, you have to use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API">browser’s WebSocket API</a>, creating C# bindings to the JavaScript API. And, of course, this setup only works in the browser so you’ll need <a href="https://github.com/randomPoison/DotNetGamePrototype/tree/master/DotNetGameClient/Packages/com.synapse-games.websockets">to abstract over both the browser API and a native implementation</a> for other platforms. You’ll find plenty of WebSocket implementations on NuGet, but none that can be shared meaningfully between Unity and a .NET project. It’s also worth highlighting that this issue isn’t specific to WebSockets; Many system resources have similar caveats that will make NuGet packages incompatible with Unity. Generally you’ll deal with this in the Unity project by abstracting over two or more platform-specific implementations, but this in turn presents complications for dependency management, in that you may only need a given dependency on certain platforms.</p>
<h1 id="part-two-assessment">Part Two: Assessment</h1>
<p>Overall, my assessment of the situation is that code sharing between Unity and .NET works just well enough to be tempting to use, while being broken enough to present major obstacles to large scale development.</p>
<p>The fundamental issue is that, while the two are similar on the surface, Unity is not a true .NET environment. On some platforms Unity uses Mono to run your code, which at least means that your code will behave like regular C# at runtime, but IL2CPP is introduces huge problems for compatibility. Combine that with the fact that Unity has a completely bespoke solution for dealing with dependency management and has to handle certain platform-specific issues that other .NET runtimes don’t (most notably ahead-of-time compilation and running in web browsers), and the picture we end up with is one where Unity is not just another .NET runtime, but its own separate thing that happens to be largely (but not completely!) source-compatible with .NET.</p>
<p>Source compatibility is the notable thing here: Being able to copy a piece of code between a Unity project and a .NET project sure <em>feels</em> like compatibility, so it’s awfully tempting to say that code sharing is possible. And, as noted above, it is possible (easy, even!) at a small scale. What worries me is that nothing about this setup seems scalable.</p>
<p>It’s easy enough to start writing some shared code and use one of the basic methods described above to integrate it into both projects. You can even get pretty far while using some small, simple packages off of NuGet. But eventually you’ll get to a point where you want to use Json.NET or WebSockets or some other thing that needs a fundamentally different solution between .NET and Unity and you’ll hit a wall. You’ll have some piece of core functionality that should be shared between your client and server yet <em>can’t be</em>. And at that point you’ll be close to your ship date and too heavily invested in your current architecture to make major changes, so you’ll hack around the problem and do what you can to get things working.</p>
<p>In some ways, this is a worse situation than being outright incompatible, because it provides the opportunity to start doing something now that is all-but-guaranteed to fail somewhere down the line. It feels like something that should <em>just work</em>; I’m writing the same code in my client and server projects, it seems silly to not be able to share code between the two. But on digging deeper into the situation, it becomes clear that the two have very different code environments: Different compilation processes, different runtime environment, different idiomatic solutions to common problems. The shared language gives the veneer of commonality, where in reality there is an ocean of difference.</p>
<p>It’s worth noting, though, that this assessment is more a gut feeling than a complete analysis. In practice there are no hard blockers to sharing code, just a hundred little things that make it difficult. My experience as a software developer tells me that won’t work out, that you’ll spend more hacking around the problems than is worth it for the convenience of sharing code, that you’ll not be able to use helpful libraries in your server code because they’re not compatible with Unity. But, the only way to really know how bad things are would be to build out a real, large-scale, production-ready project and see what problems you run into.</p>
<p>That all being said, there are potentially ways in which better automation and tooling could improve the situation, maybe even enough to make the effort worthwhile:</p>
<ul>
<li><strong>Build out better support for resolving NuGet dependencies</strong> in a Unity project. It’s already possible to export a packages dependencies as a JSON file and include it in Unity, so a plugin that can fully resolve dependency trees, detect conflict, and import the dependencies into Unity would make interop much smoother. Ideally this would be built directly into UPM, but if it’s not something Unity is willing to do, then it may be possible to make this work as a custom package.</li>
<li><strong>Automatically detect incompatibilities with Unity</strong>. You can use reflection and disassembly to inspect the contents of a .NET DLL. In theory, you could automate the process of detecting if the library uses any language features that don’t work with IL2CPP. I have no idea if this is actually possible to do in a robust way, but it would make a huge difference to be able to detect these issues ahead of time.</li>
</ul>
<p>I’m certainly not the only person interested in making this work, so hopefully someone with more time (and more expertise with the wider .NET world) can make some progress here. Until then, I’ll be investigating other avenues for client/server code sharing and see if I can’t come up with a more satisfying solution.</p>I’ve been doing some investigation into building client/server game architectures with Unity on the front end and a C# server on the back end, specifically pairing an ASP.NET server in a traditional .NET environment with a Unity front-end. For games with no realtime gameplay and a desire for high scalability, as is often the case with online mobile games (my industry, for better or worse), it makes sense to build your server with something other than Unity. Traditional .NET development is attractive in this case because it allows you to use the same programming language, C#, to build both your client and server.Using async/await in Unity2019-10-07T00:00:00+00:002019-10-07T00:00:00+00:00https://randompoison.github.io/posts/unity-async<p>I’ve been investigating the usage of <a href="https://docs.microsoft.com/en-us/dotnet/csharp/async">C#’s async/await functionality</a> in Unity projects, and in doing so I’ve done a number of experiments to determine the nuances of how it works in specific cases<sup id="fnref:coroutines-are-bad" role="doc-noteref"><a href="#fn:coroutines-are-bad" class="footnote" rel="footnote">1</a></sup>. This post attempts to list out and demonstrate these details so that others can better determine if using async/await makes sense for their Unity project.</p>
<p>All of these tests were done with Unity 2019.2.4f1. I can’t guarantee that everything will behave the same on other versions of Unity, and the async/await support was known to be buggy in the 2017/2018 release cycles. You can view the various test scripts I wrote <a href="https://github.com/randomPoison/unity-tests-and-examples/tree/async-await-examples/Assets/Scripts/AsyncTests">on GitHub</a>.</p>
<p>A major caveat here: Some of the details highlighted are general details about task-based async code in C#, but some details are specific to how <a href="https://github.com/Cysharp/UniTask" title="The UniTask package for Unity">UniTask</a> implements task support for Unity. If you choose to use a different implementation of tasks (or not use a custom task implementation at all), some of these details may be wrong.</p>
<h2 id="no-minimum-delay">No Minimum Delay</h2>
<p>One nice advantage of <code class="language-plaintext highlighter-rouge">await</code> is that it doesn’t impose a 1 frame minimum delay the way that <code class="language-plaintext highlighter-rouge">yield return</code> does. This is something that I’ve run into when caching assets in-memory. For example:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">IEnumerator</span> <span class="nf">LoadPrefab</span><span class="p">(</span><span class="n">Action</span><span class="p"><</span><span class="n">GameObject</span><span class="p">></span> <span class="n">callback</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(!</span><span class="n">assetIsLoaded</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">yield</span> <span class="k">return</span> <span class="nf">LoadPrefabIntoCache</span><span class="p">();</span>
<span class="p">}</span>
<span class="kt">var</span> <span class="n">prefab</span> <span class="p">=</span> <span class="nf">GetPrefabFromCache</span><span class="p">();</span>
<span class="nf">callback</span><span class="p">(</span><span class="n">prefab</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Some code that wants to use the coroutine;</span>
<span class="n">GameObject</span> <span class="n">prefab</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
<span class="k">yield</span> <span class="k">return</span> <span class="nf">LoadPrefab</span><span class="p">(</span><span class="n">result</span> <span class="p">=></span> <span class="p">{</span> <span class="n">prefab</span> <span class="p">=</span> <span class="n">result</span><span class="p">;</span> <span class="p">});</span>
</code></pre></div></div>
<p>The first time coroutine runs you’ll have to wait for the prefab to be loaded, however on subsequent calls it would be ideal if the code calling <code class="language-plaintext highlighter-rouge">LoadPrefab()</code> would resume on the same frame. Unfortunately, if you <code class="language-plaintext highlighter-rouge">yield return</code> on a coroutine, the calling code cannot resume until the next frame at the earliest, even if the invoked coroutine completes synchronously. If you need to avoid the 1 frame delay, you have to manually check if the work would complete synchronously:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">GameObject</span> <span class="n">prefab</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">IsPrefabCached</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">prefab</span> <span class="p">=</span> <span class="nf">GetPrefabFromCache</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="k">yield</span> <span class="k">return</span> <span class="nf">LoadPrefab</span><span class="p">(</span><span class="n">result</span> <span class="p">=></span> <span class="p">{</span> <span class="n">prefab</span> <span class="p">=</span> <span class="n">result</span><span class="p">;</span> <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Fortunately, <code class="language-plaintext highlighter-rouge">await</code> doesn’t have this restriction; If you <code class="language-plaintext highlighter-rouge">await</code> a task that completes synchronously, the calling task will also resume synchronously. It’s worth noting, though, that if you <code class="language-plaintext highlighter-rouge">await</code> a task that doesn’t complete synchronously, your task won’t resume until the next frame even if the child task completes within the same frame<sup id="fnref:sub-frame-await" role="doc-noteref"><a href="#fn:sub-frame-await" class="footnote" rel="footnote">2</a></sup>.</p>
<h2 id="execution-order-stays-the-same">Execution Order Stays The Same</h2>
<p>When starting a coroutine, the body of the coroutine will synchronously execute up to the first <code class="language-plaintext highlighter-rouge">yield</code> statement:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">IEnumerator</span> <span class="nf">MyCoroutine</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Beginning of MyCoroutine()"</span><span class="p">);</span>
<span class="k">yield</span> <span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"End of MyCoroutine()"</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// The following test:</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Before coroutine"</span><span class="p">);</span>
<span class="nf">StartCoroutine</span><span class="p">(</span><span class="nf">MyCoroutine</span><span class="p">());</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"After coroutine"</span><span class="p">);</span>
<span class="c1">// Will print the following:</span>
<span class="c1">//</span>
<span class="c1">// Before coroutine</span>
<span class="c1">// Beginning of MyCoroutine()</span>
<span class="c1">// After coroutine</span>
<span class="c1">// End of MyCoroutine()</span>
</code></pre></div></div>
<p>Tasks work the same way, synchronously executing up to the first <code class="language-plaintext highlighter-rouge">await</code> before returning to the calling code:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">async</span> <span class="k">void</span> <span class="nf">VoidTask</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Doing a void task!"</span><span class="p">);</span>
<span class="k">await</span> <span class="n">UniTask</span><span class="p">.</span><span class="nf">Delay</span><span class="p">(</span><span class="m">1000</span><span class="p">);</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Void task resumed!"</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// The following test:</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"About to call VoidTask()"</span><span class="p">);</span>
<span class="nf">VoidTask</span><span class="p">();</span>
<span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"Returned from VoidTask()"</span><span class="p">);</span>
<span class="c1">// Will print the following:</span>
<span class="c1">//</span>
<span class="c1">// About to call VoidTask()</span>
<span class="c1">// Doing a void task!</span>
<span class="c1">// Returned from VoidTask()</span>
<span class="c1">// Void task resumed!</span>
</code></pre></div></div>
<h2 id="starting-and-stopping-tasks">Starting and Stopping Tasks</h2>
<p>There are two major differences between coroutines and tasks that you’ll need to be aware of:</p>
<ul>
<li>Tasks start automatically as soon as you call an <code class="language-plaintext highlighter-rouge">async</code> function, there’s no <code class="language-plaintext highlighter-rouge">StartCoroutine()</code> equivalent for tasks.</li>
<li>Tasks are not tied to a <code class="language-plaintext highlighter-rouge">GameObject</code> the way that coroutines are, and will continue to run even if the the object that spawned them is destroyed.</li>
</ul>
<p>For starting tasks, this change is convenient and removes some of the confusion that made coroutines difficult to work with (e.g. I’ve seen many people not call <code class="language-plaintext highlighter-rouge">StartCoroutine()</code> and then be confused why their coroutine wasn’t running).</p>
<p>By default, a task will end automatically once it runs to completion. However, if you want to be able to cancel a task before it completes you’ll need to <a href="https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-cancel-a-task-and-its-children">use a <code class="language-plaintext highlighter-rouge">CancellationToken</code></a>:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cancellation</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CancellationTokenSource</span><span class="p">();</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="k">await</span> <span class="n">UniTask</span><span class="p">.</span><span class="nf">Delay</span><span class="p">(</span><span class="m">500</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">:</span> <span class="n">cancellation</span><span class="p">.</span><span class="n">Token</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">finally</span>
<span class="p">{</span>
<span class="n">cancellation</span><span class="p">.</span><span class="nf">Dispose</span><span class="p">();</span>
<span class="n">cancellation</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Somewhere else, in reaction to an event that</span>
<span class="c1">// should cancel the pending task:</span>
<span class="n">cancellation</span><span class="p">.</span><span class="nf">Cancel</span><span class="p">();</span>
</code></pre></div></div>
<p>This approach requires a bit more setup than is needed with coroutines (mainly to handle disposing of the <code class="language-plaintext highlighter-rouge">CancellationTokenSource</code> when done), but makes the default behavior more intuitive and gives you better control over when your tasks run.</p>
<h3 id="cancel-task-when-game-object-is-destroyed">Cancel Task When Game Object is Destroyed</h3>
<p>For example, imagine a scenario where you want to load a sprite from an asset bundle and assign it to a sprite renderer on a game object. If the game object is destroyed while waiting for the asset to load, the subsequent attempt to update the sprite renderer will throw an exception:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">sprite</span> <span class="p">=</span> <span class="k">await</span> <span class="n">assetBundle</span><span class="p">.</span><span class="n">LoadAssetAsync</span><span class="p"><</span><span class="n">Sprite</span><span class="p">>();</span>
<span class="c1">// This will throw an exception of the game object was</span>
<span class="c1">// destroyed while waiting for the sprite to load.</span>
<span class="n">spriteRenderer</span><span class="p">.</span><span class="n">sprite</span> <span class="p">=</span> <span class="n">sprite</span><span class="p">;</span>
</code></pre></div></div>
<p>To cancel the task in the case that the game object is destroyed, you can use a <code class="language-plaintext highlighter-rouge">CancellationTokenSource</code> and the <code class="language-plaintext highlighter-rouge">RegisterRaiseCancelOnDestroy()</code> extension method:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">cancellation</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CancellationTokenSource</span><span class="p">();</span>
<span class="n">cancellation</span><span class="p">.</span><span class="nf">RegisterRaiseCancelOnDestroy</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="c1">// This await will never resume if the game object</span>
<span class="c1">// is destroyed.</span>
<span class="kt">var</span> <span class="n">sprite</span> <span class="p">=</span> <span class="k">await</span> <span class="n">assetBundle</span>
<span class="p">.</span><span class="n">LoadAssetAsync</span><span class="p"><</span><span class="n">Sprite</span><span class="p">>()</span>
<span class="p">.</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="n">cancellationToken</span><span class="p">:</span> <span class="n">cancellation</span><span class="p">.</span><span class="n">Token</span><span class="p">);</span>
<span class="n">spriteRenderer</span><span class="p">.</span><span class="n">sprite</span> <span class="p">=</span> <span class="n">sprite</span><span class="p">;</span>
</code></pre></div></div>
<h3 id="perform-cleanup-when-task-is-cancelled">Perform Cleanup When Task is Cancelled</h3>
<p>One of the problems with coroutines is that there’s no way to detect if a coroutine has been cancelled, making it effectively impossible to perform cleanup or consistently maintain invariants in the face of arbitrary coroutine cancellation. Fortunately, with async/await you can use <code class="language-plaintext highlighter-rouge">try</code> blocks to perform any final cleanup logic the same way you would anywhere else:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span>
<span class="p">{</span>
<span class="k">await</span> <span class="nf">SomeAsyncOperation</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">finally</span>
<span class="p">{</span>
<span class="c1">// This logic will be run, even if the task</span>
<span class="c1">// is cancelled.</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This works with task cancellation as well, since cancellation is done by throwing an <a href="https://docs.microsoft.com/en-us/dotnet/api/system.operationcanceledexception"><code class="language-plaintext highlighter-rouge">OperationCancelledException</code></a>. This also means that it’s possible to run logic only in the case that the task was cancelled while letting exceptions propagate as normal:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>try
{
await SomeAsyncOperation();
}
catch (OperationCancelledException)
{
// This logic only runs if the task was
// cancelled. Be sure to re-throw the
// exception so that any parent tasks are
// cancelled as well.
throw;
}
</code></pre></div></div>
<h2 id="exceptions-and-callstacks">Exceptions and Callstacks</h2>
<p>With coroutines, each one behaves as its own top-level “stack”, meaning that callstacks from within a coroutine don’t show where the coroutine was spawned from. This also applies to exceptions, which don’t unwind through a hierarchy of coroutines, making exceptions thrown in coroutines non-intuitive. This makes debugging errors in coroutines often very difficult, as you lose all context for logging and exceptions from within a coroutine. Tasks, on the other hand, are a first-class part of the language and so handle these cases much better.</p>
<p>Exceptions behave the same as with synchronous code, unwinding the stack through tasks and providing a trace of the path it followed. For example:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">ThrowRecursive</span><span class="p">(</span><span class="kt">int</span> <span class="n">depth</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">depth</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">Exception</span><span class="p">(</span><span class="s">"Exception thrown from deep in the call stack"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="k">await</span> <span class="nf">ThrowRecursive</span><span class="p">(</span><span class="n">depth</span> <span class="p">-</span> <span class="m">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Calling this as <code class="language-plaintext highlighter-rouge">await ThrowRecursive(3)</code> shows the following in the console:</p>
<p><img src="../../assets/unity-async-exception-log.png" alt="Exception stack trace showing up in the Unity editor logs." /></p>
<p>You can see the full stack of calls, from the top-level function that called <code class="language-plaintext highlighter-rouge">ThrowRecursive()</code> down through the multiple <code class="language-plaintext highlighter-rouge">await</code> statements. This also applies to any call stacks generated, including the ones included in debug logging. This makes debugging with async/await far easier than with coroutines.</p>
<h2 id="compatibility-with-coroutines">Compatibility with Coroutines</h2>
<p>UniTask provides functionality for awaiting a coroutine within tasks and for yielding on tasks within coroutines. Using <code class="language-plaintext highlighter-rouge">await</code> with an <code class="language-plaintext highlighter-rouge">IEnumerator</code> an <code class="language-plaintext highlighter-rouge">AsyncOperation</code> or a <code class="language-plaintext highlighter-rouge">YieldInstruction</code> will work without issue. If you want to include a cancellation token or otherwise configure how the task will wait for the coroutine, you can use the <code class="language-plaintext highlighter-rouge">ConfigureAwait()</code> helper method:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">await</span> <span class="n">Resources</span><span class="p">.</span><span class="nf">Load</span><span class="p">(</span><span class="s">"MyAsset"</span><span class="p">).</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="n">cancellationToken</span><span class="p">:</span> <span class="n">token</span><span class="p">);</span>
</code></pre></div></div>
<p>To <code class="language-plaintext highlighter-rouge">yield return</code> a <code class="language-plaintext highlighter-rouge">Task</code> or <code class="language-plaintext highlighter-rouge">UniTask</code> you must use the <code class="language-plaintext highlighter-rouge">ToCoroutine()</code> extension method:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">yield</span> <span class="k">return</span> <span class="nf">MyTask</span><span class="p">().</span><span class="nf">ToCoroutine</span><span class="p">();</span>
</code></pre></div></div>
<h2 id="debugging">Debugging</h2>
<p>When debugging with Visual Studio, you can step over <code class="language-plaintext highlighter-rouge">await</code> statements in the debugger and it will step to the next line!<sup id="fnref:step-over-await-crash" role="doc-noteref"><a href="#fn:step-over-await-crash" class="footnote" rel="footnote">3</a></sup> This is because tasks are a first-class part of the language, so the debugger is able to track them directly and better determine when to break in the debugger. Coroutines, on the other hand, aren’t really a part of the language and are a hacky misuse of <a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/yield">C# iterators</a>, and so the debugger can’t “see through” them the way it can with tasks.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:coroutines-are-bad" role="doc-endnote">
<p>Years of experience with coroutines and their <em>nuances</em> have made me very wary of all the ways that Unity can surprise you when it comes to async code. <a href="#fnref:coroutines-are-bad" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:sub-frame-await" role="doc-endnote">
<p>In theory, it would be possible to resume a task within the same frame by having it resume at a later part of the update loop, e.g. <code class="language-plaintext highlighter-rouge">await</code> during the main update and then resume during <code class="language-plaintext highlighter-rouge">LateUpdate</code>. However, this is probably not currently supported by UniTask and would probably not be something that is generally useful. <a href="#fnref:sub-frame-await" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:step-over-await-crash" role="doc-endnote">
<p>There’s <a href="https://github.com/Cysharp/UniTask/issues/41">currently a bug</a> with the Unity editor and <code class="language-plaintext highlighter-rouge">UniTask</code> that is causing the editor to crash when stepping over <code class="language-plaintext highlighter-rouge">await</code> in an <code class="language-plaintext highlighter-rouge">async UniTask</code> function. This shouldn’t be an issue if you’re using <code class="language-plaintext highlighter-rouge">async Task</code>, though. <a href="#fnref:step-over-await-crash" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>I’ve been investigating the usage of C#’s async/await functionality in Unity projects, and in doing so I’ve done a number of experiments to determine the nuances of how it works in specific cases1. This post attempts to list out and demonstrate these details so that others can better determine if using async/await makes sense for their Unity project. Years of experience with coroutines and their nuances have made me very wary of all the ways that Unity can surprise you when it comes to async code. ↩The Brilliance of SpatialOS’s Modular Inspector2019-03-29T00:00:00+00:002019-03-29T00:00:00+00:00https://randompoison.github.io/posts/spatialos-inspector<p>Improbable, the developers of SpatialOS, recently released an <a href="https://docs.improbable.io/reference/13.6/shared/operate/modular-inspector">alpha preview of their new Modular Inspector</a>, and boy is this thing :fire: HOT :fire:. When I first saw the demo videos, I was blown away by the sheer ingenuity of the design. In particular, I’m impressed by how well the new inspector is designed to give the user the power to wrangle complex worlds and see exactly what information they care about.</p>
<p>Since I’m part of the ongoing process to put together an <a href="https://github.com/amethyst/amethyst-editor">editor for Amethyst</a>, I want to go into some deeper detail about why this new inspector is so brilliant. ECS is starting to become a more widely-recognized paradigm in the gamedev community, but I think there’s a lot of confusion around how you make ECS as comprehensible and easy to use as the existing Object Oriented paradigms. I think the SpatialOS Modular Inspector is the first graphical tool to really demonstrate how this is possible.</p>
<h2 id="queries-not-hierarchies">Queries, Not Hierarchies</h2>
<p>The biggest thing that the Modular Inspector does is replace the traditional hierarchy view that’s common in game editors with a tool for building dynamic queries over the contents of your world.</p>
<p><img src="https://commondatastorage.googleapis.com/improbable-docs/docs2/reference/66d91c228763d67c/assets/shared/operate/inspector/dynamic-constraint-in-inspector.png" alt="The Query Editor module in the Modular Inspector" /></p>
<p>In most editors that come with game engines, the default scene view is a nested hierarchy based on parent/child relationships between objects. For example, this is the Hierarchy view in the PlayCanvas editor:</p>
<p><img src="https://developer.playcanvas.com/images/user-manual/editor/hierarchy.png" alt="The Hierarchy view in the PlayCanvas editor" /></p>
<p>This is also the provided scene view in Unity (in the Hierarchy view) and Unreal (in the World Outliner). While this view is often comfortable and familiar for game developers, it maps poorly to ECS, where the vast majority of your game logic is oriented around flat lists of entities, grouped by which components they have. As such, the Query Editor in the SpatialOS inspector is a brilliant paradigm shift because it means that the graphical inspector for you game operates on the same logic that your systems (or workers, in the case of SpatialOS) do: It queries the world with a set of constraints (usually the presence or absence of certain component types) and gets back a flat list of all entities matching the specified criteria.</p>
<h2 id="modular-composable-tools">Modular, Composable Tools</h2>
<p>The Query Editor itself doesn’t actually show the results of the query, though. Instead, the query’s output is piped into one or more other modules that are used to visualize the entity data. This highlights the next big thing that the SpatialOS inspector does well: It builds upon a modular set of tools that can be composed by piping the output of one module into another.</p>
<p><img src="../../assets/spatialos-inspector-input-output.png" alt="Inputs and outputs between modules in the SpatialOS inspector" /></p>
<p>This is brilliant for two reasons:</p>
<ul>
<li>It’s a truly modular approach to constructing your UI. Each module takes specific inputs to configure it, but doesn’t specify where those configuration values need to come from. As such, you can manually set values yourself, use the output from another module, use values set on components/workers, or any other source of data that the inspector supports. While the SpatialOS inspector isn’t itself extensible, one can easily imagine this same model working well with custom extensions and plugins, where user-defined plugins can easily communicate through data inputs and outputs.</li>
<li>It puts the emphasis on data and how it flows through the components of your UI. Data flow is a big part of the ECS paradigm, and modeling our UI tools around data flows means that they fit more naturally with our intuition of how our games work.</li>
</ul>
<p>The SpatialOS team clearly took inspiration from visual scripting tools here, where this model of nodes connected by data inputs and outputs is common. However, this is the first time I’ve seen the paradigm applied to constructing custom UI configurations, and I’m impressed at how well the paradigm works in the context of making a customizable UI.</p>
<h2 id="different-visualizations-for-different-situations">Different Visualizations for Different Situations</h2>
<p><img src="https://commondatastorage.googleapis.com/improbable-docs/docs2/reference/66d91c228763d67c/assets/shared/operate/inspector/query-editor-module.png" alt="The different visualization tools available in the Modular Inspector" /></p>
<p>The Modular Inspector also provides different customizable ways of visualizing component data on the entities in your query. The Viewport provides a 2D visualization of the positions and movement of entities in a specified area, and the Entity Table provides a direct look at specific fields of the components attached to those entities. Both of these modules can be customized: You can tweak the colors and icons used for different entity types in the Viewport, and you can select which components and fields are displayed in the Entity Table. Combined with the fact that you can have multiples of each, all driven by different queries, these tools provide you tons of control over how you visualize the data in your world.</p>
<h2 id="it-looks-really-good">It Looks Really Good</h2>
<p>I mean damn, just look at it :eyes:</p>
<h2 id="applying-this-to-other-engineseditors">Applying This to Other Engines/Editors</h2>
<p>It’s worth noting that the tools that Improbable has put together are clearly focused on one thing: Debugging ECS worlds. These tools have little use when it comes to authoring content for your game, but are incredibly useful when it comes time to verify that your world simulation is doing what you expect. As such, I think there’s a lot we can learn from these tools in order to build better debugging tools for Amethyst and other ECS-based game engines.</p>Improbable, the developers of SpatialOS, recently released an alpha preview of their new Modular Inspector, and boy is this thing :fire: HOT :fire:. When I first saw the demo videos, I was blown away by the sheer ingenuity of the design. In particular, I’m impressed by how well the new inspector is designed to give the user the power to wrangle complex worlds and see exactly what information they care about.Chaining Functions Without Returning Self2019-03-21T00:00:00+00:002019-03-21T00:00:00+00:00https://randompoison.github.io/posts/returning-self<!-- markdownlint-disable MD002 -->
<p>It’s a common pattern in the Rust ecosystem to have a function return <code class="language-plaintext highlighter-rouge">self</code> at the end in order to enable method chaining. For example:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// Create, modify, and consume a `Foo` in a single expression.</span>
<span class="c">// So concise! Much ergonomic! Wow!</span>
<span class="nf">consume</span><span class="p">(</span>
<span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span>
<span class="nf">.chain</span><span class="p">()</span>
<span class="nf">.chain</span><span class="p">()</span>
<span class="nf">.chain</span><span class="p">()</span>
<span class="nf">.chain</span><span class="p">()</span>
<span class="p">);</span>
<span class="c">// Method definitions that make this possible:</span>
<span class="c">// -------------------------------------------</span>
<span class="nd">#[derive(Default)]</span>
<span class="k">struct</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="c">// Some internal state.</span>
<span class="p">}</span>
<span class="k">impl</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="k">fn</span> <span class="nf">chain</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-></span> <span class="n">Self</span> <span class="p">{</span>
<span class="c">// Make some changes to `self`, then return `self`.</span>
<span class="k">self</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">fn</span> <span class="nf">consume</span><span class="p">(</span><span class="n">foo</span><span class="p">:</span> <span class="n">Foo</span><span class="p">)</span> <span class="p">{</span>
<span class="c">// Do something with the final `Foo` after it's</span>
<span class="c">// been fully initialized and configured.</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=474c0928fe2620c4a889378f46558336">Run in the Playground</a></p>
</blockquote>
<p>This approach is often used in combination with the <a href="https://github.com/rust-unofficial/patterns/blob/master/patterns/builder.md">builder pattern</a>, though it can also be applied to a wide variety of other situations. The above example demonstrates the most straightforward of these cases (i.e. initializing and modifying an object in a single statement), but, as I’m going to demonstrate, this approach quickly breaks down when applied to a wider variety of use cases.</p>
<p>In this post, I intend to cover the following points:</p>
<ul>
<li>Returning <code class="language-plaintext highlighter-rouge">self</code> is not an effective way of achieving method chaining in Rust.</li>
<li>Method and function chaining should be orthogonal to the return type of a function.</li>
<li>You should only return <code class="language-plaintext highlighter-rouge">self</code> from a function if it’s semantically meaningful to do so.</li>
<li>Method cascades provide a promising alternative to returning <code class="language-plaintext highlighter-rouge">self</code> when you want method chaining.</li>
</ul>
<h2 id="chaining-by-returning-self">Chaining by Returning <code class="language-plaintext highlighter-rouge">self</code></h2>
<p>Since returning <code class="language-plaintext highlighter-rouge">self</code> is currently the de facto way of enabling method chaining in the Rust ecosystem, I’m going to start by demonstrating that doing so doesn’t work as well as we would like. To show this, we’re going to work with the following definitions:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// Define a struct `Foo` with some internal state that its methods</span>
<span class="c">// will modify. We'll use `Foo::default()` throughout the examples</span>
<span class="c">// to create the initial instance of the data.</span>
<span class="nd">#[derive(Debug,</span> <span class="nd">Default)]</span>
<span class="k">struct</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="n">value</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">impl</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="c">// Define a method that can be chained by taking and returning</span>
<span class="c">// ownership of the data.</span>
<span class="k">fn</span> <span class="nf">chain_move</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="k">-></span> <span class="n">Self</span> <span class="p">{</span>
<span class="k">self</span><span class="py">.value</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">self</span>
<span class="p">}</span>
<span class="c">// Define a method that can be chained on a borrow of the data.</span>
<span class="k">fn</span> <span class="nf">chain_ref</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="k">-></span> <span class="o">&</span><span class="k">mut</span> <span class="n">Self</span> <span class="p">{</span>
<span class="k">self</span><span class="py">.value</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">self</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c">// Define a function that will consume the final data by taking</span>
<span class="c">// ownership of it.</span>
<span class="k">fn</span> <span class="nf">consume_move</span><span class="p">(</span><span class="n">foo</span><span class="p">:</span> <span class="n">Foo</span><span class="p">)</span> <span class="p">{</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}"</span><span class="p">,</span> <span class="n">foo</span><span class="p">);</span>
<span class="p">}</span>
<span class="c">// Define a function that will consume the final data by borrowing it.</span>
<span class="k">fn</span> <span class="nf">consume_ref</span><span class="p">(</span><span class="n">foo</span><span class="p">:</span> <span class="o">&</span><span class="n">Foo</span><span class="p">)</span> <span class="p">{</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}"</span><span class="p">,</span> <span class="n">foo</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In the examples I will also sometimes use an imaginary method <code class="language-plaintext highlighter-rouge">chain</code> to demonstrate an idealized way of performing method chaining. This will be used to show the “ideal” use case (i.e. the most ergonomic way of applying method chaining in a given situation) so as to compare how <code class="language-plaintext highlighter-rouge">chain_ref</code> and <code class="language-plaintext highlighter-rouge">chain_move</code> work in practice.</p>
<p>Let’s now take a look at each of the use cases we would like to support, and see how they work with each of the method chaining approaches.</p>
<h3 id="single-method-chain">Single Method Chain</h3>
<p>The most basic case is having a single long method chain, from construction into the consumption of your type:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">consume</span><span class="p">(</span><span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span><span class="nf">.chain</span><span class="p">()</span><span class="nf">.chain</span><span class="p">());</span>
</code></pre></div></div>
<p>This works reasonably well with both <code class="language-plaintext highlighter-rouge">chain_move</code> and <code class="language-plaintext highlighter-rouge">chain_ref</code> so long as you match the chaining style with the consumer:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">consume_move</span><span class="p">(</span><span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span><span class="nf">.chain_move</span><span class="p">()</span><span class="nf">.chain_move</span><span class="p">());</span>
<span class="nf">consume_ref</span><span class="p">(</span><span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span><span class="nf">.chain_ref</span><span class="p">()</span><span class="nf">.chain_ref</span><span class="p">());</span>
</code></pre></div></div>
<blockquote>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9402739e03bf725420ad70a0283ac943">Run in the Playground</a></p>
</blockquote>
<p>Note, though, that while the <code class="language-plaintext highlighter-rouge">chain_move</code> version works with both <code class="language-plaintext highlighter-rouge">consume_move</code> and <code class="language-plaintext highlighter-rouge">consume_ref</code> version, <code class="language-plaintext highlighter-rouge">chain_ref</code> can only be used with <code class="language-plaintext highlighter-rouge">consume_ref</code>. If we try to pass the result of <code class="language-plaintext highlighter-rouge">chain_ref</code> into <code class="language-plaintext highlighter-rouge">consume_move</code>, we get this error:</p>
<pre><code class="language-txt">error[E0308]: mismatched types
--> src/main.rs:9:18
|
9 | consume_move(Foo::default().chain_ref().chain_ref()); // Doesn't compile.
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Foo`, found &mut Foo
|
= note: expected type `Foo`
found type `&mut Foo`
</code></pre>
<blockquote>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=876fb51a394c47b29a4baa1aefd6f822">Run in the Playground</a></p>
</blockquote>
<p>Since <code class="language-plaintext highlighter-rouge">chain_ref()</code> returns a <code class="language-plaintext highlighter-rouge">&mut Foo</code>, we can’t use it anywhere a <code class="language-plaintext highlighter-rouge">Foo</code> is expected (though there are ways of working around this, which will be covered below).</p>
<h3 id="use-before-consuming">Use Before Consuming</h3>
<p>Let’s say you needed to log the value before consuming it. The most intuitive way of doing this would be to directly bind the result of the chain to a variable, log the variable, then pass the variable to the consume method:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">foo</span> <span class="o">=</span> <span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span><span class="nf">.chain</span><span class="p">()</span><span class="nf">.chain</span><span class="p">()</span><span class="nf">.chain</span><span class="p">();</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"foo: {:?}"</span><span class="p">,</span> <span class="n">foo</span><span class="p">);</span>
<span class="nf">consume_ref</span><span class="p">(</span><span class="o">&</span><span class="n">foo</span><span class="p">);</span>
<span class="nf">consome_move</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
</code></pre></div></div>
<p>This can be done directly with <code class="language-plaintext highlighter-rouge">chain_move</code>:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">foo</span> <span class="o">=</span> <span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span><span class="nf">.chain_move</span><span class="p">()</span><span class="nf">.chain_move</span><span class="p">();</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"foo: {:?}"</span><span class="p">,</span> <span class="n">foo</span><span class="p">);</span>
<span class="nf">consume_ref</span><span class="p">(</span><span class="o">&</span><span class="n">foo</span><span class="p">);</span>
<span class="nf">consume_move</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
</code></pre></div></div>
<blockquote>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c3dad670cc24c7d710fc2cc4278c691c">Run in the Playground</a></p>
</blockquote>
<p>But doing the same thing with <code class="language-plaintext highlighter-rouge">chain_ref</code> won’t compile:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">foo</span> <span class="o">=</span> <span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span><span class="nf">.chain_ref</span><span class="p">()</span><span class="nf">.chain_ref</span><span class="p">();</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"foo: {:?}"</span><span class="p">,</span> <span class="n">foo</span><span class="p">);</span>
<span class="nf">consume_ref</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
</code></pre></div></div>
<pre><code class="language-txt">error[E0716]: temporary value dropped while borrowed
--> src/main.rs:2:15
|
2 | let foo = Foo::default().chain_ref().chain_ref();
| ^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
3 | println!("foo: {:?}", foo);
| --- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
</code></pre>
<blockquote>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fcf0b2d4b3422d5151fde27df66637ae">Run in the Playground</a></p>
</blockquote>
<p>As the compiler helpfully notes, the temporary value created by <code class="language-plaintext highlighter-rouge">Foo::default()</code> is dropped at the end of the chain sequence, so we can’t bind it to a variable. Instead, we must first create the initial <code class="language-plaintext highlighter-rouge">Foo</code> and bind it to a mutable variable. Once that’s done, we are able to use <code class="language-plaintext highlighter-rouge">chain_ref</code> to apply modifications to it before logging and consuming the final value:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="k">mut</span> <span class="n">foo</span> <span class="o">=</span> <span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span>
<span class="n">foo</span><span class="nf">.chain_ref</span><span class="p">()</span><span class="nf">.chain_ref</span><span class="p">();</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"foo: {:?}"</span><span class="p">,</span> <span class="n">foo</span><span class="p">);</span>
<span class="nf">consume_ref</span><span class="p">(</span><span class="o">&</span><span class="n">foo</span><span class="p">);</span>
<span class="nf">consume_move</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
</code></pre></div></div>
<blockquote>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=234824151a664395c788cac897c49a9e">Run in the Playground</a></p>
</blockquote>
<p>Note that you must also use this approach to use <code class="language-plaintext highlighter-rouge">chain_ref</code> in combination with <code class="language-plaintext highlighter-rouge">consume_move</code>; By binding the initial <code class="language-plaintext highlighter-rouge">Foo</code> to a variable, you avoid the issue of it being a temporary value and being dropped too early.</p>
<p>While this is functional, it has a few drawbacks as compared to the <code class="language-plaintext highlighter-rouge">chain_move</code> version:</p>
<ul>
<li>You can no longer create and modify the value in a single expression.</li>
<li>You have to bind <code class="language-plaintext highlighter-rouge">foo</code> as a mutable variable, which loosens some of the guarantees you get in the <code class="language-plaintext highlighter-rouge">chain_move</code> version when binding the variable immutably.</li>
<li>When converting from the single chain version to this version, it’s easy to initially apply the naïve transformation shown above and get tripped up when it doesn’t work. The <code class="language-plaintext highlighter-rouge">chain_move</code> version, on the other hand, works fine with the naïve transformation.</li>
</ul>
<p>For this case, both <code class="language-plaintext highlighter-rouge">chain_ref</code> and <code class="language-plaintext highlighter-rouge">chain_move</code> work equally well with <code class="language-plaintext highlighter-rouge">consume_ref</code> and <code class="language-plaintext highlighter-rouge">consume_move</code> since, once the object is bound to a variable, it is easy to either lend that value to another function or to transfer ownership entirely.</p>
<h3 id="modifying-an-owned-value">Modifying an Owned Value</h3>
<p>Now let’s say that you want want perform an initial method chain, then conditionally apply another chain of operations to the same object. This means that we already have a bound, mutable variable that we would like to modify in the same method-chaining style that we use to create the object. The ideal version of this would be as follows:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="k">mut</span> <span class="n">foo</span> <span class="o">=</span> <span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span><span class="nf">.chain</span><span class="p">()</span><span class="nf">.chain</span><span class="p">()</span><span class="nf">.chain</span><span class="p">();</span>
<span class="k">if</span> <span class="n">some_condition</span> <span class="p">{</span>
<span class="n">foo</span><span class="nf">.chain</span><span class="p">()</span><span class="nf">.chain</span><span class="p">()</span><span class="nf">.chain</span><span class="p">();</span>
<span class="p">}</span>
<span class="nf">consume_ref</span><span class="p">(</span><span class="o">&</span><span class="n">foo</span><span class="p">);</span>
<span class="nf">consume_move</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
</code></pre></div></div>
<p>In this case, the <code class="language-plaintext highlighter-rouge">chain_ref</code> version performs reasonably well (though you again need to first bind the variable before performing the initial chain of modifications):</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="k">mut</span> <span class="n">foo</span> <span class="o">=</span> <span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span>
<span class="n">foo</span><span class="nf">.chain_ref</span><span class="p">()</span><span class="nf">.chain_ref</span><span class="p">()</span><span class="nf">.chain_ref</span><span class="p">();</span>
<span class="k">if</span> <span class="n">some_condition</span> <span class="p">{</span>
<span class="n">foo</span><span class="nf">.chain_ref</span><span class="p">()</span><span class="nf">.chain_ref</span><span class="p">()</span><span class="nf">.chain_ref</span><span class="p">();</span>
<span class="p">}</span>
<span class="nf">consume_ref</span><span class="p">(</span><span class="o">&</span><span class="n">foo</span><span class="p">);</span>
<span class="nf">consume_move</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
</code></pre></div></div>
<blockquote>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=80f4b820c323958a1a39c5bd0ae3e3ac">Run in the Playground</a></p>
</blockquote>
<p>Doing the same with <code class="language-plaintext highlighter-rouge">chain_move</code> can also be made to work, though it requires the value to be rebound <em>after</em> the conditional chain:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">foo</span> <span class="o">=</span> <span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span>
<span class="k">let</span> <span class="n">foo</span> <span class="o">=</span> <span class="k">if</span> <span class="n">some_condition</span> <span class="p">{</span>
<span class="n">foo</span><span class="nf">.chain_move</span><span class="p">()</span><span class="nf">.chain_move</span><span class="p">()</span><span class="nf">.chain_move</span><span class="p">();</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">foo</span>
<span class="p">};</span>
<span class="nf">consume_ref</span><span class="p">(</span><span class="o">&</span><span class="n">foo</span><span class="p">);</span>
<span class="nf">consume_move</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
</code></pre></div></div>
<blockquote>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b2ca33b23cbbabad739be513639001f">Run in the Playground</a></p>
</blockquote>
<p>Again, this is functional but somewhat awkward to construct (having the extra <code class="language-plaintext highlighter-rouge">else</code> branch only to satisfy the borrow checker) and not necessarily an obvious construction for someone who’s not already familiar with the details of Rust’s ownership rules.</p>
<p>In this case, both <code class="language-plaintext highlighter-rouge">chain_ref</code> and <code class="language-plaintext highlighter-rouge">chain_move</code> work, but both have ergonomic drawbacks as compared to the ideal version.</p>
<h3 id="chaining-within-a-function">Chaining Within a Function</h3>
<p>Let’s say you want to break some of your logic into a separate function. This is possible with both functions, thought the signature of your helper function will have to change depending on which chaining approach you are using:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">do_modifications_ref</span><span class="p">(</span><span class="n">foo</span><span class="p">:</span> <span class="o">&</span><span class="k">mut</span> <span class="n">Foo</span><span class="p">)</span> <span class="p">{</span>
<span class="n">foo</span><span class="nf">.chain_ref</span><span class="p">()</span><span class="nf">.chain_ref</span><span class="p">()</span><span class="nf">.chain_ref</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">do_modifications_move</span><span class="p">(</span><span class="n">foo</span><span class="p">:</span> <span class="n">Foo</span><span class="p">)</span> <span class="k">-></span> <span class="n">Foo</span> <span class="p">{</span>
<span class="n">foo</span><span class="nf">.chain_move</span><span class="p">()</span><span class="nf">.chain_move</span><span class="p">()</span><span class="nf">.chain_move</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It’s worth noting that you can still use <code class="language-plaintext highlighter-rouge">chain_ref</code> within <code class="language-plaintext highlighter-rouge">do_modifications_move</code>, but you can’t use <code class="language-plaintext highlighter-rouge">chain_move</code> within <code class="language-plaintext highlighter-rouge">do_modifications_ref</code> (since you can’t take ownership of <code class="language-plaintext highlighter-rouge">foo</code>).</p>
<h3 id="no-chaining-at-all">No Chaining At All</h3>
<p>Let’s say you’re a boring person and don’t want to use method chaining at all, plain-old method calls are enough for you. If that’s the case, the <code class="language-plaintext highlighter-rouge">chain_ref</code> version can also be used to modify the value without chaining, e.g.:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="k">mut</span> <span class="n">foo</span> <span class="o">=</span> <span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span>
<span class="n">foo</span><span class="nf">.chain_ref</span><span class="p">();</span>
<span class="n">foo</span><span class="nf">.chain_ref</span><span class="p">();</span>
<span class="n">foo</span><span class="nf">.chain_ref</span><span class="p">();</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">chain_move</code> version can technically be used without chaining, but requires the variable to be re-bound in each statement, again making the code both harder to read and harder to write:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">foo</span> <span class="o">=</span> <span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span>
<span class="k">let</span> <span class="n">foo</span> <span class="o">=</span> <span class="n">foo</span><span class="nf">.chain_move</span><span class="p">();</span>
<span class="k">let</span> <span class="n">foo</span> <span class="o">=</span> <span class="n">foo</span><span class="nf">.chain_move</span><span class="p">();</span>
<span class="k">let</span> <span class="n">foo</span> <span class="o">=</span> <span class="n">foo</span><span class="nf">.chain_move</span><span class="p">();</span>
</code></pre></div></div>
<h3 id="a-real-world-example">A Real-World Example</h3>
<p>In the abstract, this may seem like a number of minor issues and trivial complaints. To provide a real-world example of the implications these drawbacks have, let’s look at an example that I ran into (one that motivated my writing this article).</p>
<p>Say you’re writing a tool that uses <a href="https://doc.rust-lang.org/std/process/struct.Command.html">std::process::Command</a> to spawn a child process. <code class="language-plaintext highlighter-rouge">Command</code> is designed to be used via method chaining by having all its methods take and then return <code class="language-plaintext highlighter-rouge">&mut self</code>. Your initial version looks something like this:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">result</span> <span class="o">=</span> <span class="nn">Command</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"foo"</span><span class="p">)</span>
<span class="nf">.arg</span><span class="p">(</span><span class="s">"--bar"</span><span class="p">)</span>
<span class="nf">.arg</span><span class="p">(</span><span class="s">"--baz"</span><span class="p">)</span>
<span class="nf">.arg</span><span class="p">(</span><span class="s">"quux"</span><span class="p">)</span>
<span class="nf">.status</span><span class="p">()</span>
<span class="nf">.unwrap</span><span class="p">();</span>
</code></pre></div></div>
<blockquote>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5124c4f3041bd78893e58fcdc815f0d6">Run in the Playground</a></p>
</blockquote>
<p>At some point later, you realize that you want to only pass the <code class="language-plaintext highlighter-rouge">--baz</code> flag conditionally, so you make the obvious changes to your code:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">command</span> <span class="o">=</span> <span class="nn">Command</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"foo"</span><span class="p">)</span>
<span class="nf">.arg</span><span class="p">(</span><span class="s">"--bar"</span><span class="p">);</span>
<span class="k">if</span> <span class="n">set_baz</span> <span class="p">{</span>
<span class="n">command</span><span class="nf">.arg</span><span class="p">(</span><span class="s">"--baz"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">let</span> <span class="n">result</span> <span class="o">=</span> <span class="n">command</span>
<span class="nf">.arg</span><span class="p">(</span><span class="s">"quux"</span><span class="p">)</span>
<span class="nf">.status</span><span class="p">()</span>
<span class="nf">.unwrap</span><span class="p">();</span>
</code></pre></div></div>
<blockquote>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=179416e54846de7d564b465ff11b6e92">Run in the Playground</a></p>
</blockquote>
<p>But it doesn’t compile, because you can’t bind the result of the initial method chain when the chaining methods return <code class="language-plaintext highlighter-rouge">&mut Self</code> (<a href="https://doc.rust-lang.org/std/process/struct.Command.html#method.arg">as <code class="language-plaintext highlighter-rouge">Command::arg</code> does</a>). To get it to work, you have to bind the result of <code class="language-plaintext highlighter-rouge">Command::new</code> to a variable, then perform all configuration on it:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="k">mut</span> <span class="n">command</span> <span class="o">=</span> <span class="nn">Command</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"foo"</span><span class="p">);</span>
<span class="n">command</span><span class="nf">.arg</span><span class="p">(</span><span class="s">"--bar"</span><span class="p">);</span>
<span class="k">if</span> <span class="n">set_baz</span> <span class="p">{</span>
<span class="n">command</span><span class="nf">.arg</span><span class="p">(</span><span class="s">"--baz"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">let</span> <span class="n">result</span> <span class="o">=</span> <span class="n">command</span>
<span class="nf">.arg</span><span class="p">(</span><span class="s">"quux"</span><span class="p">)</span>
<span class="nf">.status</span><span class="p">()</span>
<span class="nf">.unwrap</span><span class="p">();</span>
</code></pre></div></div>
<blockquote>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=77df0254b3abde92ae409f259d545bec">Run in the Playground</a></p>
</blockquote>
<p>This is a minor bit of friction for someone already familiar with Rust, but it can be a frustrating (and unnecessary) roadblock for someone new to the language.</p>
<h2 id="method-chaining-is-orthogonal-to-return-value">Method Chaining is Orthogonal to Return Value</h2>
<p>At this point, I feel comfortable in having demonstrated that returning <code class="language-plaintext highlighter-rouge">self</code> is, at best, an awkward way of implementing method chaining for a Rust type. Beyond that, though, it’s worth asking a more fundamental question: <em>Should</em> it work better? Should we consider this a failing of Rust, that the language doesn’t play well with method chaining? Or is there something fundamentally wrong with this form of method chaining?</p>
<p>To answer these questions, let’s take a look at the function signature for <code class="language-plaintext highlighter-rouge">chain_ref</code>:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">chain_ref</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="k">-></span> <span class="o">&</span><span class="k">mut</span> <span class="n">Self</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
</code></pre></div></div>
<p>Rust’s type system allows us learn a lot about what a function can do solely based on its signature. Key here is that <code class="language-plaintext highlighter-rouge">chain_ref</code> only takes a single parameter: <code class="language-plaintext highlighter-rouge">&mut self</code>. We therefore know that it can (and almost certainly will) mutate <code class="language-plaintext highlighter-rouge">self</code> in some way. We also know that it is probably pure relative to <code class="language-plaintext highlighter-rouge">self</code>, such that the same value for <code class="language-plaintext highlighter-rouge">self</code> will produce the same mutation, since <code class="language-plaintext highlighter-rouge">chain_ref</code> takes no other parameters to influence its behavior.</p>
<p>But what does returning <code class="language-plaintext highlighter-rouge">&mut Self</code> tell us about <code class="language-plaintext highlighter-rouge">chain_ref</code>? Normally, the return type would tell us what the result of the operation is. But in this case, the returned value actually has nothing to do with the internal logic of <code class="language-plaintext highlighter-rouge">chain_ref</code>, it’s only there to enable method chaining, which is completely orthogonal to <code class="language-plaintext highlighter-rouge">chain_ref</code> itself.</p>
<p>This becomes especially problematic if your function has an actual return value. Take <a href="https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.insert"><code class="language-plaintext highlighter-rouge">HashMap::insert</code></a> as an example. <code class="language-plaintext highlighter-rouge">insert</code> returns the previous value if one was replaced, however it’s not always necessary to check the return value. In some cases, I may want to insert many elements into a hash map, in which case using a method chain would be clear and concise:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">map</span> <span class="o">=</span> <span class="nn">HashMap</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
<span class="nf">.insert</span><span class="p">(</span><span class="s">"foo"</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="nf">.insert</span><span class="p">(</span><span class="s">"bar"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="nf">.insert</span><span class="p">(</span><span class="s">"baz"</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
<span class="nf">.insert</span><span class="p">(</span><span class="s">"quux"</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span>
</code></pre></div></div>
<p>But there’s no way to make this work while still returning a value from <code class="language-plaintext highlighter-rouge">insert</code>: You can either return a value or you can return <code class="language-plaintext highlighter-rouge">self</code>, but not both.</p>
<p>The fundamental problem with returning <code class="language-plaintext highlighter-rouge">self</code> solely for the purpose of enabling method chaining is that you’re contorting your API in order to enable something that’s completely orthogonal to what your API is doing. Your function’s signature should reflect its behavior, and should be usable in a method chain regardless of its return type.</p>
<h2 id="were-only-talking-about-method-chains">We’re Only Talking About Method Chains</h2>
<p>At this point I’ve covered the practical issues with returning <code class="language-plaintext highlighter-rouge">self</code> and the more conceptual reason why it doesn’t make sense. Before I move on to discussing alternate solutions, I want to emphasize an important point: Returning <code class="language-plaintext highlighter-rouge">self</code> is only an issue if it’s being done <strong>solely to enable method chaining</strong>. It’s entirely reasonable to return <code class="language-plaintext highlighter-rouge">self</code> from a function if doing so is semantically meaningful, and I am in no way trying to say that it is never appropriate to return <code class="language-plaintext highlighter-rouge">self</code> from a method in Rust. It only becomes an issue if you’re returning <code class="language-plaintext highlighter-rouge">self</code> from a function for no reason other than to allow users to chain those methods together.</p>
<h2 id="method-cascades">Method Cascades</h2>
<p>As is often the case, we don’t need to invent a whole new solution to this problem when we could simply steal good ideas from another programming language.</p>
<p>Dart provides first-class support for method chaining in the form of <a href="https://www.dartlang.org/guides/language/language-tour#cascade-notation-">method cascades</a>. The <code class="language-plaintext highlighter-rouge">..</code> operator is the the “cascaded method invocation operator”, and behaves similarly to <code class="language-plaintext highlighter-rouge">.</code> except that discards the result of the method invocation and returns the original receiver instead. This allows <em>any</em> method to be chained in Dart, without requiring the author to have thought ahead of time to return <code class="language-plaintext highlighter-rouge">self</code>.</p>
<p>In Dart, the syntax looks something like this:</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="n">addressBook</span> <span class="o">=</span> <span class="o">(</span><span class="n">AddressBookBuilder</span><span class="o">()</span>
<span class="o">..</span><span class="na">name</span> <span class="o">=</span> <span class="s">'jenny'</span>
<span class="o">..</span><span class="na">email</span> <span class="o">=</span> <span class="s">'[email protected]'</span>
<span class="o">..</span><span class="na">phone</span> <span class="o">=</span> <span class="o">(</span><span class="n">PhoneNumberBuilder</span><span class="o">()</span>
<span class="o">..</span><span class="na">number</span> <span class="o">=</span> <span class="s">'415-555-0100'</span>
<span class="o">..</span><span class="na">label</span> <span class="o">=</span> <span class="s">'home'</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">())</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
</code></pre></div></div>
<p>While there’s no equivalent syntax built into Rust, we could achieve something very similar with the help of a fairly simple macro. In fact, there’s already the <a href="https://crates.io/crates/cascade">cascade</a> crate which does just that!</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">foo</span> <span class="o">=</span> <span class="nd">cascade!</span> <span class="p">{</span>
<span class="n">foo</span><span class="p">:</span> <span class="nn">Foo</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span>
<span class="o">..</span><span class="nf">chain</span><span class="p">();</span>
<span class="o">..</span><span class="nf">chain</span><span class="p">();</span>
<span class="o">..</span><span class="nf">chain</span><span class="p">();</span>
<span class="p">|</span> <span class="k">if</span> <span class="n">some_condition</span> <span class="p">{</span>
<span class="nd">cascade!</span> <span class="p">{</span>
<span class="o">&</span><span class="k">mut</span> <span class="n">foo</span><span class="p">;</span>
<span class="o">..</span><span class="nf">chain</span><span class="p">();</span>
<span class="o">..</span><span class="nf">chain</span><span class="p">();</span>
<span class="o">..</span><span class="nf">chain</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="o">..</span><span class="nf">chain</span><span class="p">();</span>
<span class="o">..</span><span class="nf">chain</span><span class="p">();</span>
<span class="o">..</span><span class="nf">chain</span><span class="p">();</span>
<span class="p">};</span>
<span class="nf">consume_ref</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span> <span class="n">foo</span><span class="p">);</span>
<span class="nf">consume_move</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
</code></pre></div></div>
<p>Since this pattern hasn’t yet seen wide usage in the Rust community, I expect that it will take some time and iteration to fully adapt it to Rust as a language (though the cascade crate is certainly a good start). Looking to the future, I would personally like to see this pattern become “official” in some regards, either through inclusion in the standard library or 🤞 a native syntax 🤞.</p>
<h2 id="conclusion">Conclusion</h2>
<p>So in summary:</p>
<ul>
<li>Don’t return <code class="language-plaintext highlighter-rouge">self</code> from methods if you’re <strong>only</strong> doing so to enable chaining.</li>
<li>Start using the <code class="language-plaintext highlighter-rouge">cascade</code> crate instead!</li>
</ul>
<p>Comments and discussion can be found online:</p>
<ul>
<li><a href="https://www.reddit.com/r/rust/comments/b3tmh1/chaining_functions_without_returning_self">Reddit</a></li>
<li><a href="https://users.rust-lang.org/t/blog-post-chaining-functions-without-returning-self/26504">The Rust Users Forum</a></li>
</ul>