Compare commits

..

16 Commits

Author SHA1 Message Date
b766c1dc35 chore(cmake): update cmake version 2024-09-14 20:46:32 -06:00
e94c03b171 Merge pull request 'refactor/logger-rework' (#2) from refactor/logger-rework into master
Reviewed-on: http://localhost:3001/illyum/IsoEngine/pulls/2
2024-09-14 02:42:02 -06:00
93d62c5b1a chore(refactor): use new logger api 2024-09-14 02:41:14 -06:00
fef4a92378 feat(logging): improve logging and add log macros 2024-09-14 02:40:25 -06:00
0fa35696bb feat(crash): add crash reporting 2024-09-14 02:39:50 -06:00
842c8fb19d feat(ui): rework renderer to work with deltatime and ui rendering 2024-09-13 07:44:55 -06:00
b359f36fb7 feat(components): add new components to component header 2024-09-13 07:44:06 -06:00
23544c16ad feat(ui): render ui layer 2024-09-13 07:43:42 -06:00
9f6271f9ba feat(assetpacker): add asset packing 2024-09-13 07:43:19 -06:00
4797f0a832 feat(cmake): create precompiled header 2024-09-12 21:25:28 -06:00
fdb5ee6e1e fix(merge): resolve conflict in CMakeLists.txt 2024-09-12 21:15:12 -06:00
7cdf73ac20 feat(cmake): enable glfw event waiting for raylib 2024-09-12 21:13:49 -06:00
ff75af9f72 feat: pass delta to scene manager's update function 2024-09-12 21:12:11 -06:00
3b87746e7d feat(layers): add layering system 2024-09-12 21:11:27 -06:00
d486da8994 chore: remove all build folders 2024-09-12 21:10:47 -06:00
54e4b00f8c Merge pull request 'build: rework cmake static and shared library building' (#1) from refactor/dll-support into master
Reviewed-on: http://localhost:3001/illyum/IsoEngine/pulls/1
2024-09-11 23:24:55 -06:00
25 changed files with 808 additions and 245 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ build-debug/
build-release/
build-minsizerel/
cmake-build-debug/
build*/
# Tooling files
output.txt

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.24)
cmake_minimum_required(VERSION 3.28)
project(IsoEngine LANGUAGES CXX)
@ -12,7 +12,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build." FORCE)
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_XCODE_GENERATE_SCHEME ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@ -30,12 +30,16 @@ set(FETCHCONTENT_QUIET OFF)
include(FetchContent)
# enet is header-only
set(ENET_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/thirdparty/enet")
set(ENET_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/vendor/enet")
FetchContent_Declare(
raylib
URL https://github.com/raysan5/raylib/archive/refs/tags/5.0.zip
)
set(BUILD_EXAMPLES OF CACHE BOOL "" FORCE)
set(SUPPORT_EVENTS_WAITING ON CACHE BOOL "" FORCE)
set(SUPPORT_BUSY_WAIT_LOOP OFF CACHE BOOL "" FORCE)
if (BUILD_SHARED_LIBS)
set(RAYLIB_LIBTYPE SHARED)
endif()
@ -100,16 +104,21 @@ set(ENGINE_SOURCES
src/enet_client.cpp
src/enet_server.cpp
src/components/sprite_component.cpp
src/Logger.cpp
src/Logger.h
src/components/transform_component.h
src/components/sprite_component.cpp
src/components/physics_component.cpp
src/components/physics_component.h
src/components/input_component.cpp
src/components/input_component.h
src/components/health_component.h
src/components/ui_component.cpp
src/scene_manager.cpp
src/filepacker.cpp
)
if (WIN32)
list(APPEND ENGINE_SOURCES src/platform/windows/FatalHandlerWindows.cpp)
elseif(UNIX)
list(APPEND ENGINE_SOURCES src/platform/posix/FatalHandlerPosix.cpp)
endif()
add_library(IsoEngine ${ENGINE_SOURCES})
@ -126,6 +135,7 @@ target_include_directories(IsoEngine PUBLIC
)
target_link_libraries(IsoEngine PRIVATE raylib box2d EnTT::EnTT)
target_precompile_headers(IsoEngine PRIVATE src/IsoEnginePCH.h)
# PhysFS linkage
if(BUILD_SHARED_LIBS)
@ -142,7 +152,7 @@ target_link_libraries(rlImGui PRIVATE raylib ImGui)
target_link_libraries(IsoEngine PRIVATE ImGui rlImGui)
# Example executables (client/server)
if(BUILD_CLIENT OR BUILD_SANDBOX)
if(BUILD_CLIENT)
# Client executable
set(CLIENT_SOURCE "${CMAKE_CURRENT_LIST_DIR}/sandbox/client.cpp")
add_executable(client ${CLIENT_SOURCE})
@ -181,6 +191,7 @@ endif()
# Build Configuration Optimizations
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Configuring Debug build: No optimizations applied.")
target_compile_definitions(IsoEngine PRIVATE ENABLE_LOGGING)
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
message(STATUS "Configuring Release build: Applying moderate optimizations.")
target_compile_options(IsoEngine PRIVATE -O2)

View File

@ -1,2 +1,8 @@
# Iso Engine
Smol little game engine since I have no idea what I'm doing
Smol little game engine since I have no idea what I'm doing
# Notice:
Since this uses the newer cpp modules, you need to make sure your compiler + build system + IDE supports this. You can
find more info [on this stack overflow post](https://stackoverflow.com/questions/57300495/how-to-use-c20-modules-with-cmake).

20
docs/ideas.md Normal file
View File

@ -0,0 +1,20 @@
# Ideas for this/future engine
Also just an outline for stuff
- Custom Logger
- CMake / Premake / XMake
- Event System
- Window Handler (support multiple windows)
- Layer system
- Math helpers
- Static/Dynamic linking
- Export or Build (export game as game file, or build your own exe directly)
- Shader System
- Entity Component System?
- Profiling
- Scripting? (lua?) + while-running / preprocessed scripts
- Tools (Sprite Sheet Tools, etc)
- Asset Manager / Pipeline (choose between .pak or individual files)
- Physics
- Collision Detection

View File

@ -1,45 +1,99 @@
#include "IsoEngine.h"
#include "components/transform_component.h"
#include "components/input_component.h"
#include <raylib.h>
#include "components.h"
#include <iostream>
#define ENABLE_LOGGING
enum class MenuState {
MAIN_MENU,
OPTIONS,
QUIT
};
int main() {
IsoEngine engine(800, 600, nullptr);
int screenWidth = 800;
int screenHeight = 600;
IsoEngine engine(screenWidth, screenHeight, nullptr);
engine.Initialize();
SceneManager& sceneManager = engine.GetSceneManager();
entt::entity gameScene = sceneManager.CreateScene();
sceneManager.SetActiveScene(gameScene);
entt::entity mainMenuScene = sceneManager.CreateScene();
entt::entity player = engine.GetRegistry().create();
engine.GetRegistry().emplace<TransformComponent>(player, 400.0f, 300.0f, 1.0f, 1.0f, 0.0f);
auto& registry = engine.GetRegistry();
auto& inputComponent = engine.GetRegistry().emplace<InputComponent>(player);
// Button dimensions
float buttonWidth = 200.0f;
float buttonHeight = 50.0f;
float spacing = 20.0f; // Vertical spacing between buttons
inputComponent.BindKey(InputAction::MOVE_UP, KEY_W);
inputComponent.BindKey(InputAction::MOVE_DOWN, KEY_S);
inputComponent.BindKey(InputAction::MOVE_LEFT, KEY_A);
inputComponent.BindKey(InputAction::MOVE_RIGHT, KEY_D);
// Center X position
float centerX = screenWidth / 2 - buttonWidth / 2;
inputComponent.SetActionCallback(InputAction::MOVE_UP, [&]() {
auto& transform = engine.GetRegistry().get<TransformComponent>(player);
transform.y -= 5.0f;
});
inputComponent.SetActionCallback(InputAction::MOVE_DOWN, [&]() {
auto& transform = engine.GetRegistry().get<TransformComponent>(player);
transform.y += 5.0f;
});
inputComponent.SetActionCallback(InputAction::MOVE_LEFT, [&]() {
auto& transform = engine.GetRegistry().get<TransformComponent>(player);
transform.x -= 5.0f;
});
inputComponent.SetActionCallback(InputAction::MOVE_RIGHT, [&]() {
auto& transform = engine.GetRegistry().get<TransformComponent>(player);
transform.x += 5.0f;
// Y positions for each button
float singlePlayerY = screenHeight / 2 - (buttonHeight + spacing);
float quitButtonY = screenHeight / 2 + (buttonHeight + spacing);
// Create a UI button for "Single Player"
entt::entity singlePlayerButton = registry.create();
registry.emplace<TransformComponent>(singlePlayerButton, centerX, singlePlayerY, 1.0f, 1.0f, 0.0f); // Centered horizontally
registry.emplace<UIComponent>(singlePlayerButton, "Single Player", buttonWidth, buttonHeight, [&]() {
std::cout << "Single Player button clicked!" << std::endl;
});
registry.emplace<LayerComponent>(singlePlayerButton, 1); // Main menu on layer 1
// Create a "Quit" button
entt::entity quitButton = registry.create();
registry.emplace<TransformComponent>(quitButton, centerX, quitButtonY, 1.0f, 1.0f, 0.0f); // Centered horizontally
registry.emplace<UIComponent>(quitButton, "Quit", buttonWidth, buttonHeight, [&]() {
engine.Shutdown();
});
registry.emplace<LayerComponent>(quitButton, 1); // Also on layer 1
// Create an "F3" debug overlay on a higher layer
entt::entity debugOverlay = registry.create();
registry.emplace<TransformComponent>(debugOverlay, 10.0f, 10.0f, 1.0f, 1.0f, 0.0f); // Placed at top-left corner
registry.emplace<UIComponent>(debugOverlay, "FPS: 60\nPosition: (100, 200)", 300.0f, 100.0f, nullptr);
registry.emplace<LayerComponent>(debugOverlay, 2); // Debug overlay on layer 2
// Set active scene to the main menu
sceneManager.SetActiveScene(mainMenuScene);
// Run the engine loop
engine.Run();
engine.Shutdown();
return 0;
}
// entt::entity gameScene = sceneManager.CreateScene();
// sceneManager.SetActiveScene(gameScene);
//
// entt::entity player = engine.GetRegistry().create();
// engine.GetRegistry().emplace<TransformComponent>(player, 400.0f, 300.0f, 1.0f, 1.0f, 0.0f);
//
// auto& inputComponent = engine.GetRegistry().emplace<InputComponent>(player);
//
// inputComponent.BindKey(InputAction::MOVE_UP, KEY_W);
// inputComponent.BindKey(InputAction::MOVE_DOWN, KEY_S);
// inputComponent.BindKey(InputAction::MOVE_LEFT, KEY_A);
// inputComponent.BindKey(InputAction::MOVE_RIGHT, KEY_D);
//
// inputComponent.SetActionCallback(InputAction::MOVE_UP, [&]() {
// auto& transform = engine.GetRegistry().get<TransformComponent>(player);
// transform.y -= 5.0f;
// });
// inputComponent.SetActionCallback(InputAction::MOVE_DOWN, [&]() {
// auto& transform = engine.GetRegistry().get<TransformComponent>(player);
// transform.y += 5.0f;
// });
// inputComponent.SetActionCallback(InputAction::MOVE_LEFT, [&]() {
// auto& transform = engine.GetRegistry().get<TransformComponent>(player);
// transform.x -= 5.0f;
// });
// inputComponent.SetActionCallback(InputAction::MOVE_RIGHT, [&]() {
// auto& transform = engine.GetRegistry().get<TransformComponent>(player);
// transform.x += 5.0f;
// });

19
src/FatalHandler.h Normal file
View File

@ -0,0 +1,19 @@
//
// Created by illyum on 9/13/2024.
//
#ifndef FATALHANDLER_H
#define FATALHANDLER_H
#include <IsoEnginePCH.h>
// TODO: Implement warning popup/crash handler (platform specific)
// TODO: Separate fatal handlers for core (the engine) and app (the user's app)
// maybe let the user handle their own fatal errors if they so choose, or they can use the default? similar
// to how the user has to specify an engine application
namespace IsoEngine {
void FatalHandler(const std::string& message);
};
#endif //FATALHANDLER_H

View File

@ -1,4 +1,7 @@
#include "IsoEngine.h"
#include <Logger.h>
#include "components.h"
#include <raylib.h>
@ -16,8 +19,9 @@ void IsoEngine::Initialize() {
if (network) {
network->Initialize();
}
std::cout << "Engine initialized." << std::endl;
Logger::GetCoreLogger().AddOutputStream(&std::cout);
Logger::GetAppLogger().AddOutputStream(&std::cout);
CORE_LOG_INFO("Engine initialized!");
}
void IsoEngine::Run() {
@ -39,7 +43,7 @@ void IsoEngine::Shutdown() {
network->Shutdown();
}
std::cout << "Engine shut down." << std::endl;
CORE_LOG_INFO("Engine shut down");
}
void IsoEngine::Update() {
@ -47,7 +51,8 @@ void IsoEngine::Update() {
isRunning = false;
}
sceneManager.UpdateActiveScene();
sceneManager.UpdateActiveScene(GetFrameTime());
sceneManager.UpdateUI(registry);
}
void IsoEngine::Render() {
@ -57,5 +62,7 @@ void IsoEngine::Render() {
sceneManager.RenderActiveScene();
DrawText("IsoEngine Running!", 10, 10, 20, DARKGRAY);
sceneManager.RenderUI(registry);
DrawFPS(10, 100);
EndDrawing();
}

View File

@ -1,7 +1,6 @@
#pragma once
#include <entt/entt.hpp>
#include <iostream>
#include "network.h"
#include "scene_manager.h"

28
src/IsoEnginePCH.h Normal file
View File

@ -0,0 +1,28 @@
//
// Created by illyum on 9/12/2024.
//
#ifndef ISOENGINEPCH_H
#define ISOENGINEPCH_H
// #ifdef WIN32
// #include <Windows.h>
// #endif
#include <string>
#include <memory>
#include <vector>
#include <iomanip>
#include <sstream>
#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <functional>
#include <fstream>
#include <ctime>
#include <deque>
#include <mutex>
#include <format>
#endif //ISOENGINEPCH_H

View File

@ -1,109 +1,63 @@
#include "Logger.h"
#include "FatalHandler.h"
void Logger::Initialize(const std::string &logFile, LogLevel consoleLevel, LogLevel fileLevel) {
logFileName = logFile;
consoleLogLevel = consoleLevel;
fileLogLevel = fileLevel;
if (!logFile.empty()) {
logFileStream.open(logFile, std::ios::out | std::ios::app);
Logger& Logger::GetCoreLogger() {
static Logger coreLogger;
return coreLogger;
}
Logger& Logger::GetAppLogger() {
static Logger appLogger;
return appLogger;
}
Logger::Logger() {}
void Logger::AddOutputStream(std::ostream* stream) {
outputStreams.push_back(stream);
}
void Logger::SetLogLevel(LogLevel level) {
logLevel = level;
}
void Logger::Log(LogLevel level, const std::string& message) {
if (level >= logLevel) {
LogInternal(level, message);
}
}
Logger::~Logger() {
if (logFileStream.is_open()) {
logFileStream.close();
void Logger::LogInternal(LogLevel level, const std::string& message) {
std::string time = GetCurrentTime();
std::string levelStr = LevelToString(level);
for (auto* stream : outputStreams) {
*stream << time << " [" << levelStr << "]: " << message << std::endl;
}
}
void Logger::Log(LogLevel level, const std::string& message, const std::string& scope) {
std::lock_guard<std::mutex> lock(logMutex);
std::string Logger::GetCurrentTime() {
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::string formattedMessage = FormatMessage(level, message, scope);
memoryLogs.push_back(formattedMessage);
if (memoryLogs.size() > memoryLogSize) {
memoryLogs.pop_front();
}
if (ShouldLogToConsole(level)) {
std::cout << formattedMessage << std::endl;
}
if (ShouldLogToFile(level)) {
if (logFileStream.is_open()) {
logFileStream << formattedMessage << std::endl;
}
}
std::stringstream ss;
ss << std::put_time(std::localtime(&in_time_t), "%Y/%m/%d %H:%M:%S");
return ss.str();
}
void Logger::LogInfo(const std::string& message, const std::string& scope) {
Log(LogLevel::INFOL, message, scope);
}
void Logger::LogDebug(const std::string& message, const std::string& scope) {
Log(LogLevel::DEBUGL, message, scope);
}
void Logger::LogWarning(const std::string& message, const std::string& scope) {
Log(LogLevel::WARNINGL, message, scope);
}
void Logger::LogError(const std::string& message, const std::string& scope) {
Log(LogLevel::ERRORL, message, scope);
}
void Logger::LogCritical(const std::string& message, const std::string& scope) {
Log(LogLevel::CRITICALL, message, scope);
}
void Logger::DumpLogs(const std::string& dumpFileName) {
std::lock_guard<std::mutex> lock(logMutex);
std::ofstream dumpFile(dumpFileName, std::ios::out | std::ios::app);
if (!dumpFile.is_open()) {
std::cerr << "Failed to open dump file: " << dumpFileName << std::endl;
return;
}
dumpFile << "---- Log Dump ----" << std::endl;
dumpFile << "Timestamp: " << GetTimestamp() << std::endl;
dumpFile << "------------------" << std::endl;
for (const std::string& log : memoryLogs) {
dumpFile << log << std::endl;
}
dumpFile.close();
}
std::string Logger::FormatMessage(LogLevel level, const std::string& message, const std::string& scope) {
std::string levelStr = LogLevelToString(level);
std::string timestamp = GetTimestamp();
return timestamp + " [" + levelStr + "] [" + scope + "] " + message;
}
std::string Logger::GetTimestamp() {
std::time_t now = std::time(nullptr);
char buf[20];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now));
return std::string(buf);
}
std::string Logger::LogLevelToString(LogLevel level) {
std::string Logger::LevelToString(LogLevel level) {
switch (level) {
case LogLevel::DEBUGL: return "DEBUG";
case LogLevel::INFOL: return "INFO";
case LogLevel::WARNINGL: return "WARNING";
case LogLevel::ERRORL: return "ERROR";
case LogLevel::CRITICALL: return "CRITICAL";
default: return "UNKNOWN";
case LogLevel::TRACE_l: return "TRACE";
case LogLevel::DEBUG_l: return "DEBUG";
case LogLevel::INFO_l: return "INFO";
case LogLevel::WARN_l: return "WARN";
case LogLevel::ERROR_l: return "ERROR";
case LogLevel::FATAL_l: return "FATAL";
default: return "UNKNOWN";
}
}
bool Logger::ShouldLogToConsole(LogLevel level) {
return level >= consoleLogLevel;
}
bool Logger::ShouldLogToFile(LogLevel level) {
return level >= fileLogLevel && !logFileName.empty();
}
template<typename... Args>
void Logger::FatalHandler(const std::string& format, Args... args) {
IsoEngine::FatalHandler(format, args...);
}

View File

@ -1,56 +1,77 @@
#pragma once
#include <fstream>
#include <iostream>
#include <string>
#include <ctime>
#include <deque>
#include <mutex>
#include <IsoEnginePCH.h>
enum class LogLevel {
DEBUGL = 0,
INFOL = 10,
WARNINGL = 20,
ERRORL = 30,
CRITICALL = 40
TRACE_l,
DEBUG_l,
INFO_l,
WARN_l,
ERROR_l,
FATAL_l
};
class Logger {
public:
static Logger& GetInstance() {
static Logger instance;
return instance;
static Logger& GetCoreLogger();
static Logger& GetAppLogger();
void Log(LogLevel level, const std::string& message);
template<typename... Args>
void LogFormat(LogLevel level, const std::string& format, Args&&... args) {
if (level >= logLevel) {
std::string formattedMessage = std::vformat(format, std::make_format_args(std::forward<Args>(args)...));
LogInternal(level, formattedMessage);
}
}
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
void AddOutputStream(std::ostream* stream);
void SetLogLevel(LogLevel level);
void Initialize(const std::string &logFile = "", LogLevel consoleLevel = LogLevel::DEBUGL, LogLevel fileLevel = LogLevel::DEBUGL);
void Log(LogLevel level, const std::string &message, const std::string &scope = "");
void LogDebug(const std::string& message, const std::string& scope = "");
void LogInfo(const std::string& message, const std::string& scope = "");
void LogWarning(const std::string& message, const std::string& scope = "");
void LogError(const std::string& message, const std::string& scope = "");
void LogCritical(const std::string& message, const std::string& scope = "");
void DumpLogs(const std::string& dumpFileName = "crash_dump.log");
template<typename... Args>
static void FatalHandler(const std::string& format, Args... args);
private:
Logger() = default;
~Logger();
Logger();
void LogInternal(LogLevel level, const std::string& message);
std::string GetCurrentTime();
std::string LevelToString(LogLevel level);
std::ofstream logFileStream;
std::string logFileName;
LogLevel consoleLogLevel;
LogLevel fileLogLevel;
std::mutex logMutex;
std::deque<std::string> memoryLogs;
size_t memoryLogSize = 100;
std::string FormatMessage(LogLevel level, const std::string& message, const std::string& scope);
std::string GetTimestamp();
std::string LogLevelToString(LogLevel level);
bool ShouldLogToConsole(LogLevel level);
bool ShouldLogToFile(LogLevel level);
std::vector<std::ostream*> outputStreams;
LogLevel logLevel = LogLevel::TRACE_l;
};
#ifdef ENABLE_LOGGING
#define LOG_TRACE(...) Logger::GetAppLogger().LogFormat(LogLevel::TRACE_l, __VA_ARGS__)
#define LOG_DEBUG(...) Logger::GetAppLogger().LogFormat(LogLevel::DEBUG_l, __VA_ARGS__)
#define LOG_INFO(...) Logger::GetAppLogger().LogFormat(LogLevel::INFO_l, __VA_ARGS__)
#define LOG_WARN(...) Logger::GetAppLogger().LogFormat(LogLevel::WARN_l, __VA_ARGS__)
#define LOG_ERROR(...) Logger::GetAppLogger().LogFormat(LogLevel::ERROR_l, __VA_ARGS__)
#define LOG_FATAL(...) Logger::FatalHandler(__VA_ARGS__)
#define CORE_LOG_TRACE(...) Logger::GetCoreLogger().LogFormat(LogLevel::TRACE_l, __VA_ARGS__)
#define CORE_LOG_DEBUG(...) Logger::GetCoreLogger().LogFormat(LogLevel::DEBUG_l, __VA_ARGS__)
#define CORE_LOG_INFO(...) Logger::GetCoreLogger().LogFormat(LogLevel::INFO_l, __VA_ARGS__)
#define CORE_LOG_WARN(...) Logger::GetCoreLogger().LogFormat(LogLevel::WARN_l, __VA_ARGS__)
#define CORE_LOG_ERROR(...) Logger::GetCoreLogger().LogFormat(LogLevel::ERROR_l, __VA_ARGS__)
#define CORE_LOG_FATAL(...) Logger::FatalHandler(__VA_ARGS__)
#else
#define LOG_TRACE(...)
#define LOG_DEBUG(...)
#define LOG_INFO(...)
#define LOG_WARN(...)
#define LOG_ERROR(...)
#define LOG_FATAL(...) Logger::FatalHandler(__VA_ARGS__)
#define CORE_LOG_TRACE(...)
#define CORE_LOG_DEBUG(...)
#define CORE_LOG_INFO(...)
#define CORE_LOG_WARN(...)
#define CORE_LOG_ERROR(...)
#define CORE_LOG_FATAL(...) Logger::FatalHandler(__VA_ARGS__)
#endif

View File

@ -6,3 +6,5 @@
#include "components/health_component.h"
#include "components/input_component.h"
#include "components/physics_component.h"
#include "components/ui_component.h"
#include "components/layer_component.h"

View File

@ -1,6 +1,4 @@
#pragma once
#include <unordered_map>
#include <functional>
enum class InputAction {
MOVE_UP,

View File

@ -0,0 +1,7 @@
#pragma once
struct LayerComponent {
int layer;
LayerComponent(int layer = 0) : layer(layer) {}
};

View File

@ -0,0 +1,5 @@
//
// Created by illyum on 9/12/2024.
//
#include "ui_component.h"

View File

@ -0,0 +1,18 @@
#pragma once
enum class UIState {
NORMAL,
HOVERED,
CLICKED
};
struct UIComponent {
std::string text;
float width;
float height;
std::function<void()> onClick;
UIState state = UIState::NORMAL;
UIComponent(std::string text, float width, float height, std::function<void()> onClick)
: text(std::move(text)), width(width), height(height), onClick(std::move(onClick)) {}
};

View File

@ -7,17 +7,15 @@ public:
ENetClient() : client(nullptr), peer(nullptr) {}
void Initialize() override {
Logger& logger = Logger::GetInstance();
if (enet_initialize() != 0) {
logger.LogError("An error occurred while initializing ENet.", "ENetClient");
exit(EXIT_FAILURE);
CORE_LOG_FATAL("An error occurred while initializing ENet");
throw std::runtime_error("An error occurred while initializing ENet");
}
client = enet_host_create(nullptr, 1, 2, 0, 0);
if (!client) {
logger.LogError("An error occurred while creating the ENet client.", "ENetClient");
exit(EXIT_FAILURE);
CORE_LOG_FATAL("An error occurred while creating the ENet client");
throw std::runtime_error("An error occurred while initializing ENet Client");
}
ENetAddress address;
@ -26,42 +24,35 @@ public:
peer = enet_host_connect(client, &address, 2, 0);
if (!peer) {
logger.LogError("No available peers for initiating connection.", "ENetClient");
exit(EXIT_FAILURE);
CORE_LOG_ERROR("No available peers for initiating connection");
}
logger.LogInfo("ENet client initialized and connected to server.", "ENetClient");
CORE_LOG_TRACE("ENet client initialized and connected to server");
}
void Shutdown() override {
Logger& logger = Logger::GetInstance();
if (peer) {
enet_peer_disconnect(peer, 0);
logger.LogInfo("Disconnected from peer.", "ENetClient");
CORE_LOG_TRACE("Disconnected from peer");
}
enet_host_destroy(client);
enet_deinitialize();
logger.LogInfo("ENet client shut down.", "ENetClient");
CORE_LOG_TRACE("ENet client shut down");
}
void SendData(const void* data, size_t size) override {
Logger& logger = Logger::GetInstance();
if (peer) {
ENetPacket* packet = enet_packet_create(data, size, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(peer, 0, packet);
logger.LogDebug("Data sent to server.", "ENetClient");
CORE_LOG_TRACE("Data sent to server");
}
}
void ReceiveData() override {
Logger& logger = Logger::GetInstance();
ENetEvent event;
while (enet_host_service(client, &event, 0) > 0) {
if (event.type == ENET_EVENT_TYPE_RECEIVE) {
logger.LogInfo("Received packet: " + std::string(reinterpret_cast<const char*>(event.packet->data)), "ENetClient");
CORE_LOG_TRACE("Received packet: {}", std::string(reinterpret_cast<const char*>(event.packet->data)));
enet_packet_destroy(event.packet);
}
}

193
src/filepacker.cpp Normal file
View File

@ -0,0 +1,193 @@
//
// Created by illyum on 9/13/2024.
//
#include "filepacker.h"
#include "Logger.h"
bool FilePacker::Pack(const std::string &directoryPath, const std::string &outputBapFile) {
BapHeader header;
std::ofstream outputFile(outputBapFile, std::ios::binary);
if (!outputFile) {
CORE_LOG_ERROR("Failed to open output file");
return false;
}
std::memcpy(header.magic, static_cast<const char*>("BAP"), 3);
header.version = PACKER_VERSION;
header.fileCount = 0;
std::vector<std::pair<FileEntry, std::vector<char>>> filesData;
size_t headerSize = 3 + sizeof(header.version) + sizeof(header.fileCount);
for (const auto& entry : std::filesystem::directory_iterator(directoryPath)) {
if (entry.is_regular_file()) {
FileEntry fileEntry;
fileEntry.filePath = entry.path().string();
// TODO: Implement filetype detection
fileEntry.fileType = RAWBINARY;
fileEntry.compressedSize = COMPRESSION_NONE;
std::ifstream inputFile(entry.path(), std::ios::binary | std::ios::ate);
if (!inputFile) {
CORE_LOG_ERROR("FilePacker::Pack(): Failed to open file for packing: {}", entry.path().string());
return false;
}
fileEntry.dataSize = inputFile.tellg();
inputFile.seekg(0, std::ios::beg);
std::vector<char> fileData(fileEntry.dataSize);
inputFile.read(fileData.data(), fileEntry.dataSize);
inputFile.close();
// TODO: Implement data compression
std::vector<char> compressedData = CompressData(fileData, fileEntry.compression);
fileEntry.compressedSize = compressedData.size();
filesData.emplace_back(fileEntry, std::move(compressedData));
size_t pathLength = fileEntry.filePath.size();
size_t metadataLength = fileEntry.metadata.size();
headerSize += sizeof(pathLength) + pathLength + sizeof(fileEntry.fileType) + sizeof(fileEntry.compression)
+ sizeof(fileEntry.dataSize) + sizeof(fileEntry.compressedSize) + sizeof(fileEntry.dataOffset)
+ sizeof(metadataLength) + metadataLength;
header.fileEntries.push_back(fileEntry);
header.fileCount++;
}
}
outputFile.seekp(headerSize, std::ios::beg);
for (size_t i = 0; i < filesData.size(); ++i) {
auto& fileEntry = header.fileEntries[i];
const auto& compressedData = filesData[i].second;
fileEntry.dataOffset = outputFile.tellp();
outputFile.write(compressedData.data(), compressedData.size());
}
outputFile.seekp(0, std::ios::beg);
WriteHeader(outputFile, header);
outputFile.close();
return true;
}
std::vector<FileEntry> FilePacker::listFiles(const std::string& bapFile) {
std::ifstream inputFile(bapFile, std::ios::binary);
if (!inputFile) {
CORE_LOG_FATAL("Failed to open .bap file for listing: {}", bapFile);
throw std::runtime_error("File could not be opened");
}
BapHeader header = ReadHeader(inputFile);
inputFile.close();
return header.fileEntries;
}
bool FilePacker::ExtractFile(const std::string& bapFile, const std::string& fileName, const std::string& outputPath) {
std::ifstream inputFile(bapFile, std::ios::binary);
if (!inputFile) {
CORE_LOG_ERROR("Failed to open .bap file for extraction: {}", bapFile);
return false;
}
BapHeader header = ReadHeader(inputFile);
for (const auto& fileEntry : header.fileEntries) {
if (fileEntry.filePath == fileName) {
std::ofstream outFile(outputPath, std::ios::binary);
if (!outFile) {
CORE_LOG_ERROR("Failed to create output file: {}", outputPath);
return false;
}
std::vector<char> compressedData(fileEntry.compressedSize);
inputFile.seekg(fileEntry.dataOffset, std::ios::beg);
inputFile.read(compressedData.data(), compressedData.size());
std::vector<char> decompressedData = DecompressData(compressedData, fileEntry.compression);
outFile.write(decompressedData.data(), decompressedData.size());
outFile.close();
return true;
}
}
CORE_LOG_ERROR("File not found in .bap archive: {}", fileName);
return false;
}
void FilePacker::WriteHeader(std::ofstream& outFile, const BapHeader& header) {
outFile.write(header.magic, 3);
outFile.write(reinterpret_cast<const char*>(&header.version), sizeof(header.version));
outFile.write(reinterpret_cast<const char*>(&header.fileCount), sizeof(header.fileCount));
for (const auto& fileEntry : header.fileEntries) {
size_t pathLength = fileEntry.filePath.size();
outFile.write(reinterpret_cast<const char*>(&pathLength), sizeof(pathLength));
outFile.write(fileEntry.filePath.c_str(), pathLength);
outFile.write(reinterpret_cast<const char*>(&fileEntry.fileType), sizeof(fileEntry.fileType));
outFile.write(reinterpret_cast<const char*>(&fileEntry.compression), sizeof(fileEntry.compression));
outFile.write(reinterpret_cast<const char*>(&fileEntry.dataSize), sizeof(fileEntry.dataSize));
outFile.write(reinterpret_cast<const char*>(&fileEntry.compressedSize), sizeof(fileEntry.compressedSize));
outFile.write(reinterpret_cast<const char*>(&fileEntry.dataOffset), sizeof(fileEntry.dataOffset));
size_t metadataLength = fileEntry.metadata.size();
outFile.write(reinterpret_cast<const char*>(&metadataLength), sizeof(metadataLength));
outFile.write(fileEntry.metadata.c_str(), metadataLength);
}
}
BapHeader FilePacker::ReadHeader(std::ifstream& inFile) {
BapHeader header;
inFile.read(header.magic, 3);
inFile.read(reinterpret_cast<char*>(&header.version), sizeof(header.version));
inFile.read(reinterpret_cast<char*>(&header.fileCount), sizeof(header.fileCount));
if (header.version != PACKER_VERSION) {
CORE_LOG_FATAL("FilePacker::ReadHeader(): Wrong .bap file version.");
throw std::runtime_error("Invalid bap file version");
}
for (int i = 0; i < header.fileCount; ++i) {
FileEntry fileEntry;
size_t pathLength;
inFile.read(reinterpret_cast<char*>(&pathLength), sizeof(pathLength));
fileEntry.filePath.resize(pathLength);
inFile.read(fileEntry.filePath.data(), pathLength);
inFile.read(reinterpret_cast<char*>(&fileEntry.fileType), sizeof(fileEntry.fileType));
inFile.read(reinterpret_cast<char*>(&fileEntry.compression), sizeof(fileEntry.compression));
inFile.read(reinterpret_cast<char*>(&fileEntry.dataSize), sizeof(fileEntry.dataSize));
inFile.read(reinterpret_cast<char*>(&fileEntry.compressedSize), sizeof(fileEntry.compressedSize));
inFile.read(reinterpret_cast<char*>(&fileEntry.dataOffset), sizeof(fileEntry.dataOffset));
size_t metadataLength;
inFile.read(reinterpret_cast<char*>(&metadataLength), sizeof(metadataLength));
fileEntry.metadata.resize(metadataLength);
inFile.read(fileEntry.metadata.data(), metadataLength);
header.fileEntries.push_back(fileEntry);
}
return header;
}
std::vector<char> FilePacker::CompressData(const std::vector<char>& input, CompressionType compression) {
// IMPL: Implement actual compression on a per file basis
if (compression == COMPRESSION_NONE) {
return input;
}
return input;
}
std::vector<char> FilePacker::DecompressData(const std::vector<char>& input, CompressionType compression) {
// IMPL: Implement decompression on a per file basis
if (compression == COMPRESSION_NONE) {
return input;
}
return input;
}

55
src/filepacker.h Normal file
View File

@ -0,0 +1,55 @@
//
// Created by illyum on 9/13/2024.
//
#ifndef FILEPACKER_H
#define FILEPACKER_H
#include <IsoEnginePCH.h>
#define BAP_MAGIC "BAP"
#define PACKER_VERSION 1
enum CompressionType {
COMPRESSION_NONE,
COMPRESSION_LZMA
};
enum FileType {
PNG,
RAWTEXT,
RAWBINARY
};
struct FileEntry {
std::string filePath;
FileType fileType;
CompressionType compression;
size_t dataSize;
size_t compressedSize;
std::string metadata;
size_t dataOffset;
};
struct BapHeader {
char magic[3];
int version;
int fileCount;
std::vector<FileEntry> fileEntries;
};
class FilePacker {
public:
bool Pack(const std::string& directoryPath, const std::string& outputBapFile);
bool Unpack(const std::string& bapFile, const std::string& outputDirectory);
std::vector<FileEntry> listFiles(const std::string& bapFile);
bool ExtractFile(const std::string& bapFile, const std::string& fileName, const std::string& outputPath);
private:
void WriteHeader(std::ofstream& outFile, const BapHeader& header);
BapHeader ReadHeader(std::ifstream& inFile);
std::vector<char> CompressData(const std::vector<char>& input, CompressionType compression);
std::vector<char> DecompressData(const std::vector<char>& input, CompressionType compression);
};
#endif //FILEPACKER_H

View File

@ -3,8 +3,6 @@
#define SERVER_PORT 6771
#define SERVER_ADDRESS "127.0.0.1"
#include <iostream>
class Network {
public:
virtual void Initialize() = 0;

View File

@ -0,0 +1,38 @@
//
// Created by illyum on 9/13/2024.
//
#include "FatalHandler.h"
#include <execinfo.h>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <sstream>
#include <format>
namespace IsoEngine {
template<typename... Args>
void FatalHandler(const std::string& format, Args... args) {
std::ostringstream stream;
stream << std::format(format, args...);
// TODO: change the name of the file to reflect the time the crash happened
std::ofstream crashFile("crash.log", std::ios::app);
crashFile << "FATAL ERROR: " << stream.str() << std::endl;
void* callstack[128];
int frames = backtrace(callstack, 128);
char** symbols = backtrace_symbols(callstack, frames);
crashFile << "Stack trace:\n";
for (int i = 0; i < frames; ++i) {
crashFile << symbols[i] << std::endl;
}
free(symbols);
crashFile.close();
std::abort();
}
}

View File

@ -0,0 +1,64 @@
//
// Created by illyum on 9/13/2024.
//
#include "FatalHandler.h"
#include <windows.h>
#include <dbghelp.h>
#include <fstream>
#include <sstream>
#include <format>
#pragma comment(lib, "dbghelp.lib")
namespace IsoEngine {
template<typename... Args>
void FatalHandler(const std::string &format, Args... args) {
std::ostringstream stream;
stream << std::format(format, args...);
// TODO: change the name of the file to reflect the time the crash happened
std::ofstream crashFile("crash.log", std::ios::app);
crashFile << "FATAL ERROR: " << stream.str() << std::endl;
HANDLE process = GetCurrentProcess();
SymInitialize(process, NULL, TRUE);
CONTEXT context;
RtlCaptureContext(&context);
STACKFRAME64 stackFrame = {};
stackFrame.AddrPC.Offset = context.Rip;
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = context.Rbp;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = context.Rsp;
stackFrame.AddrStack.Mode = AddrModeFlat;
crashFile << "Stack trace:\n";
while (StackWalk64(IMAGE_FILE_MACHINE_AMD64, process, GetCurrentThread(), &stackFrame, &context, NULL,
SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
DWORD64 address = stackFrame.AddrPC.Offset;
if (address == 0) {
break;
}
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
PSYMBOL_INFO symbol = (PSYMBOL_INFO) buffer;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYM_NAME;
DWORD64 displacement = 0;
if (SymFromAddr(process, address, &displacement, symbol)) {
crashFile << symbol->Name << " - 0x" << std::hex << symbol->Address << std::dec << "\n";
} else {
crashFile << "Unresolved symbol at address: 0x" << std::hex << address << std::dec << "\n";
}
}
SymCleanup(process);
crashFile.close();
std::abort();
}
}

110
src/scene_manager.cpp Normal file
View File

@ -0,0 +1,110 @@
#include "scene_manager.h"
#include "Logger.h"
#include "raylib.h"
SceneManager::SceneManager(entt::registry& registry) : registry(registry), activeScene(entt::null) {}
void SceneManager::SetActiveScene(entt::entity scene) {
if (activeScene != entt::null) {
auto& currentScene = registry.get<SceneComponent>(activeScene);
currentScene.isActive = false;
}
activeScene = scene;
auto& newScene = registry.get<SceneComponent>(activeScene);
newScene.isActive = true;
}
entt::entity SceneManager::CreateScene() {
entt::entity scene = registry.create();
registry.emplace<SceneComponent>(scene, false);
return scene;
}
void SceneManager::UpdateActiveScene(float delta) {
if (activeScene != entt::null) {
// Iterate over entities in the active scene that have an InputComponent
auto view = registry.view<InputComponent>();
for (auto entity : view) {
auto& inputComponent = view.get<InputComponent>(entity);
inputComponent.Update(); // This will call the input logic
}
}
}
void SceneManager::RenderActiveScene() {
if (activeScene != entt::null) {
// Create a multimap to store entities sorted by layer
std::multimap<int, entt::entity> layeredEntities;
// Gather all entities with a LayerComponent
auto view = registry.view<LayerComponent, SpriteComponent, TransformComponent>();
for (auto entity : view) {
auto& layer = view.get<LayerComponent>(entity);
layeredEntities.insert({ layer.layer, entity });
}
// Render all entities sorted by layer
for (auto& pair : layeredEntities) {
auto entity = pair.second;
auto& sprite = registry.get<SpriteComponent>(entity);
auto& transform = registry.get<TransformComponent>(entity);
// Render the sprite at the position defined by the TransformComponent
sprite.Render(transform);
}
}
}
void SceneManager::UpdateUI(entt::registry& registry) {
Vector2 mousePosition = GetMousePosition(); // This assumes a raylib-like GetMousePosition, adapt as needed for your system
auto view = registry.view<UIComponent, TransformComponent>();
for (auto entity : view) {
auto& ui = view.get<UIComponent>(entity);
auto& transform = view.get<TransformComponent>(entity);
// Check if the mouse is over the button
bool isHovered = (mousePosition.x > transform.x && mousePosition.x < transform.x + ui.width &&
mousePosition.y > transform.y && mousePosition.y < transform.y + ui.height);
if (isHovered) {
ui.state = IsMouseButtonPressed(MOUSE_LEFT_BUTTON) ? UIState::CLICKED : UIState::HOVERED;
// If clicked, call the onClick function
if (ui.state == UIState::CLICKED && ui.onClick) {
ui.onClick();
}
} else {
ui.state = UIState::NORMAL;
}
}
}
void SceneManager::RenderUI(entt::registry& registry) {
auto view = registry.view<UIComponent, TransformComponent>();
for (auto entity : view) {
auto& ui = view.get<UIComponent>(entity);
auto& transform = view.get<TransformComponent>(entity);
// Render the button background
Color buttonColor = DARKGRAY;
if (ui.state == UIState::HOVERED) {
buttonColor = LIGHTGRAY;
} else if (ui.state == UIState::CLICKED) {
buttonColor = GRAY;
}
// Use the raw transform position (no adjustment to center screen)
DrawRectangle(transform.x, transform.y, ui.width, ui.height, buttonColor);
// Render the button text (centered within the button)
int textWidth = MeasureText(ui.text.c_str(), 20);
int textX = transform.x + (ui.width / 2) - (textWidth / 2);
int textY = transform.y + (ui.height / 2) - 10; // Slight vertical adjustment
DrawText(ui.text.c_str(), textX, textY, 20, BLACK);
}
}

View File

@ -5,50 +5,14 @@
class SceneManager {
public:
SceneManager(entt::registry& registry) : registry(registry), activeScene(entt::null) {}
void SetActiveScene(entt::entity scene) {
if (activeScene != entt::null) {
auto& currentScene = registry.get<SceneComponent>(activeScene);
currentScene.isActive = false;
}
activeScene = scene;
auto& newScene = registry.get<SceneComponent>(activeScene);
newScene.isActive = true;
}
entt::entity CreateScene() {
entt::entity scene = registry.create();
registry.emplace<SceneComponent>(scene, false);
return scene;
}
void UpdateActiveScene() {
if (activeScene != entt::null) {
// Iterate over entities in the active scene that have an InputComponent
auto view = registry.view<InputComponent>();
for (auto entity : view) {
auto& inputComponent = view.get<InputComponent>(entity);
inputComponent.Update(); // This will call the input logic
}
}
}
void RenderActiveScene() {
if (activeScene != entt::null) {
// Iterate over entities in the active scene that have both SpriteComponent and TransformComponent
auto view = registry.view<SpriteComponent, TransformComponent>();
for (auto entity : view) {
auto& sprite = view.get<SpriteComponent>(entity);
auto& transform = view.get<TransformComponent>(entity);
// Render the sprite at the position defined by the TransformComponent
sprite.Render(transform);
}
}
}
SceneManager(entt::registry& registry);
void SetActiveScene(entt::entity scene);
entt::entity CreateScene();
void UpdateActiveScene(float delta);
void RenderActiveScene();
void UpdateUI(entt::registry& registry);
void RenderUI(entt::registry& registry);
private:
entt::registry& registry;