0
\$\begingroup\$

Sorry if this isn’t the proper spot to ask, but something I struggle to wrap my head around is the benefit of breaking down code into smaller pieces on the surface it seems to make sense I think you get more control and scalability maybe? but then I actually write code and it’s difficult to tell what should and shouldn’t be put into its own class it kind of just seems like more work and things that can fail.

For example I’ve been stumped on managing resources in my engine loading meshes, textures, etc. What’s the difference between having a single manager class that knows how to load and get any asset vs a manager that just delegates the work to loader classes or something like that? Another example I can think of is having an asset class like a mesh that’s just a pod vs knowing how to load itself.

Looking for some clarification or advice on the subject which I guess falls under designing code?

\$\endgroup\$
2
  • \$\begingroup\$ TLDR: Main reason for "benefit of breaking down code into smaller pieces" is managing complexity. The more complex something gets, the harder it is to manage. But once you split that apart into chunks - those now smaller chunks are easier to manage once again. \$\endgroup\$
    – Kromster
    Commented Mar 25 at 8:58
  • 1
    \$\begingroup\$ This is a question about general software development principles that isn't really specific to game development. So it doesn't really belong here. Also, it's a topic that is probably as old as software development itself. The question why and how to modularize code is so old and well-debated that it is IMO a waste of time to start yet another debate on the subject. A good starting point might be to google about the "single responsibility principle". \$\endgroup\$
    – Philipp
    Commented Mar 25 at 12:16

2 Answers 2

4
\$\begingroup\$

A lot of design decisions come from the needs of your project, and your experience: e.g. "I've been burned before when I used approach X to accomplish Y, I had to re-write a lot of code to get Y in, and I had to write it using approach Z" -- in this situation, you'd use again approach Z when situation Y occurs.

For example I’ve been stumped on managing resources in my engine loading meshes, textures, etc. What’s the difference between having a single manager class that knows how to load and get any asset vs a manager that just delegates the work to loader classes or something like that?

This is an example case, so I can address that specifically.

  • Retrieving yourself within one class -- it may be easier to manager and remember where to look for things in smaller classes than it is in larger classes
  • Side effects mitigation -- if you have a monolith class, and you edit the header file, chances are your header file is included in a lot of other files; in comparison, a smaller manager will likely be included in a lesser amount of files; so when you edit the header, the need for recompilation will be less painful. I agree this is not really an issue on smaller projects, but can be very annoying on bigger projects.
  • Patterns -- We like patterns. They comfort us and allow us to think less. If you have a monolith manager, you'll likely find yourself with 2-3 patterns within the same class to load 4-5 types of assets. If you take out those functions into smaller managers, you can benefit from using some kind of interfaces and/or parent class that would abstract or take care of recurring patterns.
  • Object swap/"dependency injection" -- Say you have to "manage sound", and you have your "sound managing" functions into your Monolith class, it's all fine until your program crashes because the sound device is broken/disabled on your system. You don't want it to crash, so you need to "do nothing about sound" when the device is broken. An easy approach when you use "smaller managers" is to check whether the sound device is operational, and based on that instantiate the correct class between SoundManager and SoundManagerMute, where the SoundManagerMute receives audio requests but does nothing about it.
  • Memory/cache access -- In some situations, you'll want a tight management of the memory. It is generally easier to do with smaller scoped classes.
  • Ease of team work -- When working with a team, everyone working on the Monolith class may cause headaches when you'll get to merge the code with your teammates'.

Another example I can think of is having an asset class like a mesh that’s just a pod vs knowing how to load itself.

Again this is very specific. It can depend at first on personal preferences, what you've been taught and what you've learned, then it will be morphed into something based on the project needs and experience. You can use a class at first, then you'll realize that cache misses take a toll on the performances. Then you'll decide to go ahead with an ECS, in which situation we "act on data" and so the pod will naturally come to mind.

\$\endgroup\$
2
\$\begingroup\$

Software Architecture is all about creating and using abstractions to describe what we want the hardware to do.

For instance, you probably don't even think about the abstraction provided by a simple function call like playVideo(introVideoAsset); The playVideo function hides away a lot of details about how to decode a video stream, how to render that to the hardware, and so on. But before you even get to that, the compiler is going to choose a register to place introVideoAsset where the playVideo function is going to read it; it's going to store the memory location to return to once the video finishes playing; and it's going to jump to the correct memory location for the playVideo routine.

At the point where you're trying to play the intro video, you don't want to think about any of that, so you keep it all buried under several layers of abstraction.

Breaking code into classes has exactly the same aim: to allow you to express high-level relationships between different parts of your application. For instance, maybe you need to support loading multiple different texture formats, but you also need to support using a texture once it's loaded. If all the texture loading code is in its own class, you don't need to think about it when you're just using a texture; and when you are changing that class, you can see clearly which parts are "private", and can be changed without impacting other code.

The more code you have inside one function, or one class, the harder it is to reason at a high-level about that function or class. In an ideal world, you can just look at the name, and know immediately what its high-level purpose is. At the other extreme, a class called "Utilities" forces you to look at the list of methods it contains, because the name gives you no clue.

it’s difficult to tell what should and shouldn’t be put into its own class

Yes, this is fundamentally a difficult problem. There is no perfect level of abstraction; deciding how much to break things down, and how to track the relationships, is a skill and an art. Many books have been written about approaches and patterns which people have found helpful, and there are many debates about which one is best in which circumstances.

You will find that sometimes you break things down in a way you hope will be helpful, then find it makes your life harder, and have to decide whether to go through the pain of rewriting it, or live with it. Don't be discouraged and think you're doing something fundamentally wrong; just keep learning from those mistakes, and seeking the right balance for your particular situation.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged .