6
\$\begingroup\$

I am trying to redesign a home-grown hobby engine. It became apparent that it was far too tightly coupled and so I'm trying to redesign it in a more modular way. Unfortunately, I'm baffled by the practicalities of implementing some of the advice I've been reading, specifically by how little knowledge the rendering module is "supposed" to have of the rest of the solution. In my existing tightly coupled design I have a triangle of sorts between assets, instances and the renderer, and every time I try to break a link in it I feel I am losing something important. How do I break the following couplings without losing functionality or creating greater problems for myself?.

A) Between assets and the rendering module. Supposedly, the rendering module shouldn't have any knowledge of assets (or just about anything else). This is something I can accept in principle, but any attempt I make in practice, e.g. to feed geometry to the renderer, seems to follow one of two paths:

  1. Creating a file format- and graphic API- agnostic intermediate representation. This has the disadvantage that I must open a file, load it's contents into memory, convert them into the intermediate representation and then convert that into a vertex buffer. This seems wasteful on the face of it, but is even worse if the file format and graphic API happen to have complementary representations - the modularity would mean I cannot take advantage of this.
  2. Construct the vertex buffer outside of the rendering module and pass that in directly, trading one module's reliance on the existence of a particular interface for another module's reliance on, and detailed knowledge of, a particular implementation. This feels like madness to me.

B) Between instances and assets. This is basically to keep unnecessary state changes to a minimum. In my tightly coupled design there is a bidirectional link between instances and assets. Not every scene will make use of every asset, but for any given scene I know what instances are present. Whenever a specific "dirty" flag is set I run through the instances, make a list of required assets and make sure I don't load resources or bind buffers I don't need. Similarly, I don't want to be binding and unbinding buffers to draw a cat, then a dog, then another cat. This time I loop through the assets, then the instances. If I break the link in either direction I lose something. If I refactor the link out into a separate class, it feels like a lie to say I've decoupled them - I now have a third class that is every bit as dependent on two others as these would have been on each other.

C) Between instances and the rendering module. Connected to the point above. Instances contain data that the rendering module needs to render the geometry in the right place, in the right animation state, etc. If I can't pass my instances (or scene object) into the renderer then I have to supply the required information in some implementation agnostic format one instance at a time, setPosition, setRotation, etc. But as I mentioned in B) I want to be drawing all of my cat instances at once, before I start drawing the dogs. To make this work I have to either:

  1. Buffer the instance data inside the rendering module somehow and then sort the resulting list. Of course, the logical way to store this information would be in a structure of some kind... except... that structure looks suspiciously (exactly) like the instance I (for some reason) couldn't just pass to my renderer. I now have two essentially identical instance classes on either side of a module divide - this can't be what decoupling is all about, surely?. Not to mention whatever the sort might cost (and the games I'm most interested in making are fairly deep simulations, very heavy on the CPU already).

  2. Sort by asset outside of the renderer. Sure, this seems easy enough. But this is an optimisation that depends upon the implementation within the rendering module itself. This can't be what decoupling means either. If the renderer (about which I am to make no assumptions) happens to be a fall-back, immediate-mode OpenGL renderer intended for older hardware, then this might actually cost me more than it saves.


So this is where I am. I want to be clear, I am not doubting the advice, I am just at a complete loss to come up with a practical (and sane) implementation. A message/event system seems like a good fit for most communication between other modules, and I've used similar patterns to produce modular solutions in my real-work programming (physics analysis). But the sheer volume and flow of data needed by the renderer would seem to require something else. Which links would you break? And how would you claw back the lost functionality? Are there good "third options" to issues A and C?

\$\endgroup\$
  • \$\begingroup\$ Well-written, but sadly GDSE is not a good place for discussions. By intent it is a Q&A site. Can you rephrase this wall of text into a short self-contained paragraph of question? \$\endgroup\$ – Kromster says support Monica Dec 23 '14 at 13:55
  • 4
    \$\begingroup\$ @KromStern Thanks for the comment and not skipping the wall of text, but experience lurking here and elsewhere makes me worry I will get well-meaning but unhelpful answers if I don't include the detail. Other than the opening paragraph (which is basically fluff) the rest explains "what I have tried and exactly what I am trying to do"... It is meant as a very direct question ("how do I break these links without sacrificing functionality?") rather than a discussion. I will have a think about trimming it down, but I need to explain why the advice I already have isn't working for me... \$\endgroup\$ – DeveloperInDevelopment Dec 23 '14 at 14:09
  • \$\begingroup\$ Why don't you bring this up the in Game Development Chat; either you can get what you need directly there, or you can end up with a discussion that will help you narrow the focus on this question somewhat. \$\endgroup\$ – Josh Dec 23 '14 at 16:54
  • \$\begingroup\$ @JoshPetrie I really think the question length is misleading and that an answer could be framed in a few paragraphs: e.g. "Break coupling [A/B/C] because [that thing I thought was important] isn't important because [brief why], or it's functionality can be picked up by [some other module]." The level of detail in the question is intended to "narrow the answer set" and avoid creating a discussion. How could shortening the question narrow the answers? The solutions are known solutions, it's just that the sources I have found assume the implementation is obvious and gloss over these issues. \$\endgroup\$ – DeveloperInDevelopment Dec 23 '14 at 18:55
  • 1
    \$\begingroup\$ I don't think so, that's just another way to say "what's the best way." I have a few ideas though; since you don't seem keen on talking about this in the Game Development Chat, but this comment thread is getting too long, I've started this meta topic; please take further discussion there. \$\endgroup\$ – Josh Dec 23 '14 at 19:13
2
\$\begingroup\$

This not claim to be a complete answer, but trust me I were in you, I tried to make a rendering engine with similiar choices etc. Decoupling is possible until you start incurring the cost of integrating togheter the decoupled components. If at logical level, there's coupling you just can't get rid of it in the code unless you can think a way to decouple it at high level.

Many real software systems exists and are hell to change because are highly coupled, but there's no one assuring to you a less coupled solution exists. Maybe you can refactor here and there but if you have a goal that requires a certain solution, fighting to change that solution is likely to change the problem (I'm not saying your particular problem does not have a solution).

