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 distributedly.
My evaluations came down to two successful prototypes, one written in .NET Core 2.1 and one written in Golang 1.11. I went back and forth between the languages, and had to use a weighted decision matrix to finalize my decision. This article explores why I chose Golang for game server development, and how that has affected development so far.
One observation I made about Golang was its simplicity with interfaces. Unlike .NET Core which has many APIs for TCP servers, Golang only has one – the Conn and Listener interfaces. For a fair comparison, I used C#’s Async-Await design pattern using Socket Task Extensions (since it closely resembles go routines and channels). In both prototypes, I extended the API to create an asynchronous, non-blocking TCP server. In the C# implementation, I also utilized System.Threading.Channels for channel implementations. Both fared well, but Golang’s go routines and channels as language primitives made the implementation cleaner with far less code. Additionally, the throughput of the socket server was much higher in the Golang prototype than the .NET Core prototype.
Golang’s interfaces also made cipher implementations much easier to write (not to mention that their big integer implementation smokes .NET’s BigInteger class). In one example, I used Golang’s cipher.Stream interface to design a reverse engineered custom cipher from the Conquer Online game client. Falling back to my original observation, this interface allowed me to find a better design for my server than I originally authored. I met the authors of Golang recently at Google Cloud Next, and this process of redesigning old implementations helped me understand a saying at the conference. The language isn’t attempting to replace program implementations, it’s attempting to reinvent how we program. Though .NET Core is a great step in the right direction for C# in reinventing the language, I personally found that Golang was a lot better at concisely documenting and encouraging better designs.
When designing microservices for the game server, I found that Golang provided good value with its bundled web, database, RPC, cryptography, and encoding packages. Again, using interfaces, Golang built-in implementations were easy to use and integrate into my services. At this moment, Chimera uses custom microservice orchestration using builtin golang packages. This can be easily modified to be a custom Kubernetes scheduler. I’ll write an article about that when I get to a good transition point.
It’s clear that Golang’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 implementations and programs, it can also add complexity when doing project planning. Unlike C#, Golang enforces a strict package dependency tree. This means no circular dependencies. Don’t get me wrong, this is a good thing, as circular dependencies can often lead to problems when performing code analysis in C#. The downside being it makes planning more time consuming.
For example, a character package that manages a player’s attributes depends on a packet package for sending UI changes to the game client. An interface needs to be written for the packet package to initialize the packet without a circular dependency. That way, the packet can be initialized by passing in the character struct, and the character struct can make direct calls for updating the client through its methods using the packet package. Since this enforces good distributed programming models, even though my server wouldn’t be largely affected by circular namespaces in C#, I gave less planning complexity medium weight in my decision matrix and gave C# a bit more weight.
Making Style Compromises
Golang uses built-in vetting and auto-formatting to make implementations written in the language read consistently. I found that the built-in format tool is too strict for my liking, so I have auto-formatting disabled. Open source format and vet alternatives exist, but the main issue is that Golang can be an ugly language when it attempts to enforce one style without context. One programming style sounds like a benefit on paper, but sometimes leads to distracting code. Simple error checks get expanded into 2-3 additional lines of code, which take away from the program’s application logic.
In regards to style, I believe Golang makes two steps in the right direction, and one step in the wrong direction. Some style choices make a lot of sense, such as defining variable identifiers before data types, or how object oriented design is implemented almost like an extension of C. Other style choices bother me but make sense under the context of language parsing, such as the non-existence of one-line conditional bodies. Aka. you must always specify brackets for if statements, and that’s okay. In comparison to C#, however, the expressiveness of the language can provide better looking, more readable code. This is a trade-off, though, since C#’s expressiveness can lead to hidden costs and time sinks. I gave style expressiveness and limitations a bit less decision weight in small favor of C# since it’s more like the standard C-syntax than Golang is.
To summarize, I found Golang to be the better choice for my project. That’s definitely not true for all projects, but here are the main reasons that led me to settling on Golang:
Good interfaces and interface design
Good built-in packages for networking, cryptography, and web programming
Channels and go routines makes parallel programming cleaner and easier
The language’s socket performance and reliability is second to none
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 Golang for this project, but I’d be happy to discuss topics around languages. Cheers.