Advertisement

Circular dependencies in my engine

Started by January 29, 2018 06:10 PM
3 comments, last by ryt 6 years, 10 months ago

I created a simple game engine using Allegro but I stuck because I get circular dependencies. Main classes are Game.h and Engine.h.
Engine handles game loop, updates, rendering, input. It's also configured to take these inputs from outside.
Game class creates actual Engine, runs and exits the application, but it also has a list of scenes so that it could potentially load a different scene. It passes scene updates/rendering to Engine class because Engine is built to use IEngineCore and scenes inherit from IEngineCore.
IEngineCore is just a simple interface with Destroy(), Update() and Render() methods.

The problem is that I got circular dependency. This is because I would like to change to scene 2 from scene 1. To accomplish this I though to call Game.LoadScene() from the 1-st scene so I could change it. But when I try to do that, since Game already has scenes, it doesn't allow me to call Game.LoadScene() from Scene class. Here I have a circular dependency and the app won't compile.
Do you have any thoughts on how could I call Game.LoadScene() from Scene class?

Simply; You need a system.

Detailed; You need an own dedicated system for each of your tasks. This means that rendering, logic and scene management need there own subsystems that do know as much as needed but as less as possible from each other to be able to operate in a dedicated way.

Rendering normally dosent know anything about scenes or games (or even what is a mesh) but only handles renderbuffers, texture attachments and so on (one could also argue that shaders are a subsystem from rendering) as same as your game logic normally dosent know that it should render something as same as your scene subsystem dosent know about rendering or gamelogic. Otherwise you will run into the god-object paradox where it is intended to have a the subsystems be dedicated but need knowledge of a so called god-object that itself knows anything. This makes maintaining and extending code very difficult and leads to huge refactoring every time you change something.

I personally now prefer the totally decentralized approach where my engine (I also wrote from scratch completely) is just no more than an entry point that startups the task system and initiates subsystems once at startup. Anything then runs in parallel based on events/messages and different channels.

However you implement it, you need to breakdown your classes to atomic peaces so that one subsystem has to take care of exactly one area of operation. Those subsystems may then be known by higher level systems that may themselfs be known by even higher leveled systems so you could eliminate those dependency layers.

If something gets still a circular dependency it is either bad code design or in one from thousand cases a calculated design drawback; but then you will be able to handle it ;)

Advertisement

Try using a forward declaration:


class B;

class A {
  // use B
};

class B {
  // use A
};

 

I found a solution :). While Game and Engine are separate classes, totally independent, Game has a storage for Scene-s. So something from a scene couldn't load something from Game (like LoadScene()) because it would create a circular dependency. I solved it by creating a different class Application. It's totally independent from Game and Scene, and of course Engine. Now Game and Scene include Application.h and are totally fine with it. This class has a GetSceneId() which helped me to call it from Scene to change to a different scene and it works with Game.LoadScene() to load actual scene.
Introduction of Application class looked as a bit of cheating and a possible path to wrong design. After creating it I realized that I should probably change Game class to SceneManager and Application to Game.

@Shaarigan
I agree. The system should have components (classes) that are as much as possible independent from each other. This was my initial though but somewhere in the path I got my code dependent of other code.
I took a kinda different approach from messages, I'm trying to do everything through interfaces and abstract classes, but either way I think both ways should be ok.

@a light breeze
Thanks for code sample. I completely forgot about it.

This topic is closed to new replies.

Advertisement