Also this is the kind of questions that is unlikely to receive an answer because everyone has his super secret rendering engine best of all and don't want to share details about underlying architecture. I'm of the opinion that something nice could born only when some people start sharing ideas and do some group brainstorming about it. Groups may be eons better than individuals. Also some of the solutions you discarded because are "bad" are in reality not so bad, I tried them and are widely used by notable frameworks and rendering engines.

  • Make a list of functional features you need (what you need really at least to show something).
  • Make a design from that without any regard to optimization.
  • You at least have something working and likely to be more maintainable (and so optimizable, if required)
  • Then search for bottlenecks (after testin with real use cases!)
  • Eventually optimize.
  • Happy nights.

The point is that over optimizing for performance is likely to miss the most importan optimization point: human.

As side point, if component A is coupled with component B, there exist the chance that the coupling can be moved into a component C (again, this not always happens, but try to look for that)

\$\endgroup\$
  • \$\begingroup\$ This is pretty much how I feel looking at the problem and having attempted to solve it in numerous ways. On the other hand, I know from the high level discussions and advice that solutions do exist - I'm just stuck on the implementation. But thanks for the response. \$\endgroup\$ – DeveloperInDevelopment Dec 23 '14 at 14:22
  • \$\begingroup\$ There's the chance that a implementation that achieve a certain architecture is not possible. Just try to make the smallest code that reproduce your problem (i.e. 3/4 classes with few methods that has the coupling you want to remove). If you can't remove that coupling in so few lines of code there's the chance it is not possible (there are many things that are prooved to be impossible), or if possible is so hard that doing it for thousand lines of code is impratical \$\endgroup\$ – GameDeveloper Dec 23 '14 at 15:04
  • \$\begingroup\$ Did you tried that at the end? It should also help you to clarify the question reducing the problem to a bare minimum so that users could help. \$\endgroup\$ – GameDeveloper Dec 26 '14 at 15:06
2
\$\begingroup\$

I won't respond to each of your particular points, because I think that you're completely on track in your current thinking. You're right to be confused about how best to escape the couplings that you mention, but what you've got to realize is that, fundamentally, some couplings are inescapable.

When module A depends on data from module B, we say that module A is coupled to module B. Now, in some cases, this coupling can be truly removed so that by a restructuring of module A's logic, it no longer depends on data from module B. At some point, however, module A can not be refined any further--it depends on SOMETHING, or else it can't do anything at all. So, some things are inexorably coupled, lest they do nothing at all.

Given things that are inexorably coupled, when an author suggests "decoupling" they really mean "indirection." As you've stumbled upon here:

I now have a third class that is every bit as dependent on two others as these would have been on each other.

You're exactly right, but you've factored out the dependency. The dependency resides in module C instead of modules A and/or B. The dependency is an unavoidable fact, but you've made it more flexible/pluggable via the indirection.

\$\endgroup\$
  • \$\begingroup\$ This is good advice to keep in mind, but while I like this as an answer, I get the distinct impression that a fairly free-standing rendering module isn't only possible - it's the done thing. I just have no idea how this is achieved... \$\endgroup\$ – DeveloperInDevelopment Dec 23 '14 at 15:51

Not the answer you're looking for? Browse other questions tagged or ask your own question.