Anatomy of a Game: Startup and Loading

  • We have updated our Community Code of Conduct. Please read through the new rules for the forum that are an integral part of Paradox Interactive’s User Agreement.
Showing developer posts only. Show all posts in this thread.

blackninja9939

Senior Programmer - Crusader Kings 3
Paradox Staff
98 Badges
Aug 28, 2013
2.414
8.541
  • Surviving Mars: Digital Deluxe Edition
  • BATTLETECH: Heavy Metal
  • BATTLETECH: Season pass
  • Battle for Bosporus
  • Crusader Kings III: Royal Edition
  • Cities: Skylines - Snowfall
  • Hearts of Iron IV: No Step Back
  • Cities: Skylines - Parklife
  • BATTLETECH - Digital Deluxe Edition
  • Age of Wonders: Planetfall Season pass
  • Stellaris: Humanoids Species Pack
  • Empire of Sin
  • Cities: Skylines - Green Cities
  • Cities: Skylines - Mass Transit
  • Age of Wonders III
  • Stellaris: Necroids
  • Hearts of Iron IV: Death or Dishonor
  • Age of Wonders: Planetfall - Revelations
  • Cities: Skylines Deluxe Edition
  • Stellaris: Federations
  • BATTLETECH: Flashpoint
  • Europa Universalis IV
  • Europa Universalis 4: Emperor
  • Island Bound
  • Hearts of Iron IV: By Blood Alone
  • Crusader Kings III
  • Stellaris: Nemesis
  • Cities: Skylines Industries
  • Cities: Skylines - Campus
  • Crusader Kings II
  • Crusader Kings II: Holy Fury
  • Imperator: Rome - Magna Graecia
  • Crusader Kings II: Charlemagne
  • Crusader Kings II: Rajas of India
  • Crusader Kings II: Sons of Abraham
  • Crusader Kings II: The Old Gods
  • Europa Universalis IV: Rights of Man
  • Europa Universalis IV: Cradle of Civilization
  • Stellaris: Synthetic Dawn
  • Surviving Mars
  • BATTLETECH
  • Europa Universalis IV: Mandate of Heaven
  • Crusader Kings II: Monks and Mystics
  • Tyranny: Archon Edition
  • Europa Universalis IV: Rule Britannia
  • Crusader Kings II: Reapers Due
  • Hearts of Iron IV: Colonel
  • Stellaris Sign-up
  • Hearts of Iron IV: Expansion Pass
  • Stellaris: Apocalypse
Hello everyone! My name is Matthew and I am a programmer on CK3, welcome to the first in a series of more technical focuses posts called Anatomy of a Game.

In this series over the summer we are going to try and pull back the curtain of game development a bit and go into more detail on how our games actually work.

What happens when you launch up the game? How do we make sure to load DLCs and mods? What actually goes on in a single day of gameplay? What makes the AI decide to brutally invade you?

I hope to answer, or at the very least shed some light on, some of these questions and more over the course of this series.

These posts are not a replacement for our more traditional Dev Diaries, and if you are looking for information or teasers related to upcoming content then this is not the thing for you. But if you are interested in the background of what makes games work or are an aspiring game developer then these should prove interesting to you!

Game Setup​

In today’s post I am going to begin at well… the beginning! When you launch the game and load in to pick your character what is it that we are actually doing?
It might sound like a simple question, as well obviously we load everything right? But how do we do that in a way that feels responsive and as fast as possible? What stuff do we actually load vs deal with later? How do mods and DLC files play into overriding things?

At a high level the flow of starting the game is:
  1. Start some base functionality in the engine in order: file system, logging, threading task system
  2. Load a library called SDL, which is a common library for accessing hardware in games
  3. Load a ton of stuff for the engine to do its job, many as background tasks on threads
  4. Create the window for the game
  5. Load an even larger ton of stuff for the actual game itself
  6. Game is now loaded and you can start playing
So why does step 1 do it sub-steps in that specific order? Its purely a dependency that makes sense if you think about it, a lot of systems do logging so but that of course needs files so we load the file system first, and we want to load a lot of things in background threaded tasks but that system is going to log so we need to do it after the logging and file system.

This is a very high level set of steps and glosses over a lot of details of course, I will be glossing over numerous details in this but feel free to ask any specific questions. The bulk of interesting things happens in steps 3 and 5, they are the biggest part of our startup loading and where most of any optimization time goes.

