Ongoing Unity Shenanigans
Learning about Unity's many speed-bumps, one bump at a time.
Since the last time we talked, I’ve had to learn some concepts in Unity in order to make any forward progress.
I had my music generator running, and the month has been more about getting it to run… reliably.
Let’s talk about some of my horrible Unity learning experiences.
I’d made a lot of forward progress getting my Unity solution to kick out generated tunes, but here’s a pain in the shorts:
A lot of things can cause Unity to freeze.
The Unity UI is running on the same thread, as far as I can tell, as the game you’re trying to run, so any time your code goes for too long without responding, Unity handles this with the tried-and-true error handling strategy of becoming very, very still in hopes that large predators won’t notice it.
At first, it seems like the only response to this is to restart Unity and try again, (and yes, this will “resolve” the problem) but actually there is a thing that needs to be done in order to figure out what’s causing Unity to freeze: it is time to attach a debugger to the process, pause everything, and try to figure out where in the world things have become stuck.
I find this ideologically offensive - as a lifelong adherent to the extremely technologically advanced school of log-based-debugging, what I want to do to resolve problems is to
Watch logs while the program is running.
Make changes to add logs where I think the problem might be.
Restart the program and try again.
It ain’t much, but it’s honest work.
The editor crashing on me, though, makes the first step difficult (can’t watch logs if the UI has frozen, you need to go find the log output file deep in %APPDATA%), as well as the third step (can’t stop the program without CTRL-ALT-DEL).
Of course, attaching a debugger to the process pops up a UI window in Unity, which you need to accept in order to continue debugging, which is impossible when Unity UI is frozen, which made this step seemingly impossible - until I spent some time trying to figure out attaching a debugger in Unity a little better.
Yeugh, debuggers. I’m too much of a web developer for this.
This painful learning experience has bit me a few times now.
Async In Unity Is No Fun
C#’s “async” functionality is pretty deeply integrated into the language at this point, and it’s also baked into Flurl and .NET’s built-in websockets implementation.
Attempting to use it in my Unity project, though, led to weeks of frustration, and weeks of reading the same 6 dated blog posts.
Most of these blog posts end up referencing the UniTask library, but, unfortunately, I am either not smart enough or not familiar enough with the environment to really understand what it is that UniTask does, or how.
Why UniTask(custom task-like object) is required? Because Task is too heavy, not matched to Unity threading(single-thread). UniTask does not use thread and SynchronizationContext/ExecutionContext because almost Unity's asynchronous object is automaticaly dispatched by Unity's engine layer. It acquires more fast and more less allocation, completely integrated with Unity.
So let’s bake this down so that it’s so simple that even I can understand it:
Unity supports async, by virtue of supporting most of the stuff that’s in C#7, but the core event loop of Unity is designed really aggressively around the use of coroutines. If you have a function - like an “update” - return a async Task, the resulting behavior is going to be probably not what you wanted. In fact, it might end so badly that you end up freezing Unity.
Oh, and let’s reiterate that super important line in the middle there:
Unity is designed really aggressively around the use of coroutines.
Yeah, for anybody who knows Unity already, this is going to seem like I’ve wandered into a library, only to discover that “libraries are full of books” - but it was a revelation for me, anyways. Coroutines aren’t terribly hard to wrap one’s head around, although they’re a pretty different programming model than the ones I’m used to.
So a big part of using async/await in Unity is bridging the gap between async/await and coroutines. As a friend of mine said:
I’ve looked at a number of
Taskplugins for Unity and concluded it was far better to roll my own integration. It turned out to be impossible to abstract C# async to work with Unity coroutines without losing a lot of important semantic details, leading to an implementation that’s just much more grokkable with all its innards exposed.
Anyways, this friend helped me out with a snippet of code that wraps Async/Await code in a nice Coroutine wrapper, and that helped me sail through this particular barrier.
The Package Ecosystem
I’ve travelled here from the land of npm - which might be the greatest package manager in the entire world. (I know, I could be starting a fight with this, but someone’s gotta say it.)
For one thing, npm has a rather lot of packages on it.
A lot of packages.
And a big part of the reason for that is that it’s really easy to push packages to npm.
Unity, on the other hand, has two major sources:
The Unity Store, and nuGet.
First of all, nuGet: it’s… fine. I guess.
There’s a lot of high-quality C# code here to peruse.
Of course, the integration with Unity is terrible. The default procedure for getting a nuGet package into Unity is to:
Download the package manually from the nuGet website
Unzip the package
Navigate to the directory of the version matching the specific .NET compatibility settings you’re targeting with your Unity project (.NET Standard 2.0 by default, I think)
Take the dll in there and drop it in to your Unity project’s “plugins” folder.
If the project has any dependencies, make sure to perform this step for them, too.
This is some hot buttered nonsense, right here.
One of my co-workers noted that it can be a more sensible strategy to just build the lion’s share of your code outside of Unity, with a full raft of modern development tools - non-terrible unit testing, integrated nuGet - and then generate and import a dll for use in your game.
That brings us to the Unity Store.
Here’s the thing: compared to modern application development, indie video game development is the thunderdome.
pictured: an average independent video game studio
Budgets are smaller. Incentive to co-operate is low. All of the code is proprietary. Most of the projects are maintained by a single harried developer who’s made a grand total of $450 on their package and who is now seemingly on the hook for lifetime support, a developer who’s documented it with a now-out-of-date Word file, and who hasn’t been seen for at least six months. The Unity Store is npm, except if every package was slightly broken and every package cost $80 and every package was maintained by a single exhausted maniac.
Anyways, that is to say, I’m down a couple of hundo bucks (I don’t even know what I intend to DO with Shapes, it just looked so neat) and I’ve had to make a few changes to the packages I’ve downloaded so far in order to make forward progress.
What an expensive hobby I’ve picked up, game development.
Zenject / Zenject Signals
I spent a fair bit of time looking at Zenject and Zenject Signals for dependency injection and message passing infrastructures, and I am not yet convinced that they’re terribly necessary in Unity - however, I am still so new to Unity that these libraries might be papering over some holes that I just haven’t run in to yet.
Unity’s object graph and scripting layer have a lot of properties that seem to echo dependency injection already - instantiation isn’t handled by your code, so much as your code defines what connections and variables it needs and then you link everything together in the object graph. However, in some ways - especially when it comes to object creation - the object graph seems fairly limited, so maybe I just haven’t encountered the problem that Zenject is meant to solve, yet.
I had a similar problem with Zenject Signals vs. Unity’s built-in UnityEvents. UnityEvents seem limited - they can’t proceed without some coupling, because an object either needs to be manually subscribed to another object’s events through the UI, or Object A needs to know about Object B to programmatically subscribe to its events. However, with that being said, it seems like an awful lot of extra work to bring in a whole separate library with a load of extra Concepts just to break one small edge case of coupling.
I reserve my right to change my mind about either of these libraries if it turns out I’ve badly misjudged how useful they are - if my code becomes intractably knotted, I’ll know it’s time to start looking at More Architecture.
I dunno, I’m mostly having fun learning a bunch of stuff about Unity, although it feels like it’s slowing me down to have to “learn things” rather than heedlessly smoosh code together like I get to do when I’m working on backend or web stuff.
Here are some new tracks recorded with the current version of the procedural music generator:
Unfortunately, my vacation is over, so I’m going back to only being able to scrape a few hours together on weekends to work on this stuff.