Initial implementation.

This commit is contained in:
Patrick 2025-03-02 16:01:30 +01:00
parent 1aa4656eb0
commit f51dd5b437
9 changed files with 503 additions and 12 deletions

66
.clang-format Normal file
View File

@ -0,0 +1,66 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: None
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterUnion: true
BeforeCatch: false
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Left
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never

View File

@ -1,10 +1,14 @@
config = {
'PROJECT_NAME': 'spp_template'
'PROJECT_NAME': 'RAID Framework',
'CXX_NO_EXCEPTIONS': True
}
env = SConscript('external/scons-plus-plus/SConscript', exports = ['config'])
env.Append(CPPPATH = [Dir('private'), Dir('public')])
# app
env = env.Module('private/spp_template/SModule')
# library
env = env.Module('private/raid/SModule')
# test app
env = env.Module('private/raid_test/SModule')
env.Finalize()

@ -1 +1 @@
Subproject commit c883f3c1c777357957e30fc44cf987e8bb62c818
Subproject commit c994752c3244fdf9835b1d3a5238094d2a799855

31
private/raid/SModule Normal file
View File

@ -0,0 +1,31 @@
Import('env')
src_files = Split("""
application.cpp
""")
lib_raid = env.UnityStaticLibrary(
name = 'RAID',
target = env['LIB_DIR'] + '/raid',
source = src_files,
dependencies = {
'fmt': {},
'mijin': {},
'imgui': {
'options': {
'docking': True,
'backends': [
'sdl3',
'opengl3'
]
}
},
'SDL': {
'min': (3,0,0)
}
}
)
env.Default(lib_raid)
Return('env')

View File

@ -0,0 +1,240 @@
#include "raid/raid.hpp"
#include <chrono>
#include <thread>
#include <fmt/base.h>
#include <SDL3/SDL.h>
#include <mijin/debug/assert.hpp>
#include <mijin/util/scope_guard.hpp>
#include <imgui.h>
#include <backends/imgui_impl_opengl3.h>
#include <backends/imgui_impl_sdl3.h>
namespace raid
{
namespace
{
constexpr int GL_COLOR_BUFFER_BIT = 0x00004000;
const char* IMGUI_GLSL_VERSION = "#version 130";
}
int Application::run(int argc, char** argv)
{
(void) argc;
(void) argv;
MIJIN_SCOPE_EXIT {
cleanup();
};
if (!initSDL())
{
return ERR_INIT_FAILED;
}
if (!initGL())
{
return ERR_INIT_FAILED;
}
if (!initImGui())
{
return ERR_INIT_FAILED;
}
while (mRunning)
{
handleSDLEvents();
if (SDL_GetWindowFlags(mWindow) & SDL_WINDOW_MINIMIZED)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
ImGui::SetNextWindowPos({0, 0});
ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);
ImGui::Begin("##main", nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration);
render();
ImGui::End();
ImGui::Render();
glClearColor(0.3f, 0.3f, 0.3f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(mWindow);
}
return 0;
}
void Application::handleMessage(const Message& message)
{
switch (message.severity)
{
case MessageSeverity::INFO:
fmt::print("INFO: {}", message.text);
break;
case MessageSeverity::WARNING:
fmt::print("WARNING: {}", message.text);
break;
case MessageSeverity::ERROR:
fmt::print(stderr, "ERROR: {}", message.text);
break;
}
}
void Application::handleSDLEvent(const SDL_Event& event)
{
switch (event.type)
{
case SDL_EVENT_QUIT:
mRunning = false;
return;
default:
ImGui_ImplSDL3_ProcessEvent(&event);
break;
}
}
bool Application::initSDL()
{
if (!SDL_Init(0))
{
msgError("Error initializing SDL: {}.", SDL_GetError());
return false;
}
// GL attributes must be set before window creation
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
// TODO: not sure if these really make sense, but they are in the example
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
const SDL_WindowFlags WINDOW_FLAGS = 0
| SDL_WINDOW_OPENGL
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_HIGH_PIXEL_DENSITY;
mWindow = SDL_CreateWindow(
/* title = */ "RAID",
/* w = */ 1280,
/* h = */ 720,
/* flags = */ WINDOW_FLAGS
);
return true;
}
bool Application::initGL()
{
mGLContext = SDL_GL_CreateContext(mWindow);
if (mWindow == nullptr)
{
msgError("Error creating SDL window: {}.", SDL_GetError());
return false;
}
SDL_GL_MakeCurrent(mWindow, mGLContext);
SDL_GL_SetSwapInterval(1); // enable vsync, at least for now
glClear = reinterpret_cast<glClear_fn_t>(SDL_GL_GetProcAddress("glClear"));
glClearColor = reinterpret_cast<glClearColor_fn_t>(SDL_GL_GetProcAddress("glClearColor"));
return true;
}
bool Application::initImGui()
{
IMGUI_CHECKVERSION(); // not exactly useful when using static libs, but won't hurt
if (ImGui::CreateContext() == nullptr)
{
msgError("Error initializing ImGui context.");
return false;
}
ImGuiIO& imguiIO = ImGui::GetIO();
imguiIO.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
imguiIO.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
// default style
ImGui::StyleColorsDark();
// init the backends
if (!ImGui_ImplSDL3_InitForOpenGL(mWindow, mGLContext))
{
msgError("Error initializing ImGui SDL3 backend.");
return false;
}
if (!ImGui_ImplOpenGL3_Init(IMGUI_GLSL_VERSION))
{
msgError("Error initializing ImGui OpenGL3 backend.");
return false;
}
// init font
imguiIO.Fonts->AddFontDefault();
return true;
}
void Application::cleanup()
{
if (ImGui::GetCurrentContext() != nullptr)
{
const ImGuiIO& imguiIO = ImGui::GetIO();
if (imguiIO.BackendRendererUserData != nullptr)
{
ImGui_ImplOpenGL3_Shutdown();
}
if (imguiIO.BackendPlatformUserData != nullptr)
{
ImGui_ImplSDL3_Shutdown();
}
ImGui::DestroyContext();
}
if (mGLContext != nullptr)
{
SDL_GL_DestroyContext(mGLContext);
}
if (mWindow != nullptr)
{
SDL_DestroyWindow(mWindow);
}
SDL_Quit();
}
void Application::handleSDLEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
handleSDLEvent(event);
}
}
void QuickApp::init(QuickAppOptions options)
{
MIJIN_ASSERT_FATAL(options.callbacks.render, "Missing render callback.");
mOptions = std::move(options);
}
void QuickApp::render()
{
mOptions.callbacks.render();
}
int runQuick(int argc, char* argv[], QuickAppOptions options)
{
QuickApp app;
app.init(std::move(options));
return app.run(argc, argv);
}
}

View File

@ -6,11 +6,12 @@ src_files = Split("""
""")
prog_app = env.UnityProgram(
name = 'Template',
target = env['BIN_DIR'] + '/spp_template',
name = 'Test App',
target = env['BIN_DIR'] + '/raid_test',
source = src_files,
dependencies = {
'mijin': {}
'mijin': {},
'RAID': {}
}
)
env.Default(prog_app)

View File

@ -0,0 +1,21 @@
#include "raid/raid.hpp"
#include <imgui.h>
namespace
{
void render()
{
ImGui::Text("hi");
}
}
int main(int argc, char* argv[])
{
return raid::runQuick(argc, argv, {
.callbacks = {
.render = &render
}
});
}

View File

@ -1,5 +0,0 @@
int main(int, char**)
{
return 0;
}

133
public/raid/raid.hpp Normal file
View File

@ -0,0 +1,133 @@
#pragma once
#if !defined(RAID_PUBLIC_RAID_RAID_HPP_INCLUDED)
#define RAID_PUBLIC_RAID_RAID_HPP_INCLUDED 1
#include <cstdint>
#include <functional>
#include <fmt/format.h>
#include <SDL3/SDL.h>
namespace raid
{
inline constexpr int ERR_INIT_FAILED = 100;
enum class MessageSeverity : unsigned char
{
INFO,
WARNING,
ERROR
};
struct Message
{
MessageSeverity severity;
const char* text;
};
class Application
{
private:
bool mRunning = true;
SDL_Window* mWindow = nullptr;
SDL_GLContext mGLContext = nullptr;
using glClear_fn_t = void (*)(std::uint32_t);
using glClearColor_fn_t = void (*)(float, float, float, float);
glClear_fn_t glClear;
glClearColor_fn_t glClearColor;
public:
virtual ~Application() = default;
[[nodiscard]]
int run(int argc, char* argv[]);
protected:
virtual void render() = 0;
virtual void handleMessage(const Message& message);
virtual void handleSDLEvent(const SDL_Event& event);
void msgInfo(const char* text)
{
handleMessage({
.severity = MessageSeverity::INFO,
.text = text
});
}
void msgWarning(const char* text)
{
handleMessage({
.severity = MessageSeverity::WARNING,
.text = text
});
}
void msgError(const char* text)
{
handleMessage({
.severity = MessageSeverity::ERROR,
.text = text
});
}
void msgInfo(const std::string& text)
{
msgInfo(text.c_str());
}
void msgWarning(const std::string& text)
{
msgWarning(text.c_str());
}
void msgError(const std::string& text)
{
msgError(text.c_str());
}
template<typename TArg, typename... TArgs>
void msgInfo(fmt::format_string<TArg, TArgs...> format, TArg&& arg, TArgs&&... args)
{
std::string text = fmt::format(format, std::forward<TArg>(arg), std::forward<TArgs>(args)...);
msgInfo(text);
}
template<typename TArg, typename... TArgs>
void msgWarning(fmt::format_string<TArg, TArgs...> format, TArg&& arg, TArgs&&... args)
{
std::string text = fmt::format(format, std::forward<TArg>(arg), std::forward<TArgs>(args)...);
msgWarning(text);
}
template<typename TArg, typename... TArgs>
void msgError(fmt::format_string<TArg, TArgs...> format, TArg&& arg, TArgs&&... args)
{
std::string text = fmt::format(format, std::forward<TArg>(arg), std::forward<TArgs>(args)...);
msgError(text);
}
private:
bool initSDL();
bool initGL();
bool initImGui();
void cleanup();
void handleSDLEvents();
};
using render_cb_t = std::function<void()>;
struct QuickAppOptions
{
struct
{
render_cb_t render;
} callbacks;
};
class QuickApp : public Application
{
private:
QuickAppOptions mOptions;
public:
void init(QuickAppOptions options);
void render() override;
};
[[nodiscard]]
int runQuick(int argc, char* argv[], QuickAppOptions options);
} // namespace raid
#endif // !defined(RAID_PUBLIC_RAID_RAID_HPP_INCLUDED)