Why optimize load times you might ask? Well of course for you the end user whilst our load screens and main themes are great you’d probably rather be playing the game.
For us, well we start the game up a lot during any given day. And every second spent waiting for the game to load is just wasted time and money. When we run the game we also do so without the compiler having done its heavier optimisations on our code, so ours is even slower than yours.

It is of course not the core of our optimization times, we work much more on daily tick speed and framerate. But it is an area we keep in mind especially for our iteration speed payoffs in the day to day development.

How does one even optimize a lot of these things? It's generally split between making more efficient use of CPU time and reducing the time spent waiting.

For the CPU time that is a couple of things: multi-threading more work so more of the CPU is being used, optimize your lower level algorithms you use, and finally you can get really close to the hardware to optimize your CPU branch misprediction and memory cache misses.

For waiting the goal is just to do less of it. Waiting is whenever your CPU has to wait to get access to a resource, wait for some threaded work to actually be done at its deadline, and even things like reading data from the disk since that does take in which your code is doing nothing itself.

The one I am going to go into more detail here initially is higher level multithreading usage, as our loading of data on the game side is the one I’ve spent time optimizing at the beginning of the year and most of that was solved by introducing more multithreading capabilities and optimising things that were thread safe but not thread efficient.

Something to look at​

Our very first goal with loading is that we want to get a load screen up ASAP so that you know the game hasn’t crashed and burned on you, so we show the main splash screen there.
startscreen.png

This wonderful logo is covering up us trying to load everything we can and make you not think it is all crashing and burning. After we have it up we start loading some necessary game components up front like the mouse textures and the bare minimum UI files like how our text should look to show the proper load screen.

Another thing we try to load ASAP is the music database so we can get that main theme playing, cause just staring at a load screen in silence is less fun than having our composer’s wonderful music to tide you over the loading. Even though ironically one of our goals is to make you hear as little of that main theme as possible. Sorry Andreas…

Incidentally in other games if you see an un-skippable cutscene like someone opening a door to walk through, or one that you can only skip part of the way through, then in reality that is just a very pretty loading screen hiding a lot of work being done under the hood.

Thankfully games these days account for the fact that hardware gets better and lets you skip a lot of them once the loading is done, but if you go back to older games that was not a thought so many long scenes hiding a load cannot be skipped.

To take a modern example of that, the elevator rides in Mass Effect 1 where you had wonderful party banter in places like the Citadel were just loading screens hidden with narrative exposition on top, but even when running the game on modern hardware it would not let you skip them.
In the recently released Mass Effect Legendary Edition remaster they added a skip button during those scenes now as modern hardware can load everything quickly, you can of course still opt into hearing the great banter between your squad mates.

We’ve loaded a lot of stuff multithreaded until now, but from here on out everything we’re loading is threaded in the game side. And we load a lot now, things like the rest of files for the UI, data for our 3D entities, the map data, our game logic files, and then the front end and its UIs.

All of this loading is done whilst the main thread keeps making sure we intermittently render the load screen and poll the operating system for input events and promptly ignore them all.

The latter being because Windows will give you the dreaded application not responding pop up if you fail to poll the events it provides, since we are loading we cannot really do anything meaningful with any inputs so we just take and ignore them to make Windows sure we haven’t secretly crashed.

RIP_Windows.png


Also worth noting that for a good amount of stuff we don’t actually have it loaded even by the time you are at the main menu, because the average user is going to maybe decide what save they want, be a bit slow on clicking something etc. and all of that does not require us to have everything loaded. So we aim to continue loading other lower priority things in the background until we really truly need it.

One example of that is our history database, it is very large but also something we know that we do not need to get to the main menu and even then we only need if we are starting a new game. As saves contain their own history from the play time. So we keep that loading in the background giving us some nice extra free time to the point where it's probably loaded even if you do start a new game eventually anyway.

Database Loading​

So that has been a lot of words about the things we load and some reasons behind the priorities. Now I am going to pivot into a more specific example, loading our game and mod files into the various databases.

So how we get DLC and mod files working in the same level as the game is actually quite straightforward. For our file system we use an integrated and modified version of PhysicsFS, or PhysFS for short, which works very nicely by letting you mount multiple directories under one logical hierarchy you can then search through to find individual files or multiple files in the hierarchy to load from.

