I’m going to assume some knowledge of basic CMake or C++ project structure and pick a solid starting point for our project. The CMake configuration for this project has 2 basic artifacts, libevoengine.so
and the executable evo
that is linked with it. Building the initial project follows the standard cmake method:
-
cd build
-
cmake ..
-
make
To run the program, while still in build/
just execute ./evo
.
├── build/
├── engine/
│ ├── include
│ │ ├── evo.h
│ │ └── logger.h
│ ├── src/
│ │ └── logger.cpp
│ └── CMakeLists.txt
├── CMakeLists.txt
├── app.cpp
└── README.asciidoc
In this program, app.cpp
is our main
entrypoint and it includes engine/include/evo.h
that acts as a single point of inclusion for all engine header files. Right now, app.cpp
just outputs some text and waits for the user to press the enter key to exit. Output is sent to the console using the Evo::Logger
class defined in engine/include/logger.h
.
We’ll use this structure as a starting point for building out the engine design.
We’re going to start by taking the entrypoint away from our client applications and move it into the engine library itself.
.
├── build/
├── engine/
│ ├── include/
│ │ ├── entrypoint.h
│ │ ├── evo.h
│ │ └── logger.h
│ ├── src/
│ │ └── logger.cpp
│ └── CMakeLists.txt
├── CMakeLists.txt
├── app.cpp
└── README.asciidoc
#include <evo.h>
#include <iostream>
// We take control of program entry to ensure proper initialization
int main() {
Evo::Logger l("myLogger");
l.info("Press [ENTER] to exit...");
std::cin.get();
}
This makes app.cpp
pretty damn boring. However, we’re going do something neat with that in a minute.
We want to have control over program initialization, parameter parsing, and early validation. This all requires having access to the point at which the program starts. Sure the client application could define a main method and include our headers, but the only way to ensure proper engine startup is by taking control of it and treating the client application as more of an "application definition" instead of actual C++ application. To do this, let’s introduce a way for our client to hook into the engine and (in the future) extend it.
#pragma once
namespace Evo {
class Application {
public:
Application();
virtual ~Application();
};
// To be defined by the client application
Evo::Application* createApplication();
}
#include <evo.h>
class App : public Evo::Application {
public:
App();
~App();
private:
std::unique_ptr<Evo::Logger> logger;
};
#include "app.h"
App::App() {
logger = std::make_unique<Evo::Logger>("App");
logger->info("App initialized");
}
App::~App() {
logger->info("App destroyed");
}
Evo::Application* Evo::createApplication() {
return new App();
}
#include <iostream>
// We take control of program entry to ensure proper initialization
int main() {
Evo::Logger l("myLogger");
auto app = Evo::createApplication();
l.info("Press [ENTER] to exit...");
std::cin.get();
delete app;
}