Sign in to follow this  

Starting up engine subsystems (and shutting down)

Recommended Posts

Hi all,
I was wondering, how do you handle starting up and shutting down engine's sub systems?
Recently I've started using std::unique_ptr for the different managers, being part of my renderer class.
And when the time is there, I simply call make_unique and startup the systems through their constructors.

I personally believe this is a good and clean solution.

But, reading the book "Game engine architecture", I also noticed that it's a common way to go, to not use the constructor/destructor, but have startUp and shutDown member functions. I understand this gives full control for the order in which objects are created, but from a RAII perspective and more, it just doesn't feel good that way.

What you do prefer and with which arguments?
Any input is appreciated as always.

Share this post


Link to post
Share on other sites

When possible, I prefer ctors/dtors, for the reason of being hard to mess up.

 

However, sometimes there is no way around init/deinit, one case being if systems that are constructed in parallel need to reference each other during initalization.

 

From a design point, you shouldn't perform complex behaviour in a constructor, so if your PathFinding-system needs to process a nav-mesh on creation, its another point for not using a constructor.

 

Also, construction/initialization is sometimes more than one single phase. For my game systems, I need init/uninit, to make the systems usable. Start/End are being used when the actual game is started (mostly used for editor, where a level can be rendered anytime, but play-mode has to be actived separately). Activate/Deactive are mostly for scene-bound systems, which are suspended if another scene is pushed on top (blocking menu, ...).

Just an example of what I do, but should give you an idea when it might not be enough just to have a ctor.

 

As to what arguments to pass: I rely on an abstract, plugin-based design scheme, so I cannot explicitely pass parameters to the systems. If you can, you should do it that way - pathFindSystem.Init(CollisionSystem) is the most explicit and best way to go. In my case, I need to have a generic interface for most methods. So I pass a kind of world-context object to the init-methods, where systems can access different systems that they need (depending on which level the system lies - engine, game, scene, ...).

Edited by Juliean

Share this post


Link to post
Share on other sites

I use RAII almost everywhere.

There are some exceptions, like singleton upon ID3D11Device, which is firstly created in a specific moment as first call to singleton, but destroyed by C++.

D3d11& Device()
{
	static D3d11 device;
	return device;
}
int __cdecl wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int)
try
{
	CL::MemoryLeakDetector mld; //RAII

	if (CL::IsProcessWithTheSamePathAlreadyRunning()) { return 0; }

	PF::EtwStarter etw; //RAII

	CL::InitPaths(L"s:\\tech3\\"); //Fill static std::map [id <=> full] path to resources
	GL::Device(); //First call: device creation

	PF::MarkThisThread("Main thread");
	PF::CreateProfiler(PF::ProfMode::FrameStat); //singleton

	RD::RegCfgMisc reg; //RAII

	App app{ hInstance, {1680, 920}, reg.GetSettings() }; //RAII
	app.Run();

	return 0;
}
catch (std::exception& e)
{
	MessageBox(nullptr, Core::S2W(e.what()).c_str(), L"ERROR", MB_OK | MB_ICONERROR);
	return 1;
}

And I don't see any leaks! =)

 

My rules of thumb:

1. Ctor/dtor, stack allocated in almost all cases

2. std::unique_ptr if object should be destroyed (like Game Renderer on change MSAA mode in runtime)

3. std::shared_ptr (very rare)

4. All D3D objects are owned by Microsoft::WRL::ComPtr, but if someone need just use them, pass as raw pointer like:

void MainMenuRenderer::Render(ID3D11RenderTargetView* backBufferRtv)
{
	GLR::OmRT rt{ m_context, backBufferRtv, nullptr }; //RAII, cleanup RT/DSV in the end of the block

	m_backgroundPass.Render();
	m_textRenderer.Render();
}
Edited by Happy SDE

Share this post


Link to post
Share on other sites

Yeah I use RAII / stack almost everywhere, and do a lot of work inside constructors.

However, sometimes there is no way around init/deinit, one case being if systems that are constructed in parallel need to reference each other during initalization.

In those cases, you can usually extract a third/fourth component out of the original two, and replace that two-way dependency with regular one-way dependencies. Two-way dependencies between components are generally a code smell.

Share this post


Link to post
Share on other sites
Largely what Hodgman said; I'm not adverse to using secondary init/deinit like functions, although they might well be called something else, but I would try to do as much as possible on construction.

And, just as a point of order as it was mentioned in a post above; Singletons are the devil. Not even once.

Share this post


Link to post
Share on other sites
The last time I implemented a suitably complex system like this, each component was a separate external dll loaded via loadlibrary and getprocaddress.

Each library had an init(), deinit(), and a function called query().

The init() function and deinit() function just created an instance of a class derived from a base class called Module, and deleted it accordingly.

The query function returned a static class which indicated a version, name, description and dependencies list.

The dependencies list was an array of module names that this module needed loaded first, and an ordering number that was used for conflict resolution.

There was code in the core to handle loading and unloading at runtime plus detection of circular dependencies.

In the end the core was very short and simple and the entity of the program was implemented as extensible modules.

All module methods were registered in the code where implemented, and an event and message passing system existed to send information and events between modules. Modules could create named events (much like UE4 delegates).

After all modules are loaded, a startup method is called in them all, in dependency order. At this point each module could fire its own "module X started" event, which could be handled by module Z and Y.

There's no reason a game engine can't be done the same way.

Hope this helps!

Share this post


Link to post
Share on other sites

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