During startup when we set up our file system we mount our base directories for the core game, and then when we load DLCs and mods we look in the user directory and check what is enabled and then mount those directories into the same path. So searching for a folder like “common/landed_titles” we will look in both the game install directory and the directories of enabled downloaded mods and treat them as the same from the code’s perspective when we load that data into the landed title database.

So how do we actually load these databases? In the currently released version of CK3 we load them all on one of our background tasks and one by one load them in order, with the order having support for defining dependencies as some systems need to reference another. Which for a long time was “good enough”, but as we’ve added more and more scripted systems and more and more content in sheer volume to load that started to show its flaws.

It was the biggest amount of time on all our threaded loading tasks and dominated by a lot meaning that we were waiting on just that one chunk of work even after the other things were done, and in terms of optimisation opportunities it was very good because in theory the databases can be formalised in a very strict layout and we then know what we can and cannot load simultaneously.

To note this idea of loading databases in parallel would not have been doable without the work that one of our other programmers, @MatRopert the Tech Lead on HoI4, did when he was on Stellaris which improved our PhysFS file system to operate a lot better in a heavily threaded environment. As if file operations entirely were a threading bottleneck then we’ve had no gains by trying to do this anyway, that is a common problem that one bottleneck hides many others. I’d recommend checking out his blog post on it here, though note it is aimed at programmers more directly than this post.

So what was it that I did here? First step was formalising our database dependencies into a proper graph object in code that I could then operate on. Be that dumping it to disk as an image to cry at our dependencies, running automated tests on it to ensure that changes and additions do not break everything, or doing the actual loading of it all so we can play the game.

Setting up the dependency graph is straightforward, every database’s code can be analysed to see if it needs a strict dependency or not. Generally that means if the reading of the script file directly needs to then reference another database, such as reading in a key and looking it up, then it needs a strict relation to be added.

Here is the dependency graph I captured recently for just our logic, no interface or databases with icons present, and it's already quite the monster.

View attachment Logic_Dependencies.png

The loading of this graph is where we get our performance gains, since we know exactly what databases depend on what we can load in parallel databases that do NOT depend on each other, which is the bulk of databases. Then when something is loaded it can notify things dependent on it that it has been completed and if it was the last thing they needed they can start their loading and so on.

Anything with threading is always easier said than done, you’ve got to make sure your operations really don’t go around competing with each other or introduce exclusive ownership or atomic operations where necessary so things stay synchronized. And when doing so make sure that the speed ups you are doing don’t then get wiped out by all of the synchronization that you are doing.

So of course then queue up a lot of discovering of hidden dependencies I had to formalise, fixing dreaded circular dependencies, plenty of threading problems, optimizing a few more systems, and doing some iterations to squeeze all the performance we could out of it and we managed to get some nice gains!

So for some actual numbers of these changes, and others done by myself and @Meneth:
  • Internal debug builds this section took over 20 seconds to load and now it's down to under 4 seconds.
  • In the release builds it took this section down from about 7 seconds down to under a second.
So it won’t suddenly make the game load instantly and deprive you of the wonderful main theme, but it definitely should save you some time and saves us a lot of time we can then spend actually making the game instead of browsing twitter for memes or making XKCD posts proud…

compiling.png


History into reality​

Once we’re at the main menu we can do the final part of getting you into the game, loading a save or starting a new game. Of which both are pretty similar hence I am bundling them together.

Loading a save is at a high level a simple one, we create our base game state and then simply run through your save game file and read in different sections which fill in different data for every part of the game. Our saves, and all files in our games really, operate on a fairly simple syntax of data that we read in.

The saves can get pretty big, since our simulations can store a lot of numbers and data, so for a lot of things we either try to not write it to save games if it's a default value or we just cache it in game internally so it never appears in saves.

An example of this is the full modifiers that apply to a character from all sources, we do not actually write those in our normal save games because it's just so much data and is something we can rebuild entirely from the rest of the save. We only write them when performing an out of sync check and want to compare them directly to make sure none of the values differ or that our caches are incorrect.

Starting a new game is actually really just the process of making a new save based on the start date you pick and then loading that save. If you select a bookmark character then we fast track you through the lobby to that character when loading, but if you select play as anyone then really we just load up the save game and put the pick a character lobby up to you.

