Sign in to follow this  

Connecting the game subsystems

This topic is 4748 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Say that your game engine is made of many modules such as Renderer, SceneManager, EntityManager, etc The problem is how would to connect the modules that compose the system. I think there are basically two ways: -You can create a class thats holds them, and pass them as pointers to the other systems:
class Application
{
public:
	void Init()
	void Update();
	void Render()
	{
		m_Scene.Render(&m_Renderer, &m_ParticleManager);
	}
private:
	Renderer m_Renderer;
	SceneManager m_Scene;
	ParticleManager m_ParticleManager;
};

-You can use globals
//Renderer.h
class Renderer {...}

extern Renderer g_Renderer;

//Renderer.cpp
Renderer g_Renderer;

or
//Renderer.h
class Renderer {...}

extern Renderer* g_pRenderer;

//Renderer.cpp
static Renderer s_Renderer;
Renderer* g_pRenderer = &s_Renderer;

(This one us useful if you want to use interfaces) or, you can use a singleton
class Renderer
{
	static Renderer* Instance()
	{
		static Renderer _Instance;
		return &_Instance;
	}
};
I really guess what's the best way. At the beginning of my project I had no globals, and passed everything with parameters. The cool thing is that you immediatly knew every system that the function used, the bad thing is that I had really long calls, because some functions used 10 subsystems and I had to pass them all. Then I switched to singletons, but now I'm really asking what they offer in respect to pure globals. I'm the only programmer and I know that I should NOT create a Renderer object around in my code, so what's the problem? The global has no destruction problems (as opposed to static member pointer singletons) and it's faster to type g_pRenderer->foo() than Renderer::Instance()->foo(). It's even faster to execute.

Share this post


Link to post
Share on other sites
One way that comes to mind is to create an objectManagement class that would be a singleton. As your other modules are initialized you register them with the objectManagement class (with a name, pointer, etc). Then when you need to use a specific part of your engine you would query the objectManagement ( using the module name) for the pointer.

Renderer ren3d = objectManagement.query( "Renderer" );


The objectManagement class would handle reference counters, garbage collection, and shutdown cleanup. And since you dont need to hardcode the pointers into the objectManagement class, its easily expandable (and blackboxed).

Dunno if this is a good idea or not :) just a suggestion.

Share this post


Link to post
Share on other sites
We use an execution environment. Everyone gets a copy of the environment; on the environment, you can query for other services by name. Either services are created on-demand when asked for, or everyone is first created and put into the environment, before everyone is configured.

The most straightforward way of doing this is to derive all services from IService, and make the environment be a std::map< std::string, IService * >. IService then defines the lifecycle; something like:


class IService;
class IEnvironment {
public:
virtual void setService( char const * name, IService * svc ) = 0;
virtual IService * service( char const * name ) = 0;
};
class IService {
public:
virtual void setEnvironment( IEnvironment * env ) = 0;
virtual void start() = 0;
};
class IRenderSystem : public IService {
};
class ITextureManager : public IService {
};
class ISoundSystem : public IService {
};


After you get a specific IService by name, you can typically just static_cast<> down to the specific service interface for that name. You could use some macro-ing, and/or dynamic_cast<> asserts, to make sure it's all safe.

Typically, you'd first create everyone and add them to the environment. Then you'd do a for_each() over the environment, calling setEnvironment() on all the services within the environment. The service implementations, at this point, extract all the other services they need. Last, you do a for_each() over the environment, calling start(); that's the point when a service knows that all services are correctly configured and the system is up and running. Of course, an individual service should be able to start serving before start(), but after setEnvironment(), because other services may call it from within their start() implementations.

Share this post


Link to post
Share on other sites
hmm i use to have a renderer class, but i had pointer problems that id never seen before,

eg

renderer-> main <- somegamestuff
somegamestuff <-> renderer

caused me weird errors

so i just use the main to initialise the renderer, window, game loop etc.

then put functions in the other classes such as

draw()
run()

and i just organise those in the main, eg order to be called etc


Share this post


Link to post
Share on other sites
In the game I'm writing we are using a bunch of singleton classes and calling those singleton objects "Managers". So when the game loads up, first thing we do is create our managers, like AudioManager, VideoManager, ModeManager, DataManager, etc. Its simple and effective IMO.

Share this post


Link to post
Share on other sites
for an engine, you want more flexibility than singletons. But for a game, it's the easiest way. You just have to be careful about the order you initialise the singletons (since they are likely to cross-reference each other).

Share this post


