Illusion Engine 02 - EntryPoint

Introduction

As we all know, every C++ program starts with a main function. However, for a game engine, the position of the main function, the structure of the program, and the logical connection between the engine and the game application need to be carefully designed.

To make life easier for game developers, we want to hide the details of how the engine works from them. In this way, all they need to consider is what objects are in the game world, what properties these objects have, and how the objects interact with each other.

It can be seen that the main function should be put into the game engine. Developers only need to create a game instance, describe the game world, and hand over the update, rendering, resource recovery, and all underlying operations to the engine.

Also to better integrate the engine and the game, we need to provide developers with a template of the game instance (the template here does not refer to the template in C++, but the framework in the popular sense), telling them how to describe the game world.

As a result, the including structure of the project should look like this:

  • Application.h/.cpp contains a class named Application, which is the template of the game instance. It handles how the game updates.
  • Game.cpp contains a customized class inherited from class Application. This is the file where developers describe the game world.
  • EntryPoint.h contains the main function. It is the entry of the whole program.
  • Engine.h and Core.h are both helper files. Engine.h contains all the header files that the engine has and Core.h defines some helper macros to help us to develop the engine.

Implementation

Application.h/.cpp

Firstly, we have to create two files (.cpp/.h) in Illusion/src/Engine/Core/Application/… folder. These files would contain our Application class.

Since Application is the template for the game instance, it should have the following members:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "Engine/Core/Core.h"

namespace Illusion
{
class Application
{
public:
Application();
virtual ~Application();

void Run();
};

Application* CreateApplication();
}
  • Besides basic constructer and destructer, there’s also a function called Run(), in which we want our Game starts.
  • CreateApplication() is the Creation function of the application. This function should be implement by the user themselves inside Game.cpp since we don’t know what they will call their game application and what they will do with it. There cannot be a uniform implementation of the creation function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "Application.h"

namespace Illusion
{
Application::Application()
{
}

Application::~Application()
{
}

void Application::Run()
{
while (true);
}
}
  • Run() contains the game loop which is a while-loop.

Game.cpp

Inside Game/src/… folder, create a .cpp file and name it “Game”.

  • Game.cpp contains a class called Game which is inherited from Application class. It describe what the game world looks like.
  • extern works like #include, but the range it includes is smaller than #include.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <Engine.h>
//--------------------Entry point for the application--------------------
#include <Engine/Core/EntryPoint.h>

class Game : public Illusion::Application
{
public:
Game()
{
}
~Game() {}
};

//The Creation function for the Game Application
Illusion::Application* Illusion::CreateApplication()
{
return new Game();
}

EntryPoint.h

For the entry point of the program, we have to create a .h file called “EntryPoint” in Illusion/src/Engine/Core… folder.

  • EntryPoint.h contains the main function of the whole program. It is the entry of the game.
  • CreateApplication() is declared and called here.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
extern Illusion::Application* Illusion::CreateApplication();

int main(int argc, char** argv)
{
//Creation of the Game app
auto app = Illusion::CreateApplication();

//Start the Game app
app->Run();

//Delete the Game app
delete app;

return 0;
}

Core.h

Core.h is a helper file, and it belongs to the core of the engine. So create Core.h file in Illusion/src/Engine/Core/… folder.

Core.h contains some helper macros and definitions, such as ILLUSION_CORE_ASSERTS, ENGINE_BIND_EVENT_FN(fn), and so on. We would talk about them later.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <memory>

#ifdef ILLUSION_ENABLE_ASSERTS
#define ILLUSION_CLIENT_ASSERT(x, ...) { if(!(x)) { ENGINE_CLIENT_ERROR("Assertion Failed: {0}", __VA_ARGS__); __debugbreak(); } }
#define ILLUSION_CORE_ASSERT(x, ...) { if (!(x)) { ENGINE_CORE_ERROR("Assertion Failed: {0}", __VA_ARGS__); __debugbreak(); } }
#else
#define ILLUSION_CLIENT_ASSERT(x, ...)
#define ILLUSION_CORE_ASSERT(x, ...)
#endif

//move 1 to the left by x bit, used in creating a bitfield
#define BIT(x) (1 << x)

// Macro that generate a callable function which is like a pointer to the funtion fn
#define ENGINE_BIND_EVENT_FN(fn) std::bind(&fn, this, std::placeholders::_1)

namespace Illusion
{
template<typename T>
using Scope = std::unique_ptr<T>;

template<typename T>
using Ref = std::shared_ptr<T>;
}

Engine.h

Create the Engine.h file in Illusion/src… folder. Finally, the connection work is done here. The structure mentioned in the Introduction is completed.

1
2
// FOR USE BY OTHER APPLICATIONS
#include "Engine/Core/Application/Application.h"

Pre-compile Header

Pre-compile header is know as PCH file. PCH can compile header files into intermediate objects and store them in the disk. Therefore, as long as these header files are not changed, they do not need to be recompiled in the next compilation, which can save us a lot of time. Therefore, for #include that is used in almost every .cpp file (usually C++ standard library), we can put them in the PCH for compilation.

Inside Illusion/src/Engine/… folder, create two files (.cpp/.h) and name them pch. For pch.h, enter code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#pragma once
//--------------------C++ Standard Library--------------------
#include <iostream>
#include <memory>
#include <utility>
#include <algorithm>
#include <functional>

//--------------------C++ Standard Templates--------------------
#include <string>
#include <sstream>
#include <vector>
#include <array>
#include <unordered_map>
#include <unordered_set>

#include <Windows.h>

For pch.cpp, enter code:

1
#include "pch.h"

Although the code in pch.cpp seems useless, Visual Studio needs a source file that included pch.h in order to compile the pre-compile header.

We have already enabled the pre-compile header feature for Illusion Project in the premake.lua in the Preparation.

1
2
3
-- Define the precompile header
pchheader "pch.h"
pchsource "Illusion/src/pch.cpp"

So, to use the pre-compile header, we only have to include pch.h at the start of every cpp file in Illusion Project:

1
#include "pch.h"

For the code we have now, we only need to change the Application.cpp file.

Conclusion

So far, we have completed the creation of the structure between the game and the engine, separated the tedious include work to Engine.h, and handed over the creation, update, and destruction of the game instance to EntryPoint.h in Engine.h, and abstracted the internal behavior by the Application class. After such a design, the details of engine operation are well hidden, and the work of game developers is greatly simplified.


Illusion Engine 02 - EntryPoint
https://rigel.github.io/EntryPoint/
Author
Rigel
Posted on
July 31, 2022
Licensed under