The initial save generation is mostly just a lot of reading history files and then filling in default data. We also do a lot of semi-randomised startup content so that not every detail of every play session is always identical, though nothing on the level of something like Stellaris where the entire starting scenario changes every time since we do want to keep a fairly consistent historical background to the playthroughs.

1626283387163.png


Reading and loading all 15 MB and the near 600 files...

If you are a modder you may notice that we've got a lot less small history files these days and instead bunch them into bigger ones. This is due to the fact that on Windows the system for opening a file is not as fast as on something like Linux or OSX so we instead aim to open fewer but bigger files. Especially since we try to do a lot of our loading more aggressively in parallel now its a boon to not be waiting on the file system, hence less tiny little files these days.

We made it!​

So we’ve made it into the game! Nice! Time to actually play it, or if you’re me doing optimisations, close it, change some code, and launch it up again a few times to measure more...

I hope you’ve enjoyed this little (can one consider like 8 pages in google docs little?) dive into some of the backing of the game and technical details of it. I’ve tried to stay fairly high level because most of you are likely not software engineers, but please do give feedback on if it's too high level or getting a bit too technical so I can adapt for the future posts!

I wanted to do a series like this because game development is a bit of a black box knowledge wise to our end users compared to other forms of entertainment like books, television or music. Everyone knows roughly what goes into writing a book or how difficult it can be to play a musical instrument, in many countries we have to study such things in school.

But game development is not like that, it is the largest entertainment industry in the world but most of the consumers don’t really know what goes into it leading to misconceptions, and I think the more we can try to reduce some of the mystique of it the easier time we’ll have being able to communicate with our fans overall. And hopefully get a few people interested in maybe pursuing a career in it.

So that is all for this week folks! Feel free to ask questions here of any technical level and I will try my best to answer them!
 
  • 130Like
  • 44Love
  • 40
  • 3
Reactions:
Very interesting post indeed! Really nice to see such stories from the technical side featured as well.
I was surprised to see SDL (I guess SDL2) being used. Do you use it just for OpenGL context initialization and sound/input, or are some basic render operations done with it?
SDL2 yeah, so actual use of it is hugely rare from our game side its mostly the very lower parts of our engine. In my experience of working with it in the engine we use it for a couple of main things, handling input events from mouse and keyboard etc, making the top level window context, and then finding out some hardware info to pass on to other renders. Oh and its clip board support too for copy paste fun!

Otherwise, to my knowledge, we do not use a huge amount of SDL itself. We've got either more specific third party libraries like fmod for audio, we just use something more standard like normal C++ threads instead of SDL's, or we've rolled our own system and add abstractions around to deal with the different operating systems ourselves.
 
  • 10
  • 8Like
  • 2
  • 1Love
Reactions:
The stories of the little hacks you have to do in a game to fix a problem that nobody would suspect except by actually trying to make a game is fun. Like Windows interpreting your lack of response to user-inputs as being an indication the program has crashed rather than there being no necessary inputs.
Very interesting stuff!
I mean, part of the "deal" a program makes by starting on Windows is "I'll always promptly respond to input".
Input should be processed frequently; preferably 60+ times a second, but at the absolute minimum once.

Windows doesn't interpret the lack of such processing as it being crashed as such, but rather as it being stalled. Which objectively it is; the input queue has remained unprocessed for too long. Because the vast majority of the time, not responding to input means something's gone wrong.
 
  • 16
  • 2Like
  • 1Love
  • 1
Reactions:
-> What is your opinion on loading and parsing all of these giant files at compile-time? Granted, I don't know how easy or even how possible that would be with this setup.
Parsing files at compile time of the source code? That'd be pretty tricky given the nature of DLC and mods and make changing a text file require rebuilding the exe, all of which somewhat defeats the purpose of dynamic data if we need to bake it back into the exe. Not to mention that would require us to constexpr all of our code, which whilst I am often in favour of doing to reduce run time issues where possible, is not doable for anything needing variable data on the heap until C++20 which nobody is widely using yet.

Do maybe mean having another step between shipping the game where we combine all the files together into some big blob of binary data that we can then read in quicker when we load? Cause we have considered that, the issue then was telling what DLCs and mods are enabled and files have been modified to switch. And since our start up these days is pretty good anyway we've not super explored a bigger paradigm shift like that. Though I know its definitely a recurring idea!

