Big code changes

This commit is contained in:
2026-08-23 20:46:09 -07:00
parent 7243e13df5
commit 0cb0628101
25 changed files with 1645 additions and 227 deletions
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <memory>
#include <string>
namespace sophia {
class Application {
public:
struct Config {
int width;
int height;
std::string name;
bool headless = false;
};
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
Application(const Config& config);
~Application();
void run();
private:
class Impl;
std::unique_ptr<Impl> impl;
};
} // namespace sophia
+137
View File
@@ -0,0 +1,137 @@
#pragma once
#include <any>
#include <functional>
#include <string>
#include <unordered_map>
#include <sophia/types.hpp>
#include <sophia/util/delegate.hpp>
#include <sophia/util/logger.hpp>
#define BIT(x) (1 << (x))
namespace sophia {
// https://github.com/TheCherno/Hazel/blob/1feb70572fa87fa1c4ba784a2cfeada5b4a500db/Hazel/src/Hazel/Events/Event.h
enum EventCategoryFlags : u32 {
None = 0,
EventCategoryApplication = BIT(0),
EventCategoryInput = BIT(1),
EventCategoryKeyboard = BIT(2),
EventCategoryMouse = BIT(3),
EventCategoryMouseButton = BIT(4)
};
class Event {
public:
virtual ~Event() = default;
virtual const char* getName() const = 0;
virtual std::string toString() const { return getName(); }
virtual int getCategoryFlags() const { return EventCategoryFlags::None; };
inline bool isInCategory(EventCategoryFlags category) {
return getCategoryFlags() & category;
}
};
template <typename DerivedEvent>
class EventDelegate : public Delegate<DerivedEvent&> {
public:
EventDelegate() {
static_assert(std::is_base_of<Event, DerivedEvent>::value,
"DerivedEvent must derive from Event");
}
};
inline std::ostream& operator<<(std::ostream& os, const Event& e) {
return os << e.toString();
}
/**
* Singleton class for managing event subscription and dispatching.
*
* TODO heavy documentation is needed
*
* @todo impl removing functors from delegate
* @warning this class is in heavy development, use at your own risk
* @warning NOT THREAD SAFE
*/
class EventSystem {
public:
using EventType = decltype(std::declval<std::type_info>().hash_code());
#define GET_EVENT_TYPE(E) typeid(E).hash_code()
EventSystem(const EventSystem&) = delete;
EventSystem& operator=(const EventSystem&) = delete;
static EventSystem& get() {
static EventSystem instance;
return instance;
}
template <class E>
void addListener(std::function<void(E&)> f) {
static_assert(std::is_base_of<Event, E>::value, "E must derive from Event");
EventType eventType = GET_EVENT_TYPE(E);
// Ensures pointer to EventDelegate<E> is not null in unordered map
if (!this->eventDelegates.count(eventType)) {
this->eventDelegates[eventType] =
std::make_unique<std::any>(EventDelegate<E>());
log::trace("EventSystem creating new event type: ", eventType);
}
EventDelegate<E>& delegate =
std::any_cast<EventDelegate<E>&>(*eventDelegates[eventType]);
delegate.add(f);
}
template <class E, class O>
void addMethodListener(void (O::*memberFn)(E&), std::shared_ptr<O> obj) {
static_assert(std::is_base_of<Event, E>::value, "E must derive from Event");
EventType eventType = GET_EVENT_TYPE(E);
// Ensures pointer to EventDelegate<E> is not null in unordered map
if (!this->eventDelegates.count(eventType)) {
this->eventDelegates[eventType] =
std::make_unique<std::any>(EventDelegate<E>());
log::trace("EventSystem creating new event type: ", eventType);
}
EventDelegate<E>& delegate =
std::any_cast<EventDelegate<E>&>(*eventDelegates[eventType]);
delegate.addMethod(memberFn, obj);
}
template <class E>
void dispatch(E& event) {
static_assert(std::is_base_of<Event, E>::value, "E must derive from Event");
EventType eventType = GET_EVENT_TYPE(E);
if (!this->eventDelegates.count(eventType)) { return; }
try {
EventDelegate<E>& delegate =
std::any_cast<EventDelegate<E>&>(*this->eventDelegates.at(eventType));
delegate.invoke(event);
} catch (const std::out_of_range& error) {
log::error("event dispatch failed: ", event.toString());
}
}
private:
EventSystem() { log::info("EventSystem Initialized: ", this); }
~EventSystem() { log::info("EventSystem Destroyed"); }
std::unordered_map<EventType, std::unique_ptr<std::any>> eventDelegates;
};
} // namespace sophia
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <sophia/types.hpp>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
namespace sophia {
struct FrameInfo {
u32 frameIndex;
double elapsedTime;
double deltaTime;
glm::vec2 currentFramebufferExtent;
};
} // namespace sophia
+141
View File
@@ -0,0 +1,141 @@
#pragma once
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <sophia/types.hpp>
namespace sophia {
using KeyCode = u32;
namespace Key {
enum : KeyCode {
Space = GLFW_KEY_SPACE,
Apostrophe = GLFW_KEY_APOSTROPHE, /* ' */
Comma = GLFW_KEY_COMMA, /* , */
Minus = GLFW_KEY_MINUS, /* - */
Period = GLFW_KEY_PERIOD, /* . */
Slash = GLFW_KEY_SLASH, /* / */
D0 = GLFW_KEY_0,
D1 = GLFW_KEY_1,
D2 = GLFW_KEY_2,
D3 = GLFW_KEY_3,
D4 = GLFW_KEY_4,
D5 = GLFW_KEY_5,
D6 = GLFW_KEY_6,
D7 = GLFW_KEY_7,
D8 = GLFW_KEY_8,
D9 = GLFW_KEY_9,
Semicolon = GLFW_KEY_SEMICOLON, /* ; */
Equal = GLFW_KEY_EQUAL, /* = */
A = GLFW_KEY_A,
B = GLFW_KEY_B,
C = GLFW_KEY_C,
D = GLFW_KEY_D,
E = GLFW_KEY_E,
F = GLFW_KEY_F,
G = GLFW_KEY_G,
H = GLFW_KEY_H,
I = GLFW_KEY_I,
J = GLFW_KEY_J,
K = GLFW_KEY_K,
L = GLFW_KEY_L,
M = GLFW_KEY_M,
N = GLFW_KEY_N,
O = GLFW_KEY_O,
P = GLFW_KEY_P,
Q = GLFW_KEY_Q,
R = GLFW_KEY_R,
S = GLFW_KEY_S,
T = GLFW_KEY_T,
U = GLFW_KEY_U,
V = GLFW_KEY_V,
W = GLFW_KEY_W,
X = GLFW_KEY_X,
Y = GLFW_KEY_Y,
Z = GLFW_KEY_Z,
LeftBracket = GLFW_KEY_LEFT_BRACKET, /* [ */
Backslash = GLFW_KEY_BACKSLASH, /* \ */
RightBracket = GLFW_KEY_RIGHT_BRACKET, /* ] */
GraveAccent = GLFW_KEY_GRAVE_ACCENT, /* ` */
World1 = GLFW_KEY_WORLD_1, /* non-US #1 */
World2 = GLFW_KEY_WORLD_2, /* non-US #2 */
/* Function keys */
Escape = GLFW_KEY_ESCAPE,
Enter = GLFW_KEY_ENTER,
Tab = GLFW_KEY_TAB,
Backspace = GLFW_KEY_BACKSPACE,
Insert = GLFW_KEY_INSERT,
Delete = GLFW_KEY_DELETE,
Right = GLFW_KEY_RIGHT,
Left = GLFW_KEY_LEFT,
Down = GLFW_KEY_DOWN,
Up = GLFW_KEY_UP,
PageUp = GLFW_KEY_PAGE_UP,
PageDown = GLFW_KEY_PAGE_DOWN,
Home = GLFW_KEY_HOME,
End = GLFW_KEY_END,
CapsLock = GLFW_KEY_CAPS_LOCK,
ScrollLock = GLFW_KEY_SCROLL_LOCK,
NumLock = GLFW_KEY_NUM_LOCK,
PrintScreen = GLFW_KEY_PRINT_SCREEN,
Pause = GLFW_KEY_PAUSE,
F1 = GLFW_KEY_F1,
F2 = GLFW_KEY_F2,
F3 = GLFW_KEY_F3,
F4 = GLFW_KEY_F4,
F5 = GLFW_KEY_F5,
F6 = GLFW_KEY_F6,
F7 = GLFW_KEY_F7,
F8 = GLFW_KEY_F8,
F9 = GLFW_KEY_F9,
F10 = GLFW_KEY_F10,
F11 = GLFW_KEY_F11,
F12 = GLFW_KEY_F12,
F13 = GLFW_KEY_F13,
F14 = GLFW_KEY_F14,
F15 = GLFW_KEY_F15,
F16 = GLFW_KEY_F16,
F17 = GLFW_KEY_F17,
F18 = GLFW_KEY_F18,
F19 = GLFW_KEY_F19,
F20 = GLFW_KEY_F20,
F21 = GLFW_KEY_F21,
F22 = GLFW_KEY_F22,
F23 = GLFW_KEY_F23,
F24 = GLFW_KEY_F24,
F25 = GLFW_KEY_F25,
KP0 = GLFW_KEY_KP_0,
KP1 = GLFW_KEY_KP_1,
KP2 = GLFW_KEY_KP_2,
KP3 = GLFW_KEY_KP_3,
KP4 = GLFW_KEY_KP_4,
KP5 = GLFW_KEY_KP_5,
KP6 = GLFW_KEY_KP_6,
KP7 = GLFW_KEY_KP_7,
KP8 = GLFW_KEY_KP_8,
KP9 = GLFW_KEY_KP_9,
KPDecimal = GLFW_KEY_KP_DECIMAL,
KPDivide = GLFW_KEY_KP_DIVIDE,
KPMultiply = GLFW_KEY_KP_MULTIPLY,
KPSubtract = GLFW_KEY_KP_SUBTRACT,
KPAdd = GLFW_KEY_KP_ADD,
KPEnter = GLFW_KEY_KP_ENTER,
KPEqual = GLFW_KEY_KP_EQUAL,
LeftShift = GLFW_KEY_LEFT_SHIFT,
LeftControl = GLFW_KEY_LEFT_CONTROL,
LeftAlt = GLFW_KEY_LEFT_ALT,
LeftSuper = GLFW_KEY_LEFT_SUPER,
RightShift = GLFW_KEY_RIGHT_SHIFT,
RightControl = GLFW_KEY_RIGHT_CONTROL,
RightAlt = GLFW_KEY_RIGHT_ALT,
RightSuper = GLFW_KEY_RIGHT_SUPER,
Menu = GLFW_KEY_MENU,
};
} // namespace Key
} // namespace sophia
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include <sstream>
#include <sophia/event.hpp>
#include <sophia/key_codes.hpp>
namespace sophia {
class KeyEvent : public Event {
public:
KeyCode getKeyCode() const { return this->keycode; }
virtual int getCategoryFlags() const override {
return EventCategoryKeyboard | EventCategoryInput;
}
protected:
KeyEvent(const KeyCode keycode) : keycode{keycode} {}
KeyCode keycode;
};
class KeyPressedEvent : public KeyEvent {
public:
KeyPressedEvent(const KeyCode keycode, bool repeated = false)
: KeyEvent{keycode}, repeated{repeated} {}
bool isRepeat() const { return this->repeated; }
std::string toString() const override {
std::stringstream ss;
ss << "KeyPressedEvent: " << this->keycode
<< " (repeat = " << this->repeated << ")";
return ss.str();
}
virtual const char* getName() const override { return "KeyPressed"; }
private:
bool repeated;
};
class KeyReleasedEvent : public KeyEvent {
public:
KeyReleasedEvent(const KeyCode keycode) : KeyEvent{keycode} {}
std::string toString() const override {
std::stringstream ss;
ss << "KeyReleasedEvent: " << this->keycode;
return ss.str();
}
virtual const char* getName() const override { return "KeyReleased"; }
};
class KeyTypedEvent : public KeyEvent {
public:
KeyTypedEvent(const KeyCode keycode) : KeyEvent{keycode} {}
std::string toString() const override {
std::stringstream ss;
ss << "KeyTypedEvent: " << this->keycode;
return ss.str();
}
virtual const char* getName() const override { return "KeyTyped"; }
};
} // namespace sophia
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <stdint.h>
namespace sophia {
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using s8 = int8_t;
using s16 = int16_t;
using s32 = int32_t;
using s64 = int64_t;
using f32 = float;
using f64 = double;
} // namespace sophia
+111
View File
@@ -0,0 +1,111 @@
#pragma once
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <typeinfo>
#include <vector>
#include <sophia/util/logger.hpp>
#define DEFAULT_DELEGATE_FUNCTOR_ALLOC 5
namespace sophia {
class expired_weak_object : public std::runtime_error {
public:
explicit expired_weak_object(const std::string& message)
: std::runtime_error(message) {}
};
/**
* Creates a weak function that safely calls a method if the
* object is alive.
*
* @tparam O Class owning the method
* @tparam A Variadic template parameters for the method arguments
*
* @param method Pointer to the static method to be called
* @param obj Shared pointer to the object instance
* @returns std::function that calls method on obj if alive, or throws if
* expired
* @throws std::invalid_argument If obj is null.
* @throws expired_weak_object If the object has expired when called
*
* @note Could investigate removing throwing exceptions, and instead return a
* bool if the weak pointer is alive. This may improve performance
*/
template <class O, typename... A>
std::function<void(A...)> createWeakFunction(void (O::*method)(A...),
std::shared_ptr<O> obj) {
if (!obj) { throw std::invalid_argument("method and obj must not be null"); }
std::weak_ptr<O> weakObj = obj;
return std::function<void(A...)>([method = std::move(method),
weakObj = std::move(weakObj)](A... args) {
if (auto lockedObj = weakObj.lock()) {
(lockedObj.get()->*method)(std::forward<A>(args)...);
} else {
throw expired_weak_object("Attempting to call function of a dead class");
}
});
}
template <typename... A>
class Delegate {
public:
using Functor = std::function<void(A...)>;
Delegate() { this->functors.reserve(DEFAULT_DELEGATE_FUNCTOR_ALLOC); }
void add(const Functor& func) { functors.push_back(func); }
template <class O>
void addMethod(void (O::*memberFn)(A...), std::shared_ptr<O> obj) {
functors.push_back(createWeakFunction(memberFn, obj));
}
void clear() { this->functors.clear(); }
void invoke(A... args) {
if (this->functors.empty()) {
log::warning("invoking empty delegate");
return;
}
std::vector<size_t> deadFunctorIndexs;
for (size_t i = 0; i < functors.size(); i++) {
if (this->functors[i]) {
try {
this->functors[i](std::forward<A>(args)...);
} catch (const expired_weak_object& e) {
log::error("delegate invoke error: ", e.what());
deadFunctorIndexs.push_back(i);
}
}
}
for (auto it = deadFunctorIndexs.rbegin(); it != deadFunctorIndexs.rend();
++it) {
functors.erase(functors.begin() + *it);
}
}
void operator=(Functor c) {
clear();
if (c != nullptr) { add(c); }
}
void operator+=(Functor c) { add(c); }
void operator()() { invoke(); }
private:
std::vector<Functor> functors;
};
} // namespace sophia
+126
View File
@@ -0,0 +1,126 @@
/**
* Lots of work still needs to be done in here.
*
* Usage:
* using namespace sophia;
* log::fatal("fatal message")
* log::info("Infomation message")
*
* TODO Impl some form of log error handling
*/
#pragma once
#include <iostream>
#include <utility>
#include <sophia/types.hpp>
namespace sophia::log {
enum class LEVEL {
NONE,
FATAL,
ERROR,
WARNING,
INFO,
VERBOSE,
DEBUGGING,
TRACE,
};
// Helper print functions
#define LOG_PRINT_HELPER(MSG) std::cout << MSG
#define LOG_PREFIX_FATAL LOG_PRINT_HELPER("\033[1;91m[FATAL]\033[0m ")
#define LOG_PREFIX_ERROR LOG_PRINT_HELPER("\033[1;31m[ERROR]\033[0m ")
#define LOG_PREFIX_WARNING LOG_PRINT_HELPER("\033[1;33m[WARNING]\033[0m ")
#define LOG_PREFIX_INFO LOG_PRINT_HELPER("\033[32m[INFO]\033[0m ")
#define LOG_PREFIX_VERBOSE LOG_PRINT_HELPER("\033[36m[VERBOSE]\033[0m ")
#define LOG_PREFIX_DEBUG LOG_PRINT_HELPER("\033[1;34m[DEBUG]\033[0m ")
#define LOG_PREFIX_TRACE LOG_PRINT_HELPER("\033[1;90m[TRACE]\033[0m ")
template <typename... Args>
void log(LEVEL level = LEVEL::NONE, Args... args) {
#ifndef NDEBUG
switch (level) {
case LEVEL::FATAL:
LOG_PREFIX_FATAL;
break;
case LEVEL::ERROR:
LOG_PREFIX_ERROR;
break;
case LEVEL::WARNING:
LOG_PREFIX_WARNING;
break;
case LEVEL::INFO:
LOG_PREFIX_INFO;
break;
case LEVEL::VERBOSE:
LOG_PREFIX_VERBOSE;
break;
// case LEVEL::DEBUGGING:
// LOG_PREFIX_DEBUG;
// break;
case LEVEL::TRACE:
LOG_PREFIX_TRACE;
break;
default:
std::cout << "\033[1;90m[LOG]\033[0m ";
break;
}
((std::cout << ' ' << std::forward<Args>(args)), ...);
std::cout << std::endl;
#else
(void)level;
(void)std::initializer_list<int>{((void)args, 0)...};
#endif // !NDEBUG
}
template <typename... Args>
void fatal(Args... args) {
log(LEVEL::FATAL, args...);
}
template <typename... Args>
void error(Args... args) {
log(LEVEL::ERROR, args...);
}
template <typename... Args>
void warning(Args... args) {
log(LEVEL::WARNING, args...);
}
template <typename... Args>
void info(Args... args) {
log(LEVEL::INFO, args...);
}
template <typename... Args>
void verbose(Args... args) {
log(LEVEL::VERBOSE, args...);
}
template <typename... Args>
void debug(const char* file, u16 line, Args... args) {
#ifndef NDEBUG
LOG_PREFIX_DEBUG;
std::cout << file << "::" << line;
((std::cout << ' ' << std::forward<Args>(args)), ...);
std::cout << std::endl;
#else
(void)file;
(void)line;
(void)std::initializer_list<int>{((void)args, 0)...};
#endif // !NDEBUG
}
// trick
#define debug(...) debug(__FILE__, __LINE__, __VA_ARGS__)
template <typename... Args>
void trace(Args... args) {
log(LEVEL::TRACE, args...);
}
} // namespace sophia::log