Link to post
Share on other sites
Quote:
Original post by oliii
for an engine, you want more flexibility than singletons. But for a game, it's the easiest way. You just have to be careful about the order you initialise the singletons (since they are likely to cross-reference each other).

Is there anything wrong with manually initializing the singletons? I like to do that exactly because it'll let me specify exactly in what order they are supposed to be initialized, and if anything gets called that isn't initialized yet, at least I'll get a clean error.
So, basically, instead of

if(inst==NULL) inst=new Instance();

I'd put in

if (inst==NULL) PrintError("Instance not initialized!");

and there'd be an additional static method Init() which would just create the Instance.

Is there any reason why this would be bad practice?

Share this post


Link to post
Share on other sites
Quote:
Original post by Wuntvor
Quote:
Original post by oliii
for an engine, you want more flexibility than singletons. But for a game, it's the easiest way. You just have to be careful about the order you initialise the singletons (since they are likely to cross-reference each other).

Is there anything wrong with manually initializing the singletons? I like to do that exactly because it'll let me specify exactly in what order they are supposed to be initialized, and if anything gets called that isn't initialized yet, at least I'll get a clean error.
So, basically, instead of

if(inst==NULL) inst=new Instance();

I'd put in

if (inst==NULL) PrintError("Instance not initialized!");

and there'd be an additional static method Init() which would just create the Instance.

Is there any reason why this would be bad practice?


I'm not really sure what you mean, of course you're going to manually initialize the singletons. What oliii was getting at was that you may have two or more singletons that initialize each other. For example, if singleton class A makes a call to singleton class B in its constructor, and that call in singleton class B uses a reference to singleton class A, then since the constructor of singleton class A still hasn't finished, it will try to call the constructor again and you'll get an infinite loop of constructing. Did that make sense? Here's an example:


class Singleton_A {
int value;
public:
Singleton_A { Singleton_B *singB; singB->LoadSingAData(); }
LoadValue(int arg) { value = arg; }
};

class Singleton_B {
public:
LoadSingAData() { Singleton_A *singA; int tmp = GetDataFromFile(); singA->LoadValue(tmp); }
};


int main() {
Singleton_A SA;

Singleton_B SB; // Code never reaches this point!
return 0; // Code never reaches this point!
}



Hope that's crystal clear now. I actually had this problem a couple days ago when I was trying to load in data to my game configurations singleton class from my singleton data manager class, but it was easy to fix. You just need to be careful with what you are doing. In the code I have write now here's how I initialize my singletons:


Singleton<GameAudio> AudioManager;
Singleton<GameVideo> VideoManager;
Singleton<GameModeManager> ModeManager;

Singleton<GameData> DataManager;
Singleton<GameSettings> SettingsManager;
DataManager->LoadGameSettings(); // Initializes remaining data members of Settings Manager


Share this post


Link to post
Share on other sites
Quote:
Original post by Roots
I'm not really sure what you mean, of course you're going to manually initialize the singletons. What oliii was getting at was that you may have two or more singletons that initialize each other. For example, if singleton class A makes a call to singleton class B in its constructor, and that call in singleton class B uses a reference to singleton class A, then since the constructor of singleton class A still hasn't finished, it will try to call the constructor again and you'll get an infinite loop of constructing. Did that make sense? Here's an example:

I must have looked at weird examples then, because most singletons that I have seen actually create an instance if there isn't one already, which didn't look very practical to me.
And I am very aware of that problem, it is actually the reason why I prefer an Init() method for each of the singletons, as it'll make me aware of such problems and not just create a crash.

Share this post


Link to post
Share on other sites
Not sure if it helps, but my implementation is to have a global

CEngine engine;

which contains all of my other subsystems. When initializing something, say a sprite, I pass in a pointer to the engine. Then that sprite can do:


CSprite::Init(CEngine *engine)
{
device = engine->GetDevice();
log = engine->GetLog();

// etc.
}



the only thing to watch out for is that if you happen to release something, for example when I switch to/from full-screen I release all my resources (including the Direct3D sprite interface object that all my sprites share) you have to make sure that you re-initialize the pointer when restoring.

Example:


CSprite::Draw()
{
draw with the sprite interface pointer;
}

// if you release then recreate that sprite interface pointer, it may be in a new chunk of memory, thus you would have to re-initialize your pointers to it for all sprites

CSprite::Restore()
{
sprite interface pointer = engine->GetDXSprite();
}



Does that make sense? This setup works great for me, and enables you to avoid passing around mountains of pointers.

Share this post


Link to post
Share on other sites

This topic is 4748 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this