Society GUI: Better and Faster
Some more updates this week, focussed on GUI. GUI will be a major focus for a while, and I expect multiple iterations as the first thought/design that comes to mind is not always great... Anyhoo, onwards to this week's updates.
Path visualisation
Part of the overworld generation is simulation of land and sea routes between the cities. Why not show that? So, I've added a visualisation that does exactly that. The routes are a bit transparent and a bit flashing to avoid "hiding" map features with the road network, I think it looks fine enough.
Relations visualisation
Being a data visualisation fan, I wanted to show the relations of every city to every other one. How to do that? When hovering over a city, I would change the colours of all other city dots to an appropriate colour: green for friendly and red for hostile (and some values in-between). Now unfortunately these colours are also used in biomes, and that would result in poor contrast. So I thought I'd use colorised icons, which I hastily made. I think the visualisation is now clearer (imo!), even though the icons do hide a bit of the map underneath
Territory visualisation
Yet another piece of data to visualise. I run a simulation for each city to calculate how much territory it occupies. Because this territory/influence "spread" depends on the landscape (e.g. can't cross easily mountains) the resulting territory shapes are always interesting, therefore, cool to visualise. I do have yet another flashing red effect on the territory of the selected city to signify territory. Could be better, but it's serviceable for now I think.
Tinkering with contrast/colours
Based on feedback, I tinkered a bit with the contrast of background colour and text, I think it's better now. Any tips/opinions welcome!
Performance
Oh boy, here's where the fun begins. So, to calculate all 750 routes among all cities (the edges in a delaunay triangulation) using a high-precision A* takes a little more than I'd like, especially considering that this is just a single part of the entire city generation process. So I needed to make it faster.
I have a custom profiler, but it works in C#, and I don't have a way to calculate timings of segments of in C++ code (where the paths calculations happen). So I added support of C++ section profiling in my custom C# profiler via bindings, and it works fine, but I ended up just not using that as a simple profiling process in visual studio was enough to point out the "hot" code. Apparently I was doing some quite redundant work, plus I was saving some bitmaps. Oops! Removed all that and the time was massively reduced down to 1.7 seconds. Still quite a lot. The entire simulation now takes about 3 seconds. This is good, but not good enough, and the only way forward is to move that calculation to another thread so we don't block the poor UI.
Enter async/await drama. I have little patience for irrelevant information, so while I'm looking at C#'s async/await facilities, I found loads of info but somehow I could not find a few simple guidelines with a few simple examples. So I don't read in depth any of these resources, I only live once, and I'm not going to waste time learning how to spin asynchronous servers in C# and how does the async/await code generation works under the hood. Focus. I have a task "graph": territory calculations need city positions, route calculations need city positions, and so on. This can be used to spawn tasks asynchronously with their dependencies set accordingly, so that they can be executed efficiently in other threads.
So, after a little bit of documentation reading, I wrote some implementation, which wasn't working well, so I posted a question on /r/csharp which, err, brought me hellfire. Everybody was quick to point out that pretty much everything I was doing and assuming was wrong, some a bit harsher than others. But there was a lot of "you're wrong, read more!" rather than "you're wrong, because XYZ, here's how it should be done". Long story short, and after stirring heated discussions even among others, I actually figured out what I was doing wrong thanks to some comments, corrected the code and then it worked as I expected. All of this to go from 3000ms to 2700ms. But now I learned how to chain tasks and use async/await better, which is something!
2700ms is of course still too much, but now it's run on a different thread. So it's necessary to have some indication that we're doing work. I did implement a reusable widget that, when we start a long simulation, we display some text (with callbacks for updating it) and we also disable all input during the simulation, so nobody can presss ok or cancel. Worked like a charm. Results can be seen in the video.
Finally, I wanted to optimise another process, the calculation of resources (normal and rare) for each tile. I was looking for an algorithm that can iterate over all numbers in a deterministic pseudorandom order without allocations or shuffling, and I tried one but couldn't get it to work. If you know of such an algorithm, I'd appreciate any tip/link/name.
That's all for now, see you next week!