The Playtypus Experiment #6

5 minute read

Touhma : First Post from Inno It’s his first time , don’t be gentle !

Coding a joke

Code is like humor. When you have to explain it, it’s bad. – Cory House

cough cough

I have some explaining to do. ;)

I’ve spent the last forever working on implementing a lightweight RPC system.

For those of you with lives, RPC stands for Remote Procedure Call, for those with good lives: I’ve been developing a system so that a game server and game client can talk to each other XD

Now I know what you’re thinking -

Er… surely that’s a solved problem, why would you need to reinvent the wheel? Did you hit your head?

Glad you telepathically asked! Of course it is!, I didn’t! and yes!

not confusing at all …

Fortunately Unity even comes with its very own Netcode for Entities package, which has a lovingly crafted RPC system coded by experts that has all the bells and whistles a game developer could dream of. Unfortunately I don’t dream, and I’m not smart enough to understand how their system worked under the hood. I tried, and I think I got close, but in the end I decided it was easier to just write my own implementation ensuring that only the absolute minimum amount of data was wasted, and in as few clock cycles as possible without getting completely stupid. (I may have failed on the last part).

Touhma : Even the people at Unity & a lot of devs confirmed us that Netcode is not made at all to support a factory building game & we would be crazy not to use our own custom solution, so that was the right call, hopefully :)

So that’s why I started writing Notwerk, my minimalist implementation of an RPC system for Unity ECS. No you can’t see it. But I can describe it to you! Oh, and if you care as much about esoteric behind the scenes ramblings by an unqualified amateur as I do about the latest Reality TV show, then I won’t blame you for scrolling to the bottom of this post, quickly glancing at it to make sure there isn’t anything worth looking at (there isn’t) and finding something better to do with your time.

Setting up the playing field

At the beginning here were the requirements :

Initial Goals:

  • Jam as much useful data through the series of tubes that is the internet as physically possible
  • Ensure that the useful data actually gets where it’s going, and if it doesn’t, send it again until it does.
  • Do it as quickly as possible.
  • Make it easy to send and receive the data from our gamesystems.
  • Leverage Source Generation to remove the requirement for ‘compilable portable function pointers’ because they sound scary.

Touhma : Point of disagreement here but , whatever, we shall see in the future :D

Stretch Goals added once I started:

  • Figure out how source generation works
  • Hunt down the people responsible for Burst Compilation errors and pry the last two weeks from their cold…
  • Calm down and eventually read the manual (I’m actually finally doing that now, as I write this. Damn. I could have saved 2 weeks…)

How it went

I initially called it Notwerk because I didn’t think I’d ever get it working. But, we like it so it stuck.

I started with learning how to write a source generator, thinking that would be the hard part. I had a working example in 15 minutes. I was blown away by how incredibly awesome Roslyn Source Generation is, and decided I wanted to source generate as much as possible. So I did. I source generated so much stuff that I got way ahead of myself, and started implementing things that I wasn’t supposed to. It was fun though. A++ would use again.

Touhma : Technically the generator being a separated .dll from the game so it’s its own thing, could be hard to debug once it’s done but once it works , it works.

The source generation involved has two or three parts, depending on whether the third part is useful or not.

  • Generate code that reads incoming bytestreams, and depending on the leading two bytes (The command ID), direct the bytestream to the correct Deserializer which turns it from an ordered stream of 0’s and 1’s into named Component Data that can be used by the game to do all manner of things.
  • Generate code that turns Component Data into a stream of bytes that can be combined with other commands and sent across the network.

Then it was just a matter of writing some extremely basic systems (In Unity ECS, the S stands for System, which is the logic code) that process the Components (The C in ECS) and do the actual sending.

That took about 50 hours, or roughly 10 lines of code (including whitespace, parentheses, and directives) per hour on average.

Of course, it was more than 500 lines at one point, but I’ve rewritten everything so many times I don’t remember a time when I wasn’t rewriting it.

I’m finally relatively happy with how it works, and now it’s time for Touhma to take a look and fire me.

Touhma : Sigh … I’m not employing you, you know ? I can’t fire you.

In all actuality, I probably spent more time writing debug tools that allow logging bytestreams to console while remaining fully burst compiled than I did writing netcode, but without it I would still be wondering why things weren’t working (It turns out that if you press ctrl-d it duplicates the line you’re on, and I was reading a byte twice).

And now I have pretty colors in the console. Anyone who played GalacticScale2 with BepInEx console enabled would know how much I like console color.

I’ve spent 6 hours tonight getting it to a point where it shouldn’t be holding up Touhma’s Lockstep/GameTick/Prediction/Wizardry anymore.

Touhma : I’ll talk about that another time when I manage to get everything working as expected

So I guess if you’re reading this it wasn’t the worst thing in the world.

Touhma : Getting there \o/

Or I’m having an example made of me. Either way if anyone has any questions, feel free to ping me.

Until then, you can join Our Brand new Discord to learn more about the project, chat, share ideas & meet the dev team !

Take Care everyone !

Inno

Updated: