Skip to content

Window Application interaction

Ravi Mohan edited this page Dec 4, 2020 · 3 revisions

Karma consists of several systems including Event System, Logging System and many more. The Karma application (or some Karma game) creates the Window object (which can be seen here) to, well, display the window for rendering. So the Application class holds a pointer (or smart pointer) to Window (as a private data member).

Now Window (which contains reference to various GLFW functions) basically tracks several window-events like if the mouse moved (if yes then to what location), is some key pressed or released etc. We want Application to be aware of such events because, let us say, Window registers window close event, then the Application should terminate the program. What we don't want is Window holding any pointer or reference to Application. This is done to reduce the coupling between various components of the Karma Engine.

This decoupling allows these systems to grow and change organically without affecting any of the other systems they are attached to, as long as they still send and respond to the same events as before.

So let us see how Karma Engine implements this (which is described in this tutorial). For GitHub code, I will refer to this commit and WindowsWindow (not LinuxWindow or MacWindow).

In class WindowsWindow (which is subclass of abstract Window class) we have a function definition

inline void SetEventCallback(const EventCallbackFn& callback) override
{
	m_Data.EventCallback = callback;
}
...
private:
	struct WindowData
	{
		std::string Title;
		unsigned int Width, Height;
		bool VSync;

		EventCallbackFn EventCallback;
	};

	WindowData m_Data;

where EventCallbackFn is defined in Window

using EventCallbackFn = std::function<void(Event&)>;

When a KarmaEngine window is created (in class Application) we bind a Application::OnEvent function (which is responsible for implementing the changes in the Application based on window-events) as follows

Application::Application()
{
	m_Window = std::unique_ptr<Window>(Window::Create());
	m_Window->SetEventCallback(std::bind(&Application::OnEvent, this, std::placeholders::_1));
}
...
void Application::OnEvent(Event& e)
{
	EventDispatcher dispatcher(e);
	dispatcher.Dispatch<WindowCloseEvent>(std::bind(&Application::OnWindowClose, this, std::placeholders::_1));

	KR_CORE_TRACE("{0}", e);
}

So far so good. Next in class WindowsWindow we call WindowsWindow::SetGLFWCallbacks function when the window initializes

void WindowsWindow::Init(const WindowProps& props)
{
        ...
	m_Window = glfwCreateWindow((int)props.Width, (int)props.Height, m_Data.Title.c_str(), nullptr, nullptr);
	glfwMakeContextCurrent(m_Window);
	
	// Used for event callbacks
	glfwSetWindowUserPointer(m_Window, &m_Data);
	SetVSync(true);

	// Set glfw callbacks
	SetGLFWCallbacks(m_Window);
}

The function WindowsWindow::SetGLFWCallbacks is defined as follows

void WindowsWindow::SetGLFWCallbacks(GLFWwindow* glfwWindow)
{
	...
	glfwSetWindowSizeCallback(glfwWindow, [](GLFWwindow* window, int width, int height)
	{
		WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
		data.Width = width;
		data.Height = height;

		WindowResizeEvent event(width, height);
		data.EventCallback(event);
	});
        ...
}

What this function is essentially doing is setting a Lambda, that we define so that it calls EventCallback with the Event that we have got (for this case, it is resizing event). Note that EventCallback is already bound to Application::OnEvent when initializing the window.

Thus whenever the window resized, Application::OnEvent is called without WindowsWindow knowing the existence of Application.

Now let us understand how the Karma application terminates when close button of the Karma window is pressed. We look at the Application::OnEvent function again

void Application::OnEvent(Event& e)
{
	EventDispatcher dispatcher(e);
	dispatcher.Dispatch<WindowCloseEvent>(std::bind(&Application::OnWindowClose, this, std::placeholders::_1));

	KR_CORE_TRACE("{0}", e);
}

Here the dispatcher binds the function Application::OnWindowClose to the argument of EventDispatcher::Dispatch function (in Event.h)

template<typename T>
bool Dispatch(EventFn<T> func)
{
	if (m_Event.GetEventType() == T::GetStaticType())
	{
		m_Event.m_Handled = func(*(T*)&m_Event);
		return true;
	}
	return false;
}

So whenever the type of Event matches the type of template, which in this case is WindowCloseEvent, the bounded function, which in this case is Application::OnWindowClose (which can be found here) is called.

bool Application::OnWindowClose(WindowCloseEvent& event)
{
	m_Running = false;

	return true;
}

Once m_Running is set to false, the engine loop

void Application::Run()
{
	while (m_Running)
	{
			m_Window->OnUpdate();
	}
}

is completed. Finally the main() (which can be found here) terminates

int main(int argc, char** argv)
{
	// TODO: add engine initialization code for various systems
	Karma::Log::Init();
	KR_CORE_WARN("Initialized log");
	KR_INFO("Hello Cowboy. Your lucky number is {0}", 7);
	
	auto app = Karma::CreateApplication();
	app->Run();
	delete app;
	
	return 0;
}

Clone this wiki locally