In need of some architecture help

Started by
7 comments, last by WhiskyJoe 9 years, 9 months ago

Hey all,

For a while now I have been tinkering with my 3D engine, which is actually just a sandbox for me to try out some stuff in terms off well.. everything, not just graphics programming related, but pretty much everything. It's all for learning purposes!

When I started, I made everything (where needed) with raw pointers as I didn't have time to read up on smart pointers and I decided to dive into that now that I actually had the time to read up upon them, due to me being busy, I was kinda lost on where I was going with the engine and upon skimming through my code, I noticed I made a lot of use of singletons. Now whether they're good or bad is not really the point of this thread, but I decided I wanted to reduce them to a minimum and succeeded well enough to get the engine running like it did before and I only had one singleton left, which was my system class that is essentially the root of the engine where I initialize the window, renderer and scenemanager.

Now I got a bit curious if I could get rid of my system class being a singleton as well. I noticed I still had some dependencies that relied on system being a singleton, like my renderer class that needs a window handle to initialize DirectX11, but that was fixable by passing the right information.

Now I am facing a different kind of problem which has to do with event handling in windows. I have an abstract window class and a windows32 specific window class which both look like this:

Window.h


#ifndef _WINDOW_H_
#define _WINDOW_H_

namespace FX
{
	enum WindowEvent
	{
		eWindowEvent_Close,
		eWindowEvent_Minimize,
		eWindowEvent_Maximize,
		eWindowEvent_Resize,
		eWindowEvent_Move,
		eWindowEvent_Windowed,
		eWindowEvent_FullScreen
	};

	class System;
	class Window
	{
	public:

		virtual ~Window();
		Window();

		void Create(FX::System* a_System, const std::string& a_Title, int a_Width, int a_Height, int a_PosX, int a_PosY);

		const std::string& GetWindowTitle() const {return m_Title;}
		const int GetWindowWidth() const {return m_WindowWidth;}
		const int GetWindowHeight() const {return m_WindowHeight;}
		const int GetWindowPositionX() const {return m_WindowPositionX;}
		const int GetWindowPositionY() const {return m_WindowPositionY;}
		const bool IsFullScreen() const {return m_FullScreen;}

		virtual void SetWindowTitle(const std::string& a_Title) = 0;
		virtual void SetWindowWidth(int a_Width) = 0;
		virtual void SetWindowHeight(int a_Height) = 0;
		virtual void SetWindowPositionX(int a_X) = 0;
		virtual void SetWindowPositionY(int a_Y) = 0;
		virtual void SetFullScreen(bool a_FullScreen) = 0;
		virtual bool GetEvent() = 0;
		virtual bool ProcessEvent() = 0;

		static void WindowEventHandler(const WindowEvent a_WindowEvent, const int a_DataSize = 0, void* a_Data = NULL);

	protected:		

		std::string m_Title;

		int m_WindowWidth;
		int m_WindowHeight;
		int m_WindowPositionX;
		int m_WindowPositionY;

		bool m_FullScreen;

		FX::System* m_System;
	};
}
#endif

WindowWin32.h:


#ifndef _WINDOWWIN32_H_
#define _WINDOWWIN32_H_

#include "Window.h"

namespace FX
{
	class WindowWin32 : public Window
	{
		friend class Window;
	public:

		WindowWin32();
		virtual ~WindowWin32();

		const HINSTANCE GetHInstance() const {return m_hInstance;}
		const HWND GetHWnd() const {return m_hWnd;}
		const WNDCLASSEX* GetWndClassEx() const {return &m_WndClass;}

		void SetWindowTitle(const std::string& a_Title);
		void SetWindowWidth(int a_Width);
		void SetWindowHeight(int a_Height);
		void SetWindowPositionX(int a_X);
		void SetWindowPositionY(int a_Y);
		void SetFullScreen(bool a_FullScreen);
		bool GetEvent();
		bool ProcessEvent();

	protected:		

		void UpdatePosition();

		HINSTANCE m_hInstance;
		HWND m_hWnd;
		WNDCLASSEX m_WndClass;
		MSG m_Msg;
		DWORD m_Style;
		RECT m_Dimensions;
	};
}
#endif

In Window.h I defined a static eventhandler function that gets called by the specific windows callback like this:


