OpenRCT2's v0.4.23 Behind The Scenes

At OpenRCT2 we have been doing a monthly release since the beginning of 2024 and with releases so frequent, it's always a concern that people will not notice a change, specially if they skip a release or two. Deurklink's "what's new" videos have been of great help (thank you!) and even if some people do not watch it, we know they'll eventually find out about a new ride or feature just by playing or skimming through the changelog, but what about what's done behind the scenes? Let's dive into what the community has been doing on that front!

Reducing window invalidation

Mix has been all over the place (in a good way!) recently, mostly doing work on rides and attractions, but once he took a broom and swept the window code very hard. When the dust settled (and we stopped coughing) it was cleaner than it had ever been.

Ok, I'll stop with the analogies, what happened is that every window is actively listening to a series of game events to understand if it has to do something in response. Here are a few examples:

  1. When you resize a window, it needs to be redrawn, as the content it previously showed considered different dimensions.
  2. If the New Ride/Attraction window is open and something is invented, it should now list the newly invented thing.
  3. If the Finance tab is open, it should "livestream" every purchase and payment the game is registering as it progresses.
  4. And so on...

The easiest solution1 is to make every window react to everything, and as such they will always be "up-to-date", but that is also making the computer process a lot more stuff than it should, since the game is hardly ever broadcasting events that affect everything at every frame. The ideal solution is that each window should be carefully studied to be sensitive only to those events that are relevant to them, though this brings two difficulties with which Mix had to deal with:

  1. Actually finding out what each window needs. Examples: [a] and [b].
  2. Fixing windows who should not be reacting to a given event, but did so because it "fixed a bug"2. Examples: [c] and [d].

In one case, having the Scenery window open and not taking any action (like moving the mouse, or the window), it could be invalidating itself up to 40 times per second, and doesn't invalidate once. That's at least 40 less calculations that we're burdening your CPU with, this not only improves the performance of the game, but also makes it easier for us to debug, since the unoptimized builds will run faster3. This happened to several other windows, so the improvements stack up, thanks Mix!

Preventing unnecessary tweening of entities

In animation there is a concept called tweening, which can be poorly explained as the smoothing out of a movement or transition to make it look more natural4. Imagine you have a guest moving from the park entrance to a ride queue, you can't just remove the guest from one coordinate and make it appear in the next, because that will not look like walking. At the same time, you don't want to have to write code to describe every step it takes, so you do approximations.

In OpenRCT2 we apply tweening to all peeps and vehicles, which are the things that move the most, but then Matt found out we were doing this even when it was not needed:

  1. When the game was zoomed out - after all why smoothing out a movement that you're barely even seeing? It's like opening a map and seeing a continent, but still spending time drawing all the roads of a given town, even though it is impossible to see.
  2. When the entity was not visible - as opposed to above, where if you squint you still might be able to see5, this time the thing is not even on-screen, so why bother animating it smoothly?

As with the reduction in the window invalidation, this was all about avoiding making the code process stuff that is not needed. Thanks Matt!

Cleanup of Peep Code

In the original game, a lot of the code was shared among different entities, because of size constraints, but that didn't make up for a very readable code, nor one easy to maintain and make sense of. One such example is the Peep6 code, which would take turns depending on whether it was a guest or a staff, but always start in the same function, because in the end, they're all people, right?7

One small example is checking for every Peep whether it was a Staff or a Guest, because if it is the latter, we should update its thoughts. With the separation done by Matt it is now clear which routine belongs to whom and the game needs to do a lot less verification. In the future, this will also allow us to refactor this code knowing that we're touching either only Guest or only Staff functions.

Hidden cost of the shared_ptr abstraction

The last one is for my fellow C++ nerds out there: you know that memory management has always been pointed out to be one of the weaknesses of the language8, until C++ 11 came along and introduced smart pointers. You could then create objects and let the compiler make up the code for managing the lifetime of that object, which was great, right? Yeah, most of the times, it just so happens that these abstractions are no panacea and can introduce overhead, which is what Matt and Mix uncovered.

The PR diff is big, but just because we changed some fundamental objects like UIContext to be returned by reference, as we know exactly when they're created and deleted, such as here:

- std::shared_ptr<IAudioContext> GetAudioContext() override
+ IAudioContext& GetAudioContext() override
  {
-     return _audioContext;
+     return *_audioContext;
  }

And then we had to change tons of usages of operator -> to .  :

- GetContext()->GetUiContext()->SetKeysPressed(key, scancode);
+ GetContext()->GetUiContext().SetKeysPressed(key, scancode);

Contrary to the other topics in this post, here we had almost no difference into what the code was doing, we just changed the way the objects were being stored and retrieved, which gave a 5 FPS gain on ZehMatt's machine.

And that's a wrap, hope you can see all these differences live in the upcoming v0.4.23!

~Tupaschoal

1: And laziest too, but don't blame on us, there are A LOT of windows and events in OpenRCT2.↩︎

2: The correct would be to say it camouflaged a bug, since the window being on an invalid state due to a change that should not have affected it is undesired.↩︎

3: And for the lazy "cout debuggers" like me, it will not be crazy to add messages to be printed on window code anymore, since previously they were impossible to follow.↩︎

4: I myself didn't know what it was, and had to ask Matt.↩︎

5: Maybe not, but let's assume you would.↩︎

6: Peep being how we refer to the little people in the game↩︎

7: Even when we manually pick annoying guests and throw them into the water!↩︎

8: Funnily enough, also one of the strengths, for some.↩︎