Logic World Wednesdays: The Many Worlds Interpretation

by @MouseHatGamesDeveloper 1 month ago (edited 1 month ago)

Instanced Worlds - Jimmy

After the last few months of blog posts, you might be almost as sick of hearing about refactors as I am of coding them. But I’ve got one more big refactor to talk about today.

Logic World has a concept within the code called the “world”. This term refers to all the data associated with structures that players have built: which components have been placed where, and which component inputs/outputs have wires connecting them. The client-side world code is responsible for presenting this world data to the player. It calculates and displays the world’s 3D geometry, then keeps that 3D geometry updated.

The world is a core part of the codebase – there wouldn’t be much to do in the game if you couldn’t see anything. Almost every system in the code interacts with the world in some way. Because it is so central and so essential, the world manager was one of the first things I wrote when I started coding Logic World in late 2018. Unfortunately, as that was so long ago, I was a lot worse at writing code than I am today.

There’s an idea within object-oriented programming philosophy that you shouldn’t have global/static code. Everything should be an object – a discrete, instanced item that can be explicitly referenced and passed between functions. The most important trait of objects is that you can have multiple of them. If I have global code for my game’s boss, then I can only ever have one boss. If the code is made part of an object, then I can stick TWO bosses in my game. Instanced code is far easier to extend and add features to. It is also far easier to test.

I did not really get this concept when I first wrote the world system. I figured, there’s only ever gonna be one world, so I can simplify things by making the world code global. As you have probably guessed, that was a terrible, terrible mistake. It turns out there are actually several different parts of the game where we need 3D geometry of components and wires – where we need a world. A complete list at time of writing:

  • The main world, the place you build stuff in
  • The ghosts of components as you are placing or moving them in the main world
  • Extra placing ghosts used in grid placing
  • The transparent ghostly version of a component being resized
  • When the game renders the little images of components for the hotbar and selection menu
  • The 3D preview model of a saved subassembly

But foolish, foolish 2018 Jimmy did not foresee any of this, and he went ahead and wrote global world code. This resulted in a whole bunch of monstrously awful hacks to get the bullet points above working properly. A curated selection:

  • When placing a single component, the placing ghost was generated using the same logic as would be used in the main world, but since we couldn’t instance the main world, its code had to be copied. About 200 lines from the main world rendering code were ripped out and used for component ghosts.
    • Component-specific code affected how components were displayed – for example, code for a Circuit Board would change the color of the board depending on the board’s color data. Since component-specific code could exist both in the main world and in a weird limbo ghostly state, it all had to be written to account for both cases.
  • When loading a saved subassembly, there was no way for the game to display multiple components and wires when they were outside of the main world. As a workaround, the client would ask the server to add the subassembly to the main world, but keep it 1,000km below the ground so players wouldn’t see it. Once placed, the client would start moving the subassembly. Since the subassembly was placed in the main world now, there was no problem rendering it.
  • When rendering little images of components for the hotbar and selection menu, those 200 lines of code from the main world rendering code were copied AGAIN
    • Component-specific code ALSO had to handle this case
  • To generate the 3D board models, the game would examine what the 3D model looked like when placed in the world at the time the board was saved. There was no way for the game to generate a 3D model from saved board data if the model file got deleted.

I’ve known this was bad and terrible for a long time. But like I said, the world code is a core part of the Logic World codebase. I knew that to modify it would be a huge undertaking, and I like working on fun new things a lot more than I like fixing my old architectural mistakes, so I grit my teeth and put up with the terribleness. But while working on some new building mechanics I just couldn’t #&$*@ stand it anymore, so I grit my other set of teeth, lubed up, and dove headfirst into the gaping mouth of a literal hippopotamus. My mission: change the world code from global, to instanced.

Well, after a LOT of work, I did it, and here’s a picture to prove it!!!!

many worlds screenshot.jpg

This was absolutely crazy. Over 400 files had to be manually updated to work with the new system. But the difference in code quality is amazingly palpable, and it is going to be so easy to add cool new features now. Combing through so much of the codebase was also a great opportunity to clean up smaller systems and add some little new features to them.

In the image above, there are three worlds visible: the main world with all the components placed in it, a placing ghost world (outlined in green), and the thumbnail rendering world used for the item images on the hotbar. A month ago, 2​/3 of those features were implemented with awful gross hacks. Today 0/3 are.

Before and during this giant refactor, I was working on some nifty new features, but unfortunately the game is not in any state to show them off. You may have noticed that two non-main-world inverters in that image have erroneously off outputs; that’s just one of around a dozen remaining issues with the new feature implementations. Which is to be expected – I wrote several thousand lines of code, and I wasn’t able to test any of it while writing. I was bound to make a few mistakes. Some of those issues are truly game-breaking and need to be fixed before I can record videos again. I’ll begin my workweek by fixing those all up, and next Wednesday I should have lots to show you!

This whole journey was a bit of a nightmare. It’s pretty demoralizing when your game doesn’t even compile for a month straight. But it is OVER, and we should be smooth sailing from here on out. I believe this is the last major refactor that will be needed before Logic World 1.0. From this point forward it’s features features features baby, and GOLLY GEE WHIZ I am excited to work on those again.

Thanks for reading. I’ll see you next Wednesday :)

@Ecconia 1 month ago (edited 1 month ago)

My first thought was like “Outch” (for technical reasons and “Oh Jimmy” reasons).
But you gained a lot of XP since then, and you even have all that change documented. You can see your own progress and improvement which is super amazing.

I recall me doing very dumb stuff in the past, and I am glade I won’t do these again. My current sins are primary copy-paste bugs… And dozens of NPE’s.

It is mandatory to refactor your project every now and then - and if done well and with concept the result is mostly stunning and opens several doors.
Know and worship the power of refactoring. Especially if it should be done. Each refactoring opens new doors which can be used for great things, so keep it up until you are happy. Although it might introduce bugs and cost a lot of time.

Btw: HaVe YoU fOuNd AnD fIxEd ThE rElAy BuG?
(I will keep asking, until I see a proof that it is fixed, deal with it!)
*Answer in VC: No progress on that, cause of the refactoring. But Jimmy is pretty sure what the issue is.”

@JimmyDeveloper 1 month ago

You can see your own progress and improvement which is super amazing.

Heck yeah!!! I’m proud of how far I’ve come.

Btw: HaVe YoU fOuNd AnD fIxEd ThE rElAy BuG?

Haha yep, the transcribed answer is correct. No progress since the last time you asked; I’ve investigated and I’m pretty sure I know what the cause is but I haven’t had a chance to test a solution. Will be looking into it this week, now that the game actually compiles again lol