Circular Class Dependency

The Partridge Family were neither partridges nor a family. Discuss.
User avatar
BurakCanik
Posts: 250
Joined: February 8th, 2014, 9:16 pm
Location: Istanbul, Turkey

Circular Class Dependency

Post by BurakCanik » May 9th, 2017, 8:12 pm

Okay So I'm writing a little framework of my own, you know stealing stuff from a bunch of places including chili. Anyways I've got a little problem of circular class dependency. I have these 3 classes: Framework, GraphicsEngine and MainWindow. Framework owns the GraphicsEngine and also has a reference to a MainWindow instance. I create a MainWindow instance and THEN I create the Framework because it needs a handle to a window for GraphicsEngine to initialize properly.
BUT MainWindow also needs a reference to GraphicsEngine because, in its WndProc, it checks for resizing messages and has to:
1) Check if id3d11Device is in a valid state.
2) Call GraphicsEngine::OnResize() depending on the message.

Sort of like:

Code: Select all

// WM_SIZE is sent when the user resizes the window.  
		case WM_SIZE :
			...
			if( mGfx && mGfx->isD3DDeviceValid() )
			{
				...
				else if( wParam == SIZE_MAXIMIZED )
				{
					mAppPaused = false ;
					mMinimized = false ;
					mMaximized = true ;
					mGfx->OnResize() ;
				}
				...
So in a way, gfx needs a reference to main window and main window needs a reference to gfx.

My temporary solution that "I had a bad feeling about" was to:
1) Have MainWindow store a pointer to GraphicsEngine.
2) Implement a method called "SetGfx( GraphicsEngine* gfx ) in MainWindow".
3) Have the Framework call SetGfx() after initializing both GraphicsEngine and MainWindow&.
This way Framework sets MainWindow::GraphicsEngine* mGfx.

To sum up:

Code: Select all

class MainWindow
{
public :
   void SetGfx( GraphicsEngine* gfx ) { mGfx = gfx ; }
private :
   GraphicsEngine* mGfx ;
}

class Framework
{
public :
   Framework( MainWindow& wnd ) :
	mWnd( wnd ),
	mGfx( wnd, ... ),
        ...
{
	mWnd.SetGfx( &mGfx ) ;
}
private :
   MainWindow& mWnd ;
   GraphicsEngine mGfx ;
}
I wasn't happy with this solution to be honest.

Then I decided to use https://en.wikipedia.org/wiki/Observer_pattern. I made MainWindow store two Observable objects for 1) d3d11Device validity check, 2) OnResize() requests. I registered the GraphicsEngine (now a child of Observer) to those Observable objects in Framework constructor and finally, in WndProc(), I substituted the d3d11Device check and OnResize() calls with their respective Observable objects' OnNotify() calls.
I'm too lazy to share the new code but briefly, if the parameter to OnNotify( void* data = nullptr ) is left blank (defaults to nullptr), GraphicsEngine assumes an OnResize() call was requested, else it reinterprets the data to be the address of a boolean variable and fills that with (md3dDevice != nullptr).

Which solution makes sense ? Or do any of them make sense at all ? How would you gentlemen or ladies solve this dilemma ?

P.S: Now that I think about it, does a game nowadays really need resizing support :?
If real is what you can feel, smell, taste and see, then 'real' is simply electrical signals interpreted by your brain" - Morpheus

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Circular Class Dependency

Post by albinopapa » May 10th, 2017, 3:29 am

First to address the circular dependency: Use forward declarations

The problem I think you are running into is a design issue because you are wanting to keep a reference/pointer to each of the three. The MainWindow is instantiated and a reference is sent to Framework when it is instantiated, this is fine you can forward declare class MainWindow in Framework.h and #include MainWindow.h in Framework.cpp.

You want to forward the MainWindow reference to GraphicsEngine, this is also fine, same as before. Forward declare class MainWindow in GraphicsEngine.h and #include MainWindow. in GraphicsEngine.cpp.

Framework.h can #include GraphicsEngine.h and has to because you are storing a concrete GraphicsEngine object in the Framework class ( non-ref or pointer ).

The problem is now, how to keep a reference/pointer of GraphicsEngine in MainWindow.

You have expressed two ways of doing it, and both probably work. The next thing I could suggest is another level of abstraction, where another class besides the MainWindow class deals with the message loop and instantiates all three classes. I usually do this with a class called System. It instantiates all the top level objects in the application, while something like Game instantiates all the Game related stuff like Buffers and Entities.

System would be responsible for handling windows messages and calling m_win.OnResize or m_graphics.OnInvalidDevice.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
BurakCanik
Posts: 250
Joined: February 8th, 2014, 9:16 pm
Location: Istanbul, Turkey

Re: Circular Class Dependency

Post by BurakCanik » May 10th, 2017, 12:51 pm

Yeah I'm using forward declarations all over the place as you said. Both solutions work, I failed to add that part to my post :D
Using another System class is an intriguing idea, but I liked having the MainWindow own the message loop. I'll give this a thought but it seems like a logical solution. Thanks as always papa.
If real is what you can feel, smell, taste and see, then 'real' is simply electrical signals interpreted by your brain" - Morpheus

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Circular Class Dependency

Post by albinopapa » May 10th, 2017, 5:12 pm

Here's an example of what I have done: D3DFramework - GitHub repo. There are some errors in some files in the repo, but the Window and System class are the focus.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
BurakCanik
Posts: 250
Joined: February 8th, 2014, 9:16 pm
Location: Istanbul, Turkey

Re: Circular Class Dependency

Post by BurakCanik » May 10th, 2017, 6:14 pm

Cool, just what I needed. ComPtr<> is a nice touch too, I was using those but wasn't clear on some things like passing them to functions etc. I'm gonna tinker with your framework for now I think :D
If real is what you can feel, smell, taste and see, then 'real' is simply electrical signals interpreted by your brain" - Morpheus

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Circular Class Dependency

Post by albinopapa » May 10th, 2017, 6:36 pm

As far as ComPtr and passing to functions, if you pass by value, they increment the ref count upon entry and decrement on exit. If you are assigning to another ComPtr, then there will be two increments and a decrement for the one that was passed in.

If you pass by ref, then there is no increment/decrement for the pass to function, only when you assign to another ComPtr will there be a increment.

Most of the time, if the function is just going to use the underlying interface, I just pass the underlying interface instead of the whole smart pointer. If the function is going to take shared ownership, like a constructor, then I pass the ComPtr by reference.

NOTE:
I haven't fully tested everything in there. Just the 2D drawing capabilities using PutPixel ( similar to what chili covers in his 3D fundamentals tuts ). I'm working on testing 3D stuff, but doubt I'll get to that any time soon.

I really need to get back to coding another project I started about a month ago. Man that guy is going to be upset, hehe.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
BurakCanik
Posts: 250
Joined: February 8th, 2014, 9:16 pm
Location: Istanbul, Turkey

Re: Circular Class Dependency

Post by BurakCanik » May 11th, 2017, 3:26 pm

albinopapa wrote:Man that guy is going to be upset, hehe.
What guy :D

Btw, do you use steam or is there any other way to bother you that is faster than forums. I may have some questions related to your framework (currently I have one: What exactly do you use D2D for? From what I can see over a quick review is that it is being used for bitmaps 'n shit). I mean I could reverse engineer it for most of my needs I guess, but asking you is both quicker and also less error-prone. Of course I won't take much of your time, just quick questions every now and then :mrgreen:
If real is what you can feel, smell, taste and see, then 'real' is simply electrical signals interpreted by your brain" - Morpheus

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Circular Class Dependency

Post by albinopapa » May 11th, 2017, 6:36 pm

1) What guy?
I was introduced to someone who needed a programmer for a game. He is providing all the content and story, all I have to do is code it all. This make coding more fun for me since I only have to focus on coding and not think of design and coding. The reason I fell behind is because he's been unavailable for the past 3 weeks and I haven't been able to get his input on stuff. He currently owns a few car rental stores so is pretty busy.

2) What is D2D used for.
Well, D2D or Direct2D is an API designed to accelerate 2D operations sprite manipulations or web-page rendering. Basically, it uses Direct3D and the GPU as it's backbone to do the rendering when setup correctly, otherwise uses the CPU like Chili's framework. Direct2D handles rendering the bitmaps, including clipping. It has functionality to rotate, translate and scale built-in, using D2D::Matrix3x2. When combined with DirectWrite, you can use D2D to draw fonts.

I think it was designed for things like overlays for games and accelerate other GUI related stuff to give a more fuller experience to the end user. One of the best things IMO about D2D is how much quicker you can setup and use the API compared to the huge amount of setup required for D3D. So for 2D platformers and other 2D game, it really is the quicker option. You can, with a lot of know-how, extend D2D to actually use D3D pixel shaders to create any effect you would normally do in D3D.

You can use D2D to render something to a DXGISurface, then you would be able to use that as a texture on a 3D object as it's texture.

Actually, it was meant to replace GDI+, but GDI+ has loading and drawing of bitmap and font capabilities while Direct2D only renders bitmaps and fonts so you'd need to use something else like the Windows Imaging Component API to load images (or GDI+) and DirectWrite to load the fonts on your system. The differences are that D2D will render using your GPU and DirectWrite was designed to render fonts more clearly than GDI+.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
BurakCanik
Posts: 250
Joined: February 8th, 2014, 9:16 pm
Location: Istanbul, Turkey

Re: Circular Class Dependency

Post by BurakCanik » May 11th, 2017, 7:39 pm

Thorough answer, thanks. I need to do some research on font rendering (actually 2D in general). I knew I had to implement some sort of text rendering, at the very least for debugging purposes. I was getting sick of OutputDebugWindow(). I'll take a crack at it after I implement the most fundamental shizzle I think.
If real is what you can feel, smell, taste and see, then 'real' is simply electrical signals interpreted by your brain" - Morpheus

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Circular Class Dependency

Post by albinopapa » May 12th, 2017, 2:02 am

@Burak,
Burak wrote:Btw, do you use steam or is there any other way to bother you that is faster than forums
Yes, I'm also albinopapa on steam, discord and twitch/curse.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply