How best to refactor this game engine?

I know the code needs to be refactored to be easier to follow and nicer to read. The goal is to keep the code as legible as possible to reduce technical debt. The game engine itself is supposed to allow people to write mods for it across all the officially supported platforms and also allow transferring items/entities between servers and single player. These platforms are Linux, Windows, Android, and WebAssembly for Browser. Wasm is mainly cause I want to have a format that functions like a universal binary and I like being able to use the browser console to call arbitrary functions.

Ideally, I'd like to stick with the crates which are the main glue crate, the server crate, the client crate, and the utility crate which is shared between the 3 other crates, but if it's not possible to refactor without adding crates, then I can look into adding more crates. The current setup is so I can use the same server code on both single player and dedicated servers.

The repo is at GitHub - Foxgirl-Labs/catgirl-engine: A game engine for cool moddability and procedurally generated data and the WebAssembly demo is at https://engine.catgirl.land/

How would I best refactor the code to be more legible and reduce technical debt?

Thanks!

Before I get to the question you asked, I wanted to comment on one of the first things I noticed in the README:

Hypothetically, if a person develops a different game, like a horror game, it should be possible to join the server for that game from any other game made on the engine including the built in game.

Does this mean the game engine is sort of like RPG Maker? As in, a complete, self-contained runtime. And all of the assets and code are dynamically loadable from servers? Maybe something like a small scale Roblox? (This is really not my cup of tea. I'm an independent game developer, FWIW.)

I'll keep my suggestions short by focusing on the most important issues:

  1. All of the documentation builds on docs.rs appears to be broken except for the utils crate.

  2. The WASM demo doesn't work on Chrome or Safari (macOS; See screenshot of console). It shows an empty canvas with purple background on Firefox, without the console errors.

  3. I don't see any game code. Or much of an engine, really. Is this still in the early design stage?

  4. The server game loop is a stub that looks like it intentionally does not share any code with the client. Is this right? Have you weighed the pros and cons of this strategy before settling on it?

    • I am curious how you plan to make this scale. Take Godot for instance; one of the benefits is that you can run the same executable in headless mode (without the renderer) and you get a server. Most serious game engines are designed this way, AFAIK.

That is to say, I don't know if there is much to consider for refactoring at this time.


WASM errors in Chrome:

I haven't worked out the primary server system yet. I'm leaning towards the way Mojang handles servers, but do also want to make sure the game engine will continue working even if I no longer host auth servers. Assets/code would be transferred from the local server to client and vice versa for any modded items. Otherwise, it'd be in an assets directory or other local storage option. I haven't figured out how best to sandbox it, but am thinking about using something like the Rhai crate or potentially implementing something like datapacks on Minecraft. I know Starbound sends json between servers and clients for items and also allows transferring items between single player and servers. I just want to take it a step further and allow fully modded sandboxed items which can transfer between the two.

The purple screen is normal as it's still very very early in the design stage and I'm in the process of trying to get specific details of what it is that I want to do and not just the general idea of "modding, item transfer, and being able to join a different game from the same client".

The server right now is a stub as I have not gotten far enough to start designing the physics or the networking between the client and server. On the Linux and Windows builds, you can pass -s or --server to launch the engine into server only mode. I just also have the ability to generate only the server side for smaller executables.

What I mean by allowing joining a different game with the same client is, all games built for the engine should be able to talk to each other with the same protocol and any additions to the protocol should be able to be patched in. It'd give the effect of traveling through the multiverse. The engine isn't supposed to replace existing engines in any way, so I'm not concerned about corporations never picking up the engine due to people being able to use the same client from an existing game and any player data on it.

I've never used RPG Maker or Roblox, so I'm not entirely sure how exactly their server systems work.

Can you send me the version of your Chrome? I just installed it (previously was using Chromium, but mostly Firefox). I'll definitely have to take a look as I wonder if it's a browser version bug, or something more specific. I did notice my service worker to cache the page is broken on Chrome.

Version 130.0.6723.117 (Official Build) (arm64)

It seems I'm slightly out of date. Now I'm on:

Version 131.0.6778.70 (Official Build) (arm64)

Demo still has the same assertion and BorrowMut errors.

I'll have to reinstall OSX on my old Macbook Air. Unfortunately, I don't have the Apple M series processors and the latest version of Chrome for Linux is 131.0.6778.69. When I previously solved for these errors on my computer, it was because I needed to attach a surface to an adapter during the adapter requesting phase. [bug]: Wasm builds fail on WGPU>=22.0.0 with Grab WGPU Adapter failure · Issue #6490 · gfx-rs/wgpu · GitHub

I also had modified WGPU instance creation so it can detect WebGPU via wgpu::util::new_instance_with_webgpu_detection(wgpu::InstanceDescriptor::default()).

Turns out my girlfriend has an arm based Mac, so I have a direct way to debug the Chrome Mac issue. Will have to see if I have the energy tonight to work on debugging this and will also test on my Intel Mac. I've already verified I get the same errors on her Mac.

I've just tested on my Intel Mac OSX Monterrey. I had to fight the recovery os internet installer as apparently it's been broken since 2023 for High Sierra, so I just got it installed today.

Safari gives some details on why it's broken, Chrome doesn't, but Firefox works on an Intel 64 bit Mac. I've not yet had time to debug it. It does appear to be that issue where I have to specify which features the browser supports. It's in my todo list to add the ability to figure out the features programmatically.

Yeah, there is an additional error in Safari when requesting the wgpu device. This is what breaks it: catgirl-engine/client/src/window/window_state.rs at b447c5315489147974017054ec44cf46f29d615a · Foxgirl-Labs/catgirl-engine · GitHub

Conservatively, this should be Limits::downlevel_webgl2_defaults() for WASM. I'm not sure if this will fix the assertion failure, but it will fix the wgpu device limits.

2 Likes

I'm thinking of taking inspiration from how winit explicitly separates out platform specific code and also applying it to feature specific code when I go to refactor my code. For example, winit/src/platform_impl/mod.rs at master · rust-windowing/winit · GitHub.

I had to change the WGPU limits to:

wgpu::Limits {
    max_color_attachments: 4,
    ..wgpu::Limits::downlevel_webgl2_defaults()
}

The engine now runs on both Arm and Intel Macs, but the Arm Mac seems to be making the color of the background darker than it appears on all other devices. I'm not sure why cause the color is consistent on all other devices and the darkened background still shows up in the screenshot even on another device which displays the color correctly on the engine.

Arm Mac:

Linux (all other tested platforms are consistent with this):

I’m pretty sure it’s caused by the use of this SRGB function. catgirl-engine/client/src/window/events/mod.rs at c4ef77bb151b27ef5692c00dbcfa3f332dd93602 · Foxgirl-Labs/catgirl-engine · GitHub

The color space needed depends on the surface. And since you are not configuring the texture format on the surface, you should look it up with Surface::get_default_config(). In my experience, browsers support SRGB by default, and you have to ask for it specifically on desktop/mobile.

4 Likes

I see that the below code will cause the color to render correctly on the Arm Mac.

wgpu::Color {
    r: 104.0 / 255.0,
    g: 71.0 / 255.0,
    b: 141.0 / 255.0,
    a: 1.0,
}

The below code renders correctly on everything I've tested except for the Arm Mac

let ce_color: crate::render::Color = crate::render::srgb_to_linear_srgb(104, 71, 141);
crate::render::get_wgpu_color_from_ce_color(ce_color)

I've been trying to figure out what the difference between the Arm Mac and any other computer is to no avail. The below log is me digging through the functions to try to figure out how to identify the texture format. All tests say it's using Rgba8UnormSrgb. The only thing different between any of the tests is the Surface Present Mode, but that's a difference between Wasm and Native, and does not change how the color renders even when different on the same machine. I've also been looking at documents such as Texture Color Formats and Srgb conversions - gfx-rs/wgpu GitHub Wiki and What every coder should know about gamma | John Novak.

So, while I know what I need to format the color as to get it to display the color correctly on an Arm Mac, however, I currently do not know how to determine which format to use.

Texture Format: Rgba8UnormSrgb, SRGB: true, Depth Aspect: false, Color Aspect: true, Stencil Aspect: false
Surface Present Mode: Mailbox
TextureUsages(RENDER_ATTACHMENT)
Alpha Mode: Auto

The funny thing is that Rgba8UnormSrgb was not supported at all when I tried it on Edge with wgpu@0.19. It might have changed since this is an older version, and I only tried it on Windows/Edge. (It's possible that I'm hitting this bug.)

In your case, it says the format is supported, yet treats the color as if SRGB is disabled. Curious. :slight_smile:

I need to run some more tests on my side anyway, because I was shocked when I had to make this change the other day. Additionally, colors were too dark when the texture formats disagreed.

1 Like

I have an uncanny ability to find bugs in software, even when I'm trying not to, so I wouldn't be surprised if I found another one. Unfortunately, I don't know what I'm doing when it comes to graphics, so I'm not sure how to work around the bug or fix it. The same behavior occurs both for Rgba8UnormSrgb and Rgba8Unorm for me.

The only difference between the two is that the former does the non-linear transformation in hardware to/from the internal floating point color representation (in the shader, e.g. WGSL colors), and the latter does not. At least that's how it's supposed to be, modulo bugs. See WebGPU: 6.3. Texture Formats.

I know this is confusing, but bare with me. [1]

We can create a table describing how converting from a WGSL color to a texture color will affect the output:

:arrow_down: From \ To :arrow_right: "Srgb" texture color non-"Srgb" texture color
Linear WGSL color :white_check_mark: Looks good :x: Too dark
sRGB WGSL color :x: Too bright :white_check_mark: Looks good

An example will help: The input color (104, 71, 141) is an sRGB color. This is what you get out of a color picker, or when using rgb(104, 71, 141) in CSS. You can ignore the "sRGB WGSL color" row in the table for now, because you aren't using this color.

The output of your srgb_to_linear_srgb() function is in linear space. When this color is simply copied to a texture without the inverse transformation applied, it will appear too dark (that's the Linear WGSL color -> non-"Srgb" texture color case). Meanwhile if the transformation is applied, it will look correct: The light lavender color.

The to/from part of the transformation is also important. So far you are only dealing with converting WGSL colors to texture colors. The opposite also happens when converting from texture colors to WGSL colors. That can also be ignored for now because you aren't doing this latter conversion.


This all means that using Rgba8UnormSrgb surface texture format with your linear color is supposed to work. The other way to fix it is switching to Rgba8Unorm and removing the linear transformation in software. Those are the only two configurations that will work correctly.


  1. It's possible that I somehow got this completely wrong, but I'm confident this is the correct interpretation. I have also tested it on native macOS/M3 by setting the clear color with varying surface texture formats and verified all four quadrants of the table are reflected as described. It's both theoretically and empirically sound, AFAICT. But what do I know? I'm dumb as a box of rocks. ↩︎

1 Like

I think it might just be bugged. Testing with Rgba8Unorm does not allow me to get rid of the linear transformation I make. It behaves the same way as Rgba8UnormSrgb for me

I've started the refactoring process. It's going to take at least a couple of days. Maybe longer with my day job taking up most of my day.