tadpoleengine.github.io

switching from SDL_net to websockets

It turns out, making network code portable is even harder than making graphics code portable. Emscripten and Android are the real sons of bitches here – I had an incredibly tough time trying to get this to work. Granted, it would have been easier if I actually knew what I was doing and spent less time bumbling around in the dark, but then again, these things feel like they are way harder than they need to be.

So here’s the problem: Emscripten runs in a browser, and as such, can’t give you access to TCP connections. Guess what SDL_net bases their entire code base around? That’s right, TCP connections. It’s a fair thing to do, because BSD sockets are typically available everywhere, but the DOM basically only gives you XMLHttpRequest. They don’t have very much to work with.

Emscripten’s website says that if you do use sockets, they provide an emulation layer that relays the packets across WebSockets instead. To combat this, there are two things you can do: either use Websockify or run your own proxy server. I could not for the life of me get Websockify to work. Maybe there’s documentation for that project hidden somewhere on the internet, but I certainly couldn’t find it. So I tried to set up the proxy server, but it turns out that’s incompatible with SDL!

I briefly experimented with calling XHR directly using EM_ASM and EM_JS, but it became an incredible pain to try to pass buffers between JavaScript and C++. JS has a totally different way of thinking about memory due to garbage collection, so you can’t just toss (char*) pointers around. About halfway through experimenting with Module.memset I just threw in the towel – it felt WAY too hacky.

The only kind of networking that gets first class support in Emscripten is the Emscripten WebSockets API. They provide an API for accessing WS streams directly, because the DOM gives them a lot more leeway on that. So I had the thought – what if I based my networking paradigm around WebSockets instead of TCP connections? After mulling it over a bit, it felt like the right change. I want this framework to be used to make multiplayer games, and your options for real time multiplayer networking are basically long polling with HTTP or WebSockets. I had some momentum already so I went with the latter.

It didn’t take long to set up a basic echo server with the ws node module (SocketsIO is overkill, plus I like leaner frameworks that give me more control to structure things however I want). Then it was a matter of defining an API for my Lua programs to use. I opted with a model where the runtime manages the websocket connection, and the Lua scripts just call send_message and define a listener for receive_message. There is a little bit of weirdness because emscripten_websocket_set_onopen_callback requires a callback, which is a natural way to program in JavaScript but quite strange in C++. I want to just block on the network, but I can’t because Emscripten doesn’t give me a “yield to the browser” call. The way they schedule browser events is not clear to me at all.

After getting a simple example working in the browser, I went on the hunt for a cross-platform web sockets API in C or C++. This is where things took a sharp turn for the worse.

I quickly settled on libwebsockets, because it seemed the simplest and most portable, and as a bonus it’s pure C. The project is quite impressive, especially because as I understand it it’s basically the product of one man. iOS and Android aren’t supported as first class build candidates, so I knew I’d be in for some monkeying around, but the maintainer mentioned in a few places that people have gotten it to work on those platforms before so I didn’t think too much of it.

The first step was to get a command line C program to open a websocket connection to my echo server, send a few messages, and close the connection. This was surprisingly difficult. libwebsockets has basically no documentation to speak of, other than the maintainer’s impressive track record of responding to Github issues, and a directory full of example programs. That sounds like a lot but what I really needed was a simple, one-page introduction to the basic topics. libwebsockets supports about a billion use cases, like writing a websocket server, http client, http server, encryption, compression, etc. So a lot of what I read in the README was about these new features, when I just wanted something simple to get up and running.

It took a while but after carefully studying a few examples and googling furiously, I got something to work. Score!

Then it was actually a pretty simple matter to get it working on my macOS program. I ripped out the SDL_net guts, whipped up a few functions that mirrored the Emscripten API I created earlier, built the library, set up the include path and the linker in Xcode, and it worked like a charm. There were a few hiccups as I recall but nothing I couldn’t handle, because libwebsockets supports macOS builds out of the box.

Then I took on what turned out to be a tremendous challenge: getting it to build for iOS and Android. (As of this writing, I still have Windows and Linux to go, but I figure those will be easy now that I’ve gotten these two to work.)

I won’t recount all of the gory details, but basically I had to learn a ton of stuff about CMake, OpenSSL, ABIs, Android architectures, static linking, the NDK, and a bunch of other stuff I had pretty much no clue about until this week. There were a TON of stops and starts and dead ends, culminating in me creating the most embarrassing Github issue of all time. I ended up disabling SSL on these targets because it was too painful. But after several days and nights of staring at the screen, I got it to work!

The most unexpectedly painful thing about this project so far has been dependency management. I’m used to working on very high level languages like JavaScript, Rust, Python, and PHP, which all have fairly compelling package managers. Say what you will about npm, at least it provides a unified way for open source projects to organize their dependencies. What I’m doing in this project is pretty much building everything from source and hacking together my toolchain with Bash scripts. It feels like there has got to be a better way…

I’ve been especially puzzling over the dependency management question because I’ve decided I want to make this into an open source project once I have some of the building blocks in place. My dream is to turn this project into a company, but I just don’t see how I could get other programmers to help me out without giving them something in return. I don’t have money, so letting people use my framework for their needs will have to do.

As soon as I figure out how the project directories should be organized, what the name of this project should be (rostrum is a placeholder), and get some more of the library up to my liking, I think I’ll go public with it.