-> Would the structure of the new game generation support specifying a seed value to get reproducible starting scenarios?
For the random seed we do allow that if loading in with debug mode, the random_seed command line argument lets you specify a seed for reproducible testing. We use that in our automated benchmarks so the AI and game always behaves the same.

-> ...Have you considered using something other than SDL? Particularly when it comes to handling keyboard inputs?
I'm sure we've considered using something other than SDL, right now its already there and works just fine. On the game team side we use abstractions over it since our engine has at varying times worked on things other than just PC, for example console Stellaris, so we have a more general "input event" system we use. So if our engine team one day switches from SDL or we on the game team finds a reason to encourage them to then I'm all for it.
 
  • 9
  • 4Like
  • 1Love
Reactions:
This is some very interesting stuff. Thanks for sharing!

I know this is the CK3 forum and that's the game you're working on, but perhaps you can answer this since it seems to relate: A while back, the EU4 devs made it so you can no longer go back to the main menu if you've loaded a savegame. Instead, the program quits to the desktop and the entire application reloads. Their reasoning for doing this is because there were a lot of bugs when people loaded saves from different campaigns, and that reloading the entire application seemed to solve those issues. They knew this was a hacky way of dealing with the problem, and they tried to fix it during the 1.30 patch, but in the end they decided it was too much of a hassle.

I have two main questions:
  1. What are the technical reasons for why reloading the entire application would solve these bugs, and
  2. Why is EU4 the only PDS game that does this? Is this something that CK3 and the rest of Paradox's lineup probably should do, or is this something specific to EU4's older codebase? I don't think CK2 every did this, but it's been a while since I've loaded that game up.
EU4 is a lot older foundation code than something like CK3, the issue is the amount of game state and other application data that needs to be cleared, which if not leads to a different checksum and potentially corrupted game state (though that is rarer). Hence the relaunch to avoid that, EU4 has been doing that for quite a long time now.

We've just got cleaner flows and less tech debt surrounding that in the newer games hence not having the issue. We tear down and setup the game state and application in much more predictable and controlled ways so do not leak such different contexts into each other in different runs.

CK2 did actually have this issue, it would just block you from MP instead of doing a re-launch to clean things up. As for why that difference I would speculate its because EU4 had more things not being nicely cleaned up to the point the safest thing to ensure one could start a new game was to just relaunch everything.
 
  • 13
  • 5Like
  • 1Love
Reactions:
Is that why sometimes (not necessarily CK3) Windows shows you that small window "xy is not responding" where you can choose to kill that program while in reality simply waiting for the program be done with whatever it was doing fixes it? Because something something program Windows input stuff?
That's exactly why, yeah. "Not responding" just means that the program hasn't polled in the input queue for too long.
 
  • 15
Reactions:
This was a very interesting read and I found it well explained. Thanks for doing this.

I'm curious how launching in debug mode affects the loading of the game? Especially what files it allows you to reloaded while already in game.
So there is a difference between debug mode and the debug builds.

Debug builds are what we have internally, it is a different exe with a variety of extra harder errors and asserts as well as having optimisations done by the compiler by and large disabled. That has a lot of things and the lack of compiler optimisations makes all of the loading, and everything really, a lot slower.

Debug mode which is the in game/command line toggle effects things a lot less, the pure exe is still optimised a lot more heavily so its only more minimal overhead since it enables some more logging and other debug paths. Debug mode is what allows the hot reloading of files, we attach file watchers to the files which detect when they are saved and then reload something. In our debug builds we also have debug mode enabled by default since we also want all these debug options on top of the changed exe ones.
 
  • 10
Reactions:
Oh if this were only true...

I gave up playing Mass Effect the first time it was released because of the inability to skip through the cut scenes. I decided to try it out again with the re-release and made it about an hour before uninstalling and returning it for the same damn reason.

On Topic: What was the rationale behind packaging all the audio files into proprietary .BANK files and not providing an abstraction configuration file somewhere so that individual sound effects could be enabled/disabled via the end user?

CK2 was great about this as the sound effects were each in their own separate files, so that if one or two particular sounds were particularly annoying the user could do something about it. When CK3 1.1 came out with the constant ambient siege effect it was driving me so nuts that I eventually just ended up renaming the entire ambient .BANK file to get rid of it, along with all the other ambient sounds of course.
Huh can’t say I’ve had the same experience with my replays of the legendary edition, I’ve skipped plenty of scenes on redos.

