Skip to content

Commit b51ffb9

Browse files
committed
feat(engine): Adds headless mode support
Implements headless mode, allowing the engine to run without a graphical interface. This is achieved by conditionally initializing the window, ImGui, and rendering components based on the `headless` setting in the `EngineSettings` struct. A new example is included, demonstrating the use of headless mode with a simple test object that runs for a limited time.
1 parent 40ea749 commit b51ffb9

5 files changed

Lines changed: 143 additions & 26 deletions

File tree

core/engine/Engine.cpp

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,17 @@ void Engine::Run() {
5656
}
5757

5858
bool Engine::Start(std::string title) {
59-
SDL_Log("Initializing Window");
60-
window = new Window(title);
61-
if (window != nullptr)
62-
SDL_Log("Window Initialized");
63-
else
64-
exit(0);
59+
if (!settings.headless) {
60+
SDL_Log("Initializing Window");
61+
window = new Window(title);
62+
if (window != nullptr)
63+
SDL_Log("Window Initialized");
64+
else
65+
exit(0);
66+
} else {
67+
SDL_Log("Starting in headless mode - no window created");
68+
window = nullptr;
69+
}
6570

6671
lastFrameTime = std::chrono::high_resolution_clock::now();
6772
SDL_Delay(1000 / targetFPS);
@@ -71,15 +76,18 @@ bool Engine::Start(std::string title) {
7176
}
7277

7378
void Engine::Tick() {
74-
// Start the Dear ImGui frame
75-
ImGui_ImplSDLRenderer2_NewFrame();
76-
ImGui_ImplSDL2_NewFrame();
77-
ImGui::NewFrame();
78-
window->Update();
79-
80-
SDL_SetRenderDrawColor(window->sdlRenderer, (Uint8)(clear_color.x * 255), (Uint8)(clear_color.y * 255), (Uint8)(clear_color.z * 255),
81-
(Uint8)(clear_color.w * 255));
82-
SDL_RenderClear(window->sdlRenderer);
79+
// Only initialize ImGui and rendering if not in headless mode
80+
if (!settings.headless) {
81+
// Start the Dear ImGui frame
82+
ImGui_ImplSDLRenderer2_NewFrame();
83+
ImGui_ImplSDL2_NewFrame();
84+
ImGui::NewFrame();
85+
window->Update();
86+
87+
SDL_SetRenderDrawColor(window->sdlRenderer, (Uint8)(clear_color.x * 255), (Uint8)(clear_color.y * 255), (Uint8)(clear_color.z * 255),
88+
(Uint8)(clear_color.w * 255));
89+
SDL_RenderClear(window->sdlRenderer);
90+
}
8391

8492
// inputs processing
8593
processInput();
@@ -94,17 +102,23 @@ void Engine::Tick() {
94102
// update
95103
for (auto go : gameObjects) go->Update(deltaTime);
96104

97-
// iterate over all game objects ui
98-
auto gos = gameObjects; // clone to prevent out of bounds access
99-
for (auto go : gameObjects) go->OnGui(window->imGuiContext); // todo: find a better way to pass imgui context
105+
// Only process GUI and rendering if not in headless mode
106+
if (!settings.headless) {
107+
// iterate over all game objects ui
108+
auto gos = gameObjects; // clone to prevent out of bounds access
109+
for (auto go : gameObjects) go->OnGui(window->imGuiContext); // todo: find a better way to pass imgui context
100110

101-
for (auto go : scriptableObjects) go->OnGui(window->imGuiContext);
111+
for (auto go : scriptableObjects) go->OnGui(window->imGuiContext);
102112

103-
// Rendering
104-
ImGui::Render();
113+
// Rendering
114+
ImGui::Render();
105115

106-
// Draw
107-
for (auto go : gameObjects) go->OnDraw(window->sdlRenderer);
116+
// Draw
117+
for (auto go : gameObjects) go->OnDraw(window->sdlRenderer);
118+
119+
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), window->sdlRenderer);
120+
SDL_RenderPresent(window->sdlRenderer);
121+
}
108122

109123
// destroy objects marked to death
110124
if (!toDestroy.empty()) {
@@ -114,9 +128,6 @@ void Engine::Tick() {
114128
}
115129
toDestroy.clear();
116130
}
117-
118-
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), window->sdlRenderer);
119-
SDL_RenderPresent(window->sdlRenderer);
120131
}
121132

