A year of prototyping
A large portion of my prototyping and project planning phase for Chimera, my Conquer Online private server game project, comprised of various language evaluations. Ultimately, I decided to develop with a managed language in an attempt to scale more quickly and distributively. This article covers my experience with Go for game service development vs. C# .NET Core, another managed language I had years of prior experience in.
Clear Benefits
One observation I made about Go was its simplicity with interfaces. It takes a very C-like approach to developing extensible libraries. Unlike C# which has many separate APIs for a simple asynchronous TCP server, Go only has one which can be extended into many other patterns (the Conn and Listener interfaces). Overall, this approach led to much cleaner socket streams, block ciphers, XOR cipher streams, remote procedure calls, logging, encoding, and much more. The elegance that Go provides through its standard library is one of its largest advantages, and allows programs to be more widely affected by language optimizations.
As a service project, however, Chimera’s primary goal is not just to extend good interface and protocol design. Chimera must deliver a game world efficiently and distributively with minimal latency. This is where Go takes a very clear lead in networking and parallelism. Unlike C# and the async-await design pattern, Go makes channels and threading (go routines) language primitives. Using channels in Chimera made scheduling work cleaner while also promoting better thread synchronization patterns without sleeps or locks.
The struggles of an evolving language
Though developed by the same authors as C and Unix, Go is still a relatively new language. It brings a lot of modern features to the table, but comes with a few caveats. It’s clear that Go’s authors carefully considered how programs and packages would talk to one another through interfaces, but as much as this language choice makes it easier to design cleaner services and protocols, it can also add complexity when doing project planning.
Go enforces a strict package dependency tree. This is good, as it makes developing code analysis tools and compilers easier without the worry of circular dependencies. Though if not careful, your package system can easily become a mess as you develop subsystems to your project. A logging package that logs to a central location, for example, might depend on a configuration package and vise versa. Adding versioning only worsens the problem; though this has been alleviated recently by go modules. Go modules are an opt-in feature since the language has a backwards compatibility guarantee. If you take Go into consideration for a microservice project, slow down and take time to design your package system.
Outside of the package system, Go has one more significant caveat: limited object oriented design patterns. As I already stated, Go takes a very C-like approach to developing extensible libraries. Just as you would develop generic-like structures in C using anonymous structure composition and void pointers, Go does the same with anonymous structures and blank interfaces. Though this isn’t a game-stopper, it is something to be aware of if you’re coming from a heavily object oriented background.
Making compromises
If coming from another language such as C#, Java, or C++, you may be accustomed to limiting your use of language features in order to make your code easier to read and maintain by multiple engineers. The great news is that Go’s language syntax and features are incredibly simple, so there’s no need to limit features. The bad news is that this makes Go’s syntax incredibly strict and far less expressive. You’ll be making style compromises, and though most compromises are for a reason, some you’ll just have to live with. For better or for worse, it’ll take some time to get used to.
The main takeaway
While prototyping in Go felt like a natural extension of the language, prototyping in C# felt like a forced reinvention using the language. C# required much more research, deliberation, and implementation time. Though Chimera clearly benefited from Go, the language is still new and evolving. You’ll make some compromises, but the benefits of great interface design, networking performance, channels, and go routines make them easy ones.
If you’re starting a similar game project, then I hope you found this useful. I understand that language discussions are often complicated and opinionated, but I’d be happy to hear other people’s thoughts on the matter. It won’t influence my decision to use Go for this project at this point, but I’d be happy to discuss topics around languages. Cheers, and thanks for reading.