Hacker News new | ask | show | jobs
by nivviv 2069 days ago
A small perspective insight from a game developer:

We (Beamdog) are using nim in production for Neverwinter Nights: Enhanced Edition, for the serverside parts of running the multiplayer infra.

nim is uniquely good in providing an immense amount of value for very little effort. It gets _out of my way_ and makes it very easy to write a lot of code that mostly works really well, without having given me any serious traps and pits to fall into. No memleaks, no spurious crashes, no side-effect oopses, anything like that. It's C/++ interop has been a huge enabler for feature growth as well, as we can partly link in game code and it works fine. For example, our platform integrates seamlessly with native/openssl/dtls for game console connectivity. And it all works, and does so with good performance. It is all now a set of quite a few moving components (a message bus, various network terminators, TURN relays, state management, logging and metrics, a simple json api consumed both by game clients and web (https://nwn.beamdog.net), ...).

We're still lagging behind and are on 1.0.8, but that is totally fine. It's tested and works, and there's no real incentive to move to 1.2 or 1.4 - yet!

Usage for our game has expanded to provide a few open source supporting utilities (https://github.com/Beamdog/nwsync) and libraries (https://github.com/niv/neverwinter.nim/) too. The good part about those is that they are cross-platform as well, and we can provide one-click binaries for users.

OTOH, There's been a few rough edges and some issues along the way. Some platform snafus come to mind, but those have been early days - 0.17, etc. Some strange async bugs had been found and fixed quickly though.

Good and bad, at least for me, nim has been a real joy to work with. If I had the chance to message my 3-years-younger, I'd just say "yea, keep going with that plan", as it turned out to save us a lot of development time. I suspect the features we've put in wouldn't have been possible in the timeframe we had, if it would have been all written in, say, C++.

2 comments

Interesting, thanks for sharing.

May I ask, did you consider Go and decided against it for any reason considering your requirements of quick development, cross-platform, interoperability are all guaranteed features of Go which should have given better peace of mind considering a production application?

Go code runs much more slowly than Nim code in my experience. Nim realizes better ergonomics than Python in some ways (UFCS, command syntax, user-defined operators) and as good performance & lightweightness as C/Rust.

I'm honestly surprised Nim is not the secret weapon of many start-ups. Nim is much more "open architecture" instead of pushing some "single, canned turn-key fits most users" solutions.

Having a juggernaut like Google marketing/pushing/subsidizing something might provide false as well as true peaces of mind. :-) { Such peace is a multi-dimensional concept. :-) }

The advanced features Nim offers is in stark contrast with Go where simplicity is valued. But the power those features give you when you really need them is undeniable. There's no need to dance around certain problems.

I did move to Go from Python briefly for performance reasons but once I found Nim, there's just no going back. Simplicity might have real value in large projects but I just don't like my hands tied.

Statement on performance is surprising, I haven't personally benchmarked Nim against Go so couldn't say about that.

But I started using Go for the same reasons pointed out in parent and after being tired of changing Python code to C to resolve performance issues.

How about concurrency?

Nim is on par with C in a lot of benchmarks, e.g. https://github.com/kostya/benchmarks. Go is the king of concurrency, so whatever you compare to it loses. That said, Nim has async/await for concurrency, and I find threads and threadpools easy to use for paralelism.
Lets not get carryied away with Go advocacy,

https://www.techempower.com/benchmarks/#section=data-r19&hw=...

There is a link in another of my comments here and because Nim is pretty open architecture you can roll your own basis for what you want (more than most languages) such as: https://github.com/mratsim/weave
I can't speak about go vs nim generally, but:

Our particular design is a bunch of single-threaded apps on a message bus. Each app (network ingress/egress, data handling, relays, etc) sits on the bus, and can use async/await to do IO concurrency, but on the app level, there's no threading and no locking to observe.

Each app also has a erlang-inspired supervisor task system, where each task is just a async proc kept alive. It's proven to be very robust (from the standpoint of service availability) in the face of bugs or input validation mishaps.

The performance shouldn't be surprising, as it compiles via C (So it benefits from 50 years of work on C compilers), and almost all of the language constructs compile to essentially equivalent C code.
Nim also has an emit pragma where you can just inline C code (or code for the Javascript backend or C++ backend, etc.). So, if there is some poorly optimized (for whatever reason) hot inner loop you can fix it right there, though you start sacrificing portability (often the trade off for optimal performance). You can even do SIMD intrinsics right in Nim no problemo just using the FFI Nim has for C calls.
Python is written in C, and yet ...

Compiling to C really isn't relevant. "50 years of work on C compilers" is not at all relevant--languages that compile to LLVM get all the advantages of the optimization work.

Written in C is not the same that compiled via C. If you want Python compiled via C you can use Cython, and without much surprise, you usually get a huge speed up (e.g. https://notes-on-cython.readthedocs.io/en/latest/std_dev.htm...).
Go was a consideration and there are a few libraries internally existed for Go at that point, but nim in the end won out on acceptance and it convinced - despite the known risks of using a only semi-mature language - on feasibility in getting it done in time for the initial game release.

The PoC was incredibly quick to manifest, and iterating on it had quickly proven itself as a good way forward.

Peace of mind was a judgment call. Despite being a rather sizeable project now, what we had back then was already very stable and reliable (even under heavy benchmark load) and there weren't any great unknowns souring making the call.

What's the dev experience been like? Do you feel like there is good editor support for debugging and such?
I've been using vscode+nim. Debugging was mostly just writing correct code in the first place! ;) The only gripe I have is sometimes nimsuggest hanging itself at 100% cpu use, and I have to kill it manually.

Even with the rather oldschool approach of echo/logging.nim usage, things tend to turn around quickly. I have not felt the need to be able to attach a debugger to the process, mostly because our architecture is very pluggable. Almost all events/interactions are on a message bus and can be hooked/handled individually.

https://nim-lang.org/blog/2017/10/02/documenting-profiling-a... -- Not sure how good the VSCode debugging integration is, but all the requirements are there including gdb/lldb. Word is JetBrains folk wrote a new Nim plugin too. Personally, I like the `writeStackTrace` bit.
What build system are you using if not MSBuild then?
Our production stuff runs on Linux, so we just wrap it into docker and that's that.

Personally, I develop on Mac and it runs natively there the same as on Linux.

Windows binaries for the tooling releases I used to build with a cross-compiler, but more recently, GH actions looks attractive enough to take that role.

Edit: Sorry, could have been clearer. The build system is just running the binary directly via nim cpp -r in development, and for production it's nimble. The dockerfile is handcrafted, but of trivial complexity.

So you use Docker to keep the build system unchanged between builds? That’s genius if so and cuts deeply into MSBuild’s main advantage (comprehensively delineated system settings).
I think there might be a misunderstanding. I'm just talking about the multiplayer service infrastructure, not the whole game.

Despite doing a lot and having a lot of smaller moving parts, the mp infra is of moderate code size and build complexity is not a concern. The whole thing compiles in less than 5 minutes, and can be done with nimble (the package manager). We have a docker builder image that spits out the final production image containing the apps, and a docker-compose setup then runs those as needed.

Any kind of per-platform specifics are handled in nim itself (when defined(Linux): etc) and via nim.config/nims.nim, to link in platform libraries.

I use the Nim and GCC compilers from Debian in order to have guaranteed stable build system for 5+ years.