122133
void Engine::Exit() {
@@ -137,6 +148,11 @@ void Engine::processInput() {
137148
// clean the state
138149
arrowInput = Vector2f();
139150

151+
// Skip input processing in headless mode
152+
if (settings.headless) {
153+
return;
154+
}
155+
140156
// process events
141157
SDL_Event event;
142158
while (SDL_PollEvent(&event)) {

core/engine/Engine.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,7 @@ class Engine {
6363
void Destroy(GameObject* go);
6464

6565
void AddScriptableObject(ScriptableObject* pObject) { scriptableObjects.insert(pObject); };
66+
67+
bool IsHeadless() const { return settings.headless; }
6668
};
6769
#endif

core/engine/EngineSettings.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ struct EngineSettings {
66
bool fullscreen : 1 = false;
77
bool vsync : 1 = true;
88
bool showWindow : 1 = true;
9+
bool headless : 1 = false;
910
};
1011

1112
#endif // MOBAGEN_ENGINESETTINGS_H

examples/headless/CMakeLists.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
file(
2+
GLOB
3+
HEADLESS_INC
4+
CONFIGURE_DEPENDS
5+
${CMAKE_CURRENT_SOURCE_DIR}/*.h
6+
${CMAKE_CURRENT_SOURCE_DIR}/*.hpp
7+
)
8+
9+
file(
10+
GLOB
11+
HEADLESS_SRC
12+
CONFIGURE_DEPENDS
13+
${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
14+
${CMAKE_CURRENT_SOURCE_DIR}/*.c
15+
)
16+
17+
install(FILES ${HEADLESS_INC} DESTINATION include/headless)
18+
19+
add_executable(headless ${HEADLESS_SRC} ${HEADLESS_INC})
20+
21+
target_include_directories(headless PUBLIC ${SDL2_INCLUDE_DIR} ${CORE_INC_DIR})
22+
target_link_libraries(headless PUBLIC SDL2-static IMGUI core)

examples/headless/main.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#define SDL_MAIN_HANDLED true
2+
#include "engine/Engine.h"
3+
#include "scene/GameObject.h"
4+
#include "SDL.h"
5+
#include <iostream>
6+
7+
// Simple test object that runs for a limited time in headless mode
8+
class HeadlessTestObject : public GameObject {
9+
private:
10+
float totalTime = 0.0f;
11+
float maxRunTime = 5.0f; // Run for 5 seconds
12+
int frameCount = 0;
13+
14+
public:
15+
HeadlessTestObject(Engine* pEngine) : GameObject(pEngine) {}
16+
17+
void Update(float deltaTime) override {
18+
totalTime += deltaTime;
19+
frameCount++;
20+
21+
// Log progress every second
22+
if (frameCount % 60 == 0) { // Assuming 60 FPS
23+
SDL_Log("Headless simulation running: %.2f seconds, frame %d", totalTime, frameCount);
24+
}
25+
26+
// Exit after maxRunTime seconds
27+
if (totalTime >= maxRunTime) {
28+
SDL_Log("Headless simulation completed after %.2f seconds (%d frames)", totalTime, frameCount);
29+
engine->Exit();
30+
}
31+
}
32+
33+
void OnDraw(SDL_Renderer* renderer) override {
34+
// This should never be called in headless mode
35+
SDL_Log("Warning: OnDraw called in headless mode!");
36+
}
37+
38+
void OnGui(ImGuiContext* context) override {
39+
// This should never be called in headless mode
40+
SDL_Log("Warning: OnGui called in headless mode!");
41+
}
42+
};
43+
44+
// Main code
45+
int main(int, char**) {
46+
SDL_Log("Creating Headless Engine");
47+
48+
// Create engine settings with headless mode enabled
49+
EngineSettings settings;
50+
settings.headless = true;
51+
settings.debug = true;
52+
53+
auto engine = new Engine(settings);
54+
SDL_Log("Headless Engine Created");
55+
56+
SDL_Log("Creating Test Object");
57+
new HeadlessTestObject(engine);
58+
SDL_Log("Test Object Created");
59+
60+
SDL_Log("Starting Headless Engine");
61+
if (engine->Start("Headless Test")) {
62+
SDL_Log("Headless Engine Started");
63+
64+
SDL_Log("Running Headless Engine");
65+
engine->Run();
66+
SDL_Log("Headless Engine Stopped");
67+
}
68+
69+
SDL_Log("Exiting Headless Engine");
70+
engine->Exit();
71+
delete engine;
72+
SDL_Log("Headless Engine Exited");
73+
74+
std::cout << "Headless simulation completed successfully!" << std::endl;
75+
return 0;
76+
}

0 commit comments

Comments
 (0)