The Bank files are because we use a much more sophisticated and better third party audio library called FMOD, our old audio used to be way way too simplistic for what our audio designers and composters wanted.

Since we're talking about the loading phase : what's up with the "Initializing Game" icon on the top left that looks like it should turn, but doesn't ? =)
I’m like 80% sure that’s just cause we’re not updating the animation system whilst we’re loading on the front end or something, is very minor hence not really looked into it.
Well with all that talk there should be more optimization of the loading times of the games even on a i9 and a m2 ssd rated at 3.5gb/sec the game takes very long to load when compared to other games.
There should always be more optimisation for everything, I think that is obvious. It’s a matter of priority.
With a good processor and ssd I’d be surprised if it takes that long to load, is sub 60 seconds on my pretty decent but by no means amazing computer and that’s in full debug mode. Release is faster by a lot.
HDDs are a lot slower and we’re aware of that, though it’s again a matter of prioritisation to work on.
The launcher is always circling the log in and if it didn't finished the circling, I can't launch the game.
And when I'm in the game, it's always disconnect or kick/ lost connection from the server?
It said I need to fill my email and pw and log in again in the game, after the launcher had already successful log in.
It's really annoy.
Do you guys need to collect the information about how many in-game characters generated by the out of marriage bang?
So that we need to always log in and play the game.
Ck2 doesn't have this problem. please fix it. There are some complain about this in steam community.
This is entirely unrelated to this dev diary and I’m not really sure what you’re talking about, make a bug report or open a support ticket with more info as they are the right places to report issues.
 
  • 6Like
  • 2
  • 1
Reactions:
I have dabbled in using various graph visualisation solutions over the years. What did you use to make this visualisation of your dependency graph? Did it convert straight from your object in code to the graph or via an intermediate text dump? And if you are permitted to answer, what class is the graph object in your code?
I used the “DOT” format as an intermediary text dump and then online tools like GraphVis and a windows explorer plug in to see that text sump visualised.

In our code our directed graph type is our own made class by our engine team, so without copy pasting a lot of code is hard to share anything meaningful of.

It’s core is built off of storing nodes and edges and letting you put data into each of them, combined with a policy that describes the rules for how it works. So for my graph it uses an acyclic policy that causes asserts if you try to make nodes depend on each other through some path as that would not work to load.


So for the nodes I can store things like the function they use to initialise (if any), the state they are currently in, how long they took to load etc. For my uses I didn’t have to store any data on the connecting edges since I could determine all other state and progress via the unique nodes data, but in a different method or if the system becomes more complex that’d be then doable.
 
  • 6
