Musings about the project and my experience with it.

Complications with Cross-Platform Input Handling


It was bound to happen eventually, but the input handling module is the first part of the project to display different behavior across platforms. winit provides a fairly solid basis for input handling, but Windows and Linux differ in terms of what sort of event is delivered to the program.

Initially, I used WindowEvents for everything. This works perfectly well for keystrokes and mouse clicks, but mouse movement may still have acceleration applied, which is undesirable for camera control. winit also offers DeviceEvents for this purpose. I tried just handling mouse movement with raw input, keeping all other inputs in WindowEvents, but it seems that handling DeviceEvents on Linux causes the WindowEvents to be eaten.

The next obvious solution is to simply handle everything with DeviceEvents, but this presents additional problems. First, Windows doesn't seem to even deliver keyboard input as a DeviceEvent -- keyboard input still needs to be polled as a WindowEvent. It also means that window focus has to be handled manually, since DeviceEvents are delivered regardless of whether the window is focused or not.

To add to the complexity of this problem, apparently not all window managers are well-behaved when it comes to determining focus. I run i3wm on my Linux install, and it doesn't deliver WindowEvent::Focused events when toggling focus or switching workspaces. This will have to remain an unsolved problem for the time being.

Shared Ownership of Rendering Resources


Among the most challenging design decisions in writing the rendering code has been the issue of ownership. In order to avoid linking the rendering logic too closely with the data, most of the rendering is done by separate Renderer objects (i.e., to render an AliasModel, one must first create an AliasRenderer).

The process of converting on-disk model data to renderable format is fairly complex. Brush models are stored in a format designed for the Quake software renderer (which Michael Abrash explained quite nicely), while alias models have texture oddities that make it difficult to render them from a vertex buffer. In addition, all textures are composed of 8-bit indices into gfx/palette.lmp and must be converted to RGB in order to upload them to the GPU. Richter interleaves the position and texture coordinate data before upload.

The real challenge is in determining where to store the objects for resource creation (e.g. gfx::Factory) and the resource handles (e.g. gfx::handle::ShaderResourceView). Some of these objects are model-specific -- a particular texture might belong to one model, and thus can be stored in that model's Renderer -- but others need to be more widely available.

The most obvious example of this is the vertex buffer used for rendering quads. This is conceptually straightforward, but there are several layers of a renderer that might need this functionality. The ConsoleRenderer needs it in order to render the console background, but also needs a GlyphRenderer to render console output -- and the GlyphRenderer needs to be able to render textured quads. The ConsoleRenderer could own the GlyphRenderer, but the HudRenderer also needs access to render ammo counts.

This leads to a rather complex network of Rcs, where many different objects own the basic building blocks that make up the rendering system. It isn't bad design per se, but it's a little difficult to follow, and I'm hoping that once I have the renderer fully completed I can refine the architecture to something more elegant.

HUD Updates and Timing Bugs


HUD Screenshot

The HUD now renders armor, health and current ammo counts in addition to the per-ammo type display at the top. The latter uses conchars, which, as the name suggests, are used for rendering text to the in-game console. Now that I can load and display these I can start working on the console, which ought to make debugging a great deal easier.

Unfortunately, the client is still plagued by a bug with position lerping that causes the geometry to jitter back and forth. This is most likely caused by bad time delta calculations in Client::update_time() (Github), but I haven't been able to pinpoint the exact problem -- only that the lerp factor seems to go out of the expected range of [0, 1) once per server frame. I'll keep an eye on it.

The New Site and the Way Forward


I've started rebuilding the site with Gutenberg now that I actually have something to show for the past couple years (!) of on-and-off work. I figure a dev blog will be good to have when I look back on this project, even if I don't update it that often (the blog, not the project). It'll probably take me a while to get the site in order since my HTML/CSS skills are rusty, but the old site was impossible to maintain so this ought to make things easier.

As for the project itself, the client is coming along nicely -- I'm hoping to reach a playable state by the end of the year, even if there are still some graphical bugs. Now that the infrastructure is there for networking, input, rendering, and sound, I can start work on the little things. The devil is in the details, etc.

Defining the ultimate scope of the first alpha release is probably going to be one of the biggest challenges. There are so many features I could add to the engine, and I suspect many of them are far more complicated than they seem on the surface. Failed past projects have taught me to be wary of feature creep, so the alpha will most likely just be a working client and server -- no tools, no installers, no plugin support or anything like that. With any luck I'll be there soon.