LRESULT CALLBACK WndProc(HWND a_HWND, UINT a_Message, WPARAM a_Param1, LPARAM a_Param2)
{
	switch(a_Message)
	{
	case WM_CLOSE:
	case WM_QUIT:
	case WM_DESTROY:
		{
			FX::Window::WindowEventHandler(FX::eWindowEvent_Close);
			break;
		}
	case WM_KEYDOWN:
		{
			//FX::Keyboard::Get()->InjectKeyDown(a_Param1);
			break;
		}
	case WM_KEYUP:
		{
			//FX::Keyboard::Get()->InjectKeyUp(a_Param1);
			break;
		}
	case WM_MOUSEMOVE:
		{
			//FX::Mouse::Get()->InjectPosition((float)LOWORD(a_Param2), (float)LOWORD(a_Param2));
			//FX::Mouse::Get()->Clip();
			break;
		}
	case WM_SIZE:
		{
			// 			FX::int2 newsize;
			// 			newsize.x = LOWORD(a_Param2);
			// 			newsize.y = HIWORD(a_Param2);
			// 
			// 			if(a_Param1 == SIZE_MINIMIZED)
			// 			{
			// 				FX::Window::WindowEventHandler(FX::eWindowEvent_Minimize, 8, &newsize);
			// 				break;
			// 			}
			// 			else if (a_Param1 == SIZE_MAXIMIZED)
			// 			{
			// 				FX::Window::WindowEventHandler(FX::eWindowEvent_Maximize, 8, &newsize);
			// 				break;
			// 			}

			break;
		}
	case WM_ACTIVATE:
		{
			// 			bool active = ((LOWORD(a_Param1) != WA_INACTIVE) && !((BOOL)HIWORD(a_Param1)));
			// 			if(!active)
			// 			{
			// 				Rld::Mouse::Get()->Clear();
			// 			}
			break;
		}
	}
	return DefWindowProc(a_HWND, a_Message, a_Param1, a_Param2);
}

Now the initial problem lies with system being a non static member that needs to be called in the static function WindowEventHandler in the Window base class, but this is not allowed.

I could make it a non static function, but than I can't call it from windows32 specific callback anymore.

So how would (or should) I handle this situation? I could make the system class a singleton again, but I am curious how this would be implemented in a way like this. Any pointers, directions or different solutions would be much appreciated!

Advertisement

You can let windows remember some data for you that you can retrieve inside the window procedure and cast back to the type of your window class.

http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591%28v=vs.85%29.aspx

On creation, the window gets its messages in a well defined order. In particular, CreateWindowEx allows you to fetch an LPARAM to the WM_CREATE message. Pack there a pointer to your struct. You can also do something similar on the window class but I'd consider it a bit ugly.

Previously "Krohm"

You can let windows remember some data for you that you can retrieve inside the window procedure and cast back to the type of your window class.

http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591%28v=vs.85%29.aspx

This might be useful. So if I am not mistaken, I could pass in my system class and get it back with its get version?

On creation, the window gets its messages in a well defined order. In particular, CreateWindowEx allows you to fetch an LPARAM to the WM_CREATE message. Pack there a pointer to your struct. You can also do something similar on the window class but I'd consider it a bit ugly.

I might have missed something in there, but it seems to only really be useful on window creation, but doesn't really provide anything useful if I want to resize or move the window mid-session does it?

I haven't done all that much window(s) programming so I am not all that familiar with all the functionality and/or if the above mentioned solutions are commonly used or are considered best practice or not. Perhaps instead of making my code work, how would providing resizing (from WM_SIZE) functionality from a class or letting my application quit properly (WM_CLOSE/QUIT/DESTROY) be handled in a situation like this? Should the proc function be part of a class so I don't have to make use of static functions/members? Is there a general best practice at all?

Thanks for the replies so far! :)


I might have missed something in there, but it seems to only really be useful on window creation, but doesn't really provide anything useful if I want to resize or move the window mid-session does it?
What it gives you is the ability to pass there a pointer to an object you can use to interface with your "real" object so even though the WndProc is static, it can forward calls to non-static functions through a object pointer.

Previously "Krohm"

This would be one way:

Pass in instance of the window class to the lpParam parameter of CreateWindow or CreateWindowEx. This will get passed to the WM_NCCREATE message through the lParam.


m_handle = CreateWindow( 
        ...
        this );


The static message handler checks for the WM_NCCREATE message and gets the instance to the window class from the lParam and stores it in the GWL_USERDATA for the window. If it's any other message, it will get the instance to the window from the GWL_USERDATA where it was stored earlier, and then you can call the non-static message handler for the class.


LRESULT CALLBACK WindowW32::WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
    WindowW32* window = NULL;

    if ( message == WM_NCCREATE ) {
        // Store a pointer to the window in the GWL_USERDATA
        LPCREATESTRUCT createStruct = (LPCREATESTRUCT)lParam;
        window = reinterpret_cast<WindowW32*>( createStruct->lpCreateParams );
        SetWindowLong( hWnd, GWL_USERDATA, reinterpret_cast<long>( window ) );
    } else {
        // Get the window pointer from GWL_USERDATA and call the non-static message handler
        LONG windowAddress = GetWindowLong( hWnd, GWL_USERDATA );
        window = reinterpret_cast<WindowW32*>( windowAddress );
        return window->MessageHandler( hWnd, message, wParam, lParam );
    }
}

Thanks guys!

I'll try this out when I have the chance! :)

wintertime, on 21 Jul 2014 - 01:44 AM, said:

You can let windows remember some data for you that you can retrieve inside the window procedure and cast back to the type of your window class.

http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591%28v=vs.85%29.aspx

Note that it's better to use SetWindowLongPtr and GetWindowLongPtr - with GWLP_USERDATA - as also noted in the article you linked to. This avoids issues if you want to make a 64bit version of the program/game.

 

Just wanted to give a heads up that I got it working properly with your suggestions!

Thanks a bunch! :)

This topic is closed to new replies.

Advertisement