Reactions:
You really do not want to use debug builds while playing the game, or using any software for that matter. Debug builds might have some "additional" feature compared to release builds ( marked in c++ code with something like #ifdef DEBUG) but usually there aren't many of those. That kind of "features" are usually there to overcome some limitation of your environment, when you want some special behavior during debug. But during debug you usually want your application to behave as closer as possible to how it would actually behave.

What debug build is mostly adding additional information so execution of certain part of the code can be recognized and displayed in debugger.

For example somewhere in code you can have something like this:
Code:
auto a = 1;
auto b = a;//and after this a not used anymore in rest of the code
auto c = b;//and after this b not used anymore in rest of the code
auto d = c;//and after this c not used anymore in rest of the code
auto e = d;//and after this d not used anymore in rest of the code
auto f = e;//and after this e not used anymore in rest of the code
// now do something with f
// you will not have this example in real code, I am just showing it as example to explain what can be optimised
In release build, compiler can say, I do not need a, b, c, d, e variables anywhere, so I will replace all of this with auto f = 1; This is much faster and used much less memory. But in debug build that replacement is never done. In debug mode, you want to see when variable a get value of 1, then variable b etc. You want to know when any of those lines are executed.

Of course this is absurd example, you will never write this kind of code. But there are much more subtle optimization, where code is reduced and speed up during building release. This is just extreme example to show the point.

So if you are using software, and you are not trying to figure out why it is not working properly, you always want to use release build, and not debug build.

And about differences between "-debug_mode" and enabeling it ingame via a gui mod, I would say that there are some additional differences. File watchers are for sure attached on load time, so you will not probably have that feature with gui mod. And maybe some logging is turned on as well. I cannot come up with more ideas what "-debug_mode" can have more then gui mod. But all of that, if any additional feature exists, must be related to that flag being present during load time. And all of these are not features related to debug build, but to debug mod in release build.
To add to that, here's an actual hands-on example from CK3.
Here is the info available when I pause the execution of the game during a function that deals with handling all the data for making a frame in a Release build.
1626509590310.png

1626509599560.png

Around half the variables are just completely gone. 4 separate functions in the callstack are gone too (the ones marked "Inline Frame").
Trying to debug this is hell, so we basically only do that if there's a crash on a Release build that we have no way of reproducing on a Debug build.
In a Debug build, none of this would be gone, making my life as a programmer way easier.
 
  • 11
  • 2Like
Reactions:
You really do not want to use debug builds while playing the game, or using any software for that matter. Debug builds might have some "additional" feature compared to release builds ( marked in c++ code with something like #ifdef DEBUG) but usually there aren't many of those. That kind of "features" are usually there to overcome some limitation of your environment, when you want some special behavior during debug. But during debug you usually want your application to behave as closer as possible to how it would actually behave.

What debug build is mostly adding additional information so execution of certain part of the code can be recognized and displayed in debugger.

For example somewhere in code you can have something like this:
Code:
auto a = 1;
auto b = a;//and after this a not used anymore in rest of the code
auto c = b;//and after this b not used anymore in rest of the code
auto d = c;//and after this c not used anymore in rest of the code
auto e = d;//and after this d not used anymore in rest of the code
auto f = e;//and after this e not used anymore in rest of the code
// now do something with f
// you will not have this example in real code, I am just showing it as example to explain what can be optimised
In release build, compiler can say, I do not need a, b, c, d, e variables anywhere, so I will replace all of this with auto f = 1; This is much faster and used much less memory. But in debug build that replacement is never done. In debug mode, you want to see when variable a get value of 1, then variable b etc. You want to know when any of those lines are executed.

Of course this is absurd example, you will never write this kind of code. But there are much more subtle optimization, where code is reduced and speed up during building release. This is just extreme example to show the point.

So if you are using software, and you are not trying to figure out why it is not working properly, you always want to use release build, and not debug build.

And about differences between "-debug_mode" and enabeling it ingame via a gui mod, I would say that there are some additional differences. File watchers are for sure attached on load time, so you will not probably have that feature with gui mod. And maybe some logging is turned on as well. I cannot come up with more ideas what "-debug_mode" can have more then gui mod. But all of that, if any additional feature exists, must be related to that flag being present during load time. And all of these are not features related to debug build, but to debug mod in release build.
Very good summary, thanks for your post!

Your guess about the command line vs console command for debug mode is also mostly spot on, it just turns it on sooner so anything needing debug mode to be active during load happens. Such as attaching file watchers and doing extra logging during that time before you’d be in game with the UI setup to turn on debug mode then.
 
  • 4
Reactions:
I'm curious how the system you've set up where events, localization, and some other files can be reloaded while in game when modding works.
We have a system of file watcher set up where we use platform specific functionality wrapped into a unified interface to be notified about changes to a file path.

When it’s changed it notifies the database that then reload its, reloading is just destroying what is already there as then re-reading in the new data. To ensure that we don’t cause issues with our databases leaving dangling pointers around of things using it we destroy and recreate the data in the same memory location.

So code who are storing and using some trait from the databases for example don’t ever know that the trait has changed, just the next time they try to get the modifier a trait applies for instance then they’ll see the new values and use them.
 
  • 5
Reactions:
Will I cause a segfault if I hotload a change that replaces a one-liner script value with a 300 line formula that uses every database in the game?
Probably not. But also please don't do that.

The bit of memory reused is the statically sized part of the database object, so you using more dynamically sized memory shouldn't really affect anything.
 
  • 2Like
  • 1
Reactions:
Yeah, just wanted to chime in and say this really was fascinating! As someone only beginning to get into programming, the level of knowledge and experience at play to understand and work these systems is a bit daunting, but on the other hand your explanation of the higher level flow really makes it quite digestible.

Without knowing enough to ask a super precise question, I was curious as to how modding functions were translated from the game's base code to paradox script. Is the language at either end comparable, or are things really mutated a lot? And what sort of checks go into the process of adding some new functionality to the language, is security a big concern?
Glad you found it interesting! I definitely get the daunting nature of it, when I started learning programming a few years ago it seemed so huge and that there was so much stuff there. But the upside is that you only need to learn a little bit every day or week before you get to a critical mass of being able to code something fairly decently, and from there you can learn and refine and discover more things as you go!

I'll not answer this question too much for now because the script system as a whole is one of my planned future posts, but the highest level gist of it is that we read in the files and translate them into the objects in code for triggers and effects based on the key and store any other values need, so when it sees gold > 500 it knows to make an instance of the gold trigger and store the value 500. Every trigger and effect then knows what to do with its store data when we evaluate/execute it or build a tooltip for it. At run time we have a context that we pass through that tree to evaluate/execute something for the given character so it knows what value of gold it needs to compare against 500 for instance.
 
  • 4Like
  • 3
Reactions:
In case you didn't plan a post on it already, I'm interested to learn more about desync, and what goes into the multiplayer side of things.

Also I'd love to know more about the organisationnal aspect of your work. Like how are things prioritized, how is time allocated, how much of you and your team's time is spent actually coding vs other stuff (meetings, testing and such)? How much of that is for New Things, for bug fixing, for mod support?

As it happens, there's a programing school right where I live and work, and I know quite a few people who attended there. They learn coding skills, but I feel like they don't really learn project management, how to handle an efficient workflow as a team, and so forth.
I’ll note that down as an idea for a future one!
 
  • 6Like
  • 1
Reactions:
  • 7
Reactions:
1)The long arrow from CLifeStyleDatabase to NModifier::SDynamicModifiersForTerrainAndGovernmentTypeDependency is frightening.
2)I always have the longest load screens (because I tend not to close the browser in the background) so the main theme is properly heard... for a good ten to twenty minutes, I can make some tea listening to it ;)
3)Is the fact that you now create new game via creating a save game the reason either for the fact you can't switch the year after you chose it once in CK3 (unlike CK2 where you could do that) or for the fact that back in CK2 switching back and forth through the time created some bugs? (Or for both)
  1. It isn't toooo bad, the graph makes it look a lot more pain than it is, it all loads quite quickly now
  2. I also would think for that load time you're on a HDD which is a lot lot slower than an SSD
  3. General simplicity really (so a bit of both your points), the switching dates back and forth was always a bit buggy as it meant we need to have a game state we could modify and undo changes to per day and also just a maintenance nightmare of supporting every single date to in theory be playable despite the fact that nobody ever played outside the main start dates really. Makes it so that we can differentiate between has a game state and does not very clear cut and once we've got the game state that is the save we are using and data we have to pick characters or have someone join in an MP game.
 
  • 4
Reactions:
You mentioned Linux and the performance with respect to open small files compared to Windows. I was wondering, is there any difference in the loading time between MS Windows and Linux? Actually, it would be interesting to have an “Anatomy of a Game” regarding the port to minor platforms, the compilers used (MSVC and GCC I imagine), DirectX vs OpenGL vs Vulkan (vs Metal), etc. In my (small) experience supporting different platforms is really useful to squash hard-to-find bugs and keep the code clean enough.
I'd have to get a Linux machine or have someone do a load compare, in general Linux loads faster than Windows from my prior experience on our games by a decent amount but run time is usually pretty comparable.

As for the porting to other PC platforms there is not a whole lot to say really, we use MSVC and Clang for compilers. DirectX and OpenGL with a future looking at Vulkan/Metal but that is all on our engine side, on the game team (and even a lot of the engine code) is just abstractions over that so we never need to directly mess with that ourselves. Otherwise we generally use portable code only, or if we do need platform things then again wrap it in one API that dispatches to the platform versions.

Most of the benefit I've found is that Clang catches bugs MSVC will let through as its a bit more strict on C++'s rules sometimes (though I'm sure a few of those MSVC ignores are due to some of our compile flags there). But its never world ending stuff really, but nice to catch though for sure as its objectively more correct programming wise. Otherwise its a lot of annoying bugs that are tedious to fix that only show up for a small % of users, we still aim to fix them but platform specific bugs are a bit of a pain ;)
 
  • 3
  • 1Like
Reactions: