Question about Engine abstraction

Started by
9 comments, last by SiS-Shadowman 16 years, 10 months ago
I've a question about engine abstraction that haunts me since several days. Currently, my engine consists of several subclasses that help to initiate D3D, PhysX etc. Since these classes are members of the engine, the actual game also needs to know these classes, otherwise I get errors about unknown types, ofcourse. But since I want an abstracted Engine, the game shouldn't need to know about DirectX, PhysX, DirectInput, any shader layer from directX, at least I think it shouldn't. Its really annoying to include nearly every class for a new file, that's class is only supposed to render a box, for example. Get the idea? I don't know if this would work, but I thought about it several days. How about putting all the members of my engine into a #ifdef / #endif preprocessor block, that ONLY is true, if the engine itself is compiling, thus I would do #define ENGINE_IMPLEMENT in the engine.cpp, but not in any file of the actual game itself. I haven't tried this out, since I don't know if it would work / if it's good style / if there's any better solution. Another thing I though of, would be using a lot of void * pointers for every object of the engine, but this would mean a lot of type_casting in the engine functions itself, one can run in some problems ( like calling delete() on a void* pointer and wondering why the destructor of the class, this pointer points to isn't called *rolleyes* ). What do you suggest I should do?
Advertisement
I have a question: why are you including every class if you're not using every class? Shouldn't you be including only those classes you intend to use?

GDNet+. It's only $5 a month. You know you want it.

If hiding API dependencies is important, I would provide an abstract base class without any implementation detail, and a factory method which is then implemented as returning a reference-counted pointer (boost::shared_ptr) to an instance of an API-dependent derived class. Since the API-dependent class is defined in a file that's hidden from the user, the API dependence is hidden.
Quote:Original post by Tom
I have a question: why are you including every class if you're not using every class? Shouldn't you be including only those classes you intend to use?


just look at this source code:

class __declspec( dllexport ) CEngine{private:	HWND main_window_handle;	float m_fFrameRate;	float m_fFrameTime;	CD3D m_D3D;	CDInput m_DInput;	CPhysX m_PhysX;	CCamera m_Camera;	CConsole m_Console;	CTextureManager m_TextureManager;	CShaderManager m_ShaderManager;public:	// A lot of nice functions....}


If the NPC class wants to retrieve the frametime, it does so by calling EngineObject.frametime(). But to access the engine class, it needs its declaration, but if it then doesn't know the declaration of CPhysX or CConsole, etc... it complains of course. This is why I want to hide all these classes from the actuel game, because the engine should be the only interface for accessing ALL information about D3D, DInput, PhysX etc...

I'll try out to work with boost::shared_ptr, since all these dependencies really annoy me.
If all the engine does is provide gatekeeper access to all the subsystems, then obviously it will depend on all the subsystems. You can avoid having to pull in the #include directives for the subsystems by only using pointers/references in the engine header, as the size of a pointer is known without knowing the size of the pointee type.

I would consider renaming your "abstractions" as ToohrVyk suggests, because "CD3D" is not exactly abstract.

Also, I think your fundamental design has some flaws. If your NPC has to go all the way up to the engine object to access information, you could probably stand to do some refactoring and push some of that coupling dependency down into subsystems below the "base engine object" level.

Quote:Original post by jpetrie
If all the engine does is provide gatekeeper access to all the subsystems, then obviously it will depend on all the subsystems. You can avoid having to pull in the #include directives for the subsystems by only using pointers/references in the engine header, as the size of a pointer is known without knowing the size of the pointee type.

I'm currently working on that. Shouldn't take that long.

Quote:
I would consider renaming your "abstractions" as ToohrVyk suggests, because "CD3D" is not exactly abstract.

You're right. I should rename the classes. I was just to lazy to give them better names *g. But shouldn't take that long either.

Quote:
Also, I think your fundamental design has some flaws. If your NPC has to go all the way up to the engine object to access information, you could probably stand to do some refactoring and push some of that coupling dependency down into subsystems below the "base engine object" level.


What do you exactly mean by that? Since it's my first big game project, I'm not very experienced with it, however I'm willing to learn and refit the design (nearly) any time.

Quote:
What do you exactly mean by that? Since it's my first big game project, I'm not very experienced with it, however I'm willing to learn and refit the design (nearly) any time.

Coupling is about two entities having some kind of relationship, usually a dependency or peer-to-peer issue. In this case, your NPC has a dependency on the base engine object, because it requires that object to find out information about the frame time.

It might be better for the NPC to query this information from the game state object the NPC is a part of, or even better, for the game state object to tell the NPC what the elapsed time was (perhaps the NPC has an Update() method that is called by the game state, which passes the elapsed time). This is a more logical, narrowly-focused association, ("NPCs are part of a game state," versus "NPCs are part of the engine") and it provides an extra layer of indirection between the engine and NPC. The game state has the option of ignoring the elapsed time reported from the engine (maybe the game is paused) or distorting it (maybe its running in slow motion) and having the resulting behavior automatically passed on the the NPCs.

It also eliminates the need for you to pass around, or keep globally, the base engine object all over every class in the engine (by scoping dependencies more tightly), which can prevent you from introducing additional, even worse, dependencies out of laziness. For example, accessing your sound system directly in your rendering subsystem.
You really need to understand the concept of interfaces. The point of interfaces is to define the common contract between things and nothing more. So if you are abstracting OpenGL and Direct3D as choices of an engine, you would have an interface like IGraphicSystem, IGraphicDevice, IScene, etc.... If you need DirectInput or other input means to be abstract you might have things like IInputSystem, IInputDevice

Then the OpenGL / DirectX, DirectInput, etc implementers of each of these things your implement the interface (in C++ you would usually use inheiritance for this). so the engine does stuff like

inputSystem.ProcessInput() or whatever, but the actual method is implemted in the derived classes.

Good examples of interface based stuff are often available in .NET or Java (since they have only single inheiritance support they are FORCED to use interfaces to get most things done).
Okay, I've updated the engine. All the Engine related classes are now hidded from the actual game.

Quote:Original post by jpetrie
It might be better for the NPC to query this information from the game state object the NPC is a part of, or even better, for the game state object to tell the NPC what the elapsed time was (perhaps the NPC has an Update() method that is called by the game state, which passes the elapsed time). This is a more logical, narrowly-focused association, ("NPCs are part of a game state," versus "NPCs are part of the engine") and it provides an extra layer of indirection between the engine and NPC. The game state has the option of ignoring the elapsed time reported from the engine (maybe the game is paused) or distorting it (maybe its running in slow motion) and having the resulting behavior automatically passed on the the NPCs.


I had this idea, but this would lead to a very big argument count.
To make things easier, I wrote a CSGNode class, that functions like a tree. CSGNode's can be inserted and removed. Classes like CNPC or CItem are derived from CSGNode, so each of them has a basic set of functions.
CSGNode also provides Update() or Render() functions, declared as virtual. If I would just pass the information from the root node down to its children, I would end up with about 20 arguments about the camera position, view matrix, lighting, etc. just to render everything. Is there a way to make this easier?
Quote:
I had this idea, but this would lead to a very big argument count.
To make things easier, I wrote a CSGNode class, that functions like a tree. CSGNode's can be inserted and removed. Classes like CNPC or CItem are derived from CSGNode, so each of them has a basic set of functions.
CSGNode also provides Update() or Render() functions, declared as virtual. If I would just pass the information from the root node down to its children, I would end up with about 20 arguments about the camera position, view matrix, lighting, etc. just to render everything. Is there a way to make this easie


[rant]
First a rant, I flat out HATE that useless CSGNode stuff. If you don't know it's a class, learn your engine. Drop the C's! It's 2007 we have Intelisense! I will be dropping the C's in the reply...
[/rant]

Why would SGNode have 20 arguments? From the sounds of the class name it's a Scene Graph Node, it doesn't need ANY type of rendering information. Your Update() function should likely just take a float of delta time. If you want the object to be able to render itself then it should have access to the renderer where it can query the camera location, view matrix, and all the other stuff you're saying would be arguments in the function. You'd likely just need to pass in delta time and a reference to the Render() function. I personally have a PreRender(), Render() and PostRender() virtual functions for our objects but the preferred method is that they register themselves to be rendered by the engine itself. I only have those 3 functions for people that want to do something I never thought of, I didn't want to limit the engine. But any effect can be had by running scripts, each object can specify order and number of times they're to be run etc. So I've tested those 3 virtual functions but I don't foresee much use for them.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

This topic is closed to new replies.

Advertisement