diff --git a/src/content/posts/relearning-programming-introduction.md b/src/content/posts/relearning-programming-introduction.md new file mode 100644 index 0000000..5bfd829 --- /dev/null +++ b/src/content/posts/relearning-programming-introduction.md @@ -0,0 +1,18 @@ +--- +title: 'Relearning Programming Introduction' +pubDate: '2025-11-21' +tag: 'study' +--- + +![_tr-808](./_assets/tr-808.jpg) + +# Foreword +Hey, +I suck at coding - and that's why I decided to relearn programming. My language of choice is C++. I've been working with this language for a long time now, and it just grew on me like no other language did. + +# What Led Me Here +I've been struggling with my problem solving + the core concepts of C++ itself. Even though I am aware of the fact that it's totally common to +make mistakes, I've had the overwhelming feeling of frustration and "imposter syndrome". I tolerated it for too long. + +# My Plan and Goals +My goals with relearning programming and C++ is to thoroughly study and apply my knowledge with a modern approach to C++. diff --git a/src/content/posts/test-blog.md b/src/content/posts/test-blog.md index 8fe7565..831ed3c 100644 --- a/src/content/posts/test-blog.md +++ b/src/content/posts/test-blog.md @@ -6,4 +6,4 @@ topic: 'general' ![](./_assets/happy_dawg.png) -WELCOME!!!! \ No newline at end of file +WELCOME!!!! diff --git a/src/content/posts/the-entity-component-system.md b/src/content/posts/the-entity-component-system.md deleted file mode 100644 index 450a713..0000000 --- a/src/content/posts/the-entity-component-system.md +++ /dev/null @@ -1,342 +0,0 @@ ---- -title: 'Game Engine Series I - The Entity Component System' -pubDate: '2025-07-19' -tag: 'games' ---- - -# -Welcome to the Game Engine Series. - -In this series of blog posts, I'll be diving into one of my favorite topics:
-**Game Engine Development** - -The idea came up when I was in the middle on developing my own game engine. While -this blog serves as a purpose to teach and explain what I've learned -to programmers, this blog is also a way for my to track my progress and refine my -own understanding along the way. - -Whether you're just curious about how game engines work under the hood, or you're -building one yourself. I hope this series gives you useful insights, ideas and -maybe even some inspiration for your future projects, or even your own game engine. - -Since I want to teach you the principles of an ECS and make it -beginner-friendly, I purposely stripped the code down and removed things like multithreading and more. -These more advanced topics will be discussed in the future. - -# Core Concepts Explained -ECS stands for Entity Component System. It is a data-oriented -design pattern that separates data (_components_) from behavior (_systems_), using -entities as simple IDs to associate them. - -- **Entity** Consists of nothing but an ID. Think of it like a dumb label:
- `Player = 1`, `Tree = 2`, `Boss1 = 523`
-- **Component** - Components are attached to entities and represent what that entity - is made of.
- `Position { float x, y }`, `Health { int hp }`, `Level { uint8_t level }` -- **System** - This can be a `physics system`, `rendering system`, or whatever your game - needs. These systems iterate over your stored entites and apply the systems to them. - -# Why use ECS over OOP? -Don't get me wrong. ECS takes advantage of OOP as well, but the difference lies in the -use of OOP. Traditionally, beginners learn one thing - inheritance (_including polymorphism_). -I would say that it's quite primitive to have one base class called `Entity`, -and another class called `Player`, which is derived from the base class `Entity`, -so it would look something like this: - -```cpp -class Entity { -public: - Entity(); - virtual ~Entity(); - - virtual void update() = 0; - virtual void render() = 0; -} - -class Player : public Entity { -private: - // Some vars, objects or whatever - -public: - Player() = default; - virtual ~Player() = default; - - void update() override { /* Update logic of player */ } - void render() override { /* Render player */ } -} -``` - -Would you notice what's wrong there? Imagine this shape: -![](./_assets/oop-diamond-inheritance.png) -- Class `B` and class `C` both inherit from `A`. -- Class `D` inherits from both `B` and `C`. - -So now, if `D` tries to access a member from `A`, which copy does it get? - -Here, let's look at another example: -```cpp -class Enemy { -public: - void update() { /* generic update stuff */ } -}; - -class FlyingEnemy : public Enemy { -public: - void update() { /* flying logic */ } -}; - -class ShootingEnemy : public Enemy { -public: - void update() { /* shooting logic */ } -}; - -class FlyingShootingEnemy : public FlyingEnemy, public ShootingEnemy { -public: - void update() { - FlyingEnemy::update(); - ShootingEnemy::update(); - // shit's messy - } -}; -``` - -- `FlyingShootingEnemy` now has two instances of `Enemy` in its inheritance tree. -- If `Enemy` has members or state, they're being duplicated, which leads to ambiguity. - -The compiler gets confused, and so will you. Your project will end up in a virtual hell. -But these aren’t the only problems you’ll face. - -As your project scales, this architecture will slam you into several brick walls: -- Virtual dispatches are relatively slow - - Every virtual call to `update()` or `render()` goes through a so-called vtable. - A vtable or virtual table in C++ is a lookup table of function pointers maintained - by the compiler for each class that has virtual functions, meaning heavy use of - virtual functions will end up with a bunch of overhead, that can be avoided by ditching that - programming pattern. - - The diamond inheritance problem mentioned above. - - Poor caching - - OOP often scatters data all over memory because each object carries it's own data + vptr. - - Hard to Extend Beheavior Dynamically - - Would you like to add or remove abilities (e.g.: canShoot(), canWalk()) at runtime? - - And much more... - -See? I could go on about the issues with this approach, but these are the biggest brick walls you’ll hit. - -# The Solution -It means moving away from traditional Object-Oriented Programming (OOP) patterns like inheritance and polymorphism, -and instead starting to think in Data-Oriented design / programming (DOD). - -Instead of organizing your game logic around what objects are (e.g.: `class Player : public Entity`), -data-oriented design focuses on _what data you operate on_ and _how to structure that data to make the CPU -cache happy_. It's less about entities and more about the systems and data layout. - -Here is roughly what I came up with: - -1. First I created a Component.hpp file, exclusively for the components: -```cpp -#include -#include "raylib.h" - -namespace DREAM { - struct Health { int value = 100; }; - - struct Attack { int value = 1; }; - - struct Defense { int value = 1; }; - - struct Level { uint8_t value = 1; }; - - struct Position { Vector2 value = {0.0f, 0.0f}; }; - - struct Velocity { Vector2 value = {0.0f, 0.0f}; }; -} -``` - -Then I implemented following in the ComponentManager: -```cpp - using Entity = std::uint32_t; - - class InterfaceComponentArray { - public: - virtual ~InterfaceComponentArray() = default; - virtual void entityDestroyed(Entity entity) = 0; - }; -``` - -You might get a rough understanding on what I am trying to achieve here. Do you see the assignment of -Entity just being a simple ID/number? - -The `InterfaceComponentArray` only knows about the method `entityDestroyed()`. It will make much more sense -in a bit. - -After this we define the `ComponentArray` class: -```cpp - template - class ComponentArray : public InterfaceComponentArray { - private: - std::unordered_map m_componentMap; - - public: - ComponentArray() = default; - ~ComponentArray() override = default; - - void insertData(Entity entity, const T& component) { - // Insert new data / component for entity - } - - void removeData(Entity entity) { - // Remove data - } - - T& getData(Entity entity) { - // Return data - } - - bool hasData(Entity entity) const { - // Check if entity has any data - } - - void entityDestroyed(Entity entity) override { - // Destroy entity's data - } - }; -``` - -This provides a dedicated storage and dedicated API for each `ComponentArray` we create (e.g.: `ComponentArray`). - -Theoretically, the `ComponentManager` would work like this, but the user might have a hard time using the API. -That's why we will create a `ComponentManager` class and wrap the `ComponentArray` methods in it: -```cpp -class ComponentManager { - private: - std::unordered_map> m_componentArrays; - - template - std::shared_ptr> getComponentArray() { - const auto typeId = std::type_index(typeid(T)); - auto it = m_componentArrays.find(typeId); - if (it == m_componentArrays.end()) { - fmt::print( - fmt::emphasis::bold | fmt::fg(fmt::color::red), - "[Error] ComponentManager::getComponentArray -> Component type {} not registered.\n", - typeId.name() - ); - throw std::runtime_error("Component type not registered"); - } - return std::static_pointer_cast>(it->second); - } - - public: - ComponentManager() = default; - ~ComponentManager() = default; - - template - void registerComponent() { - const auto typeId = std::type_index(typeid(T)); - if (m_componentArrays.contains(typeId)) { - fmt::print( - fmt::emphasis::bold | fmt::fg(fmt::color::yellow), - "[Warning] ComponentManager::registerComponent -> Component type {} already registered.\n", - typeId.name() - ); - return; - } - m_componentArrays.emplace(typeId, std::make_shared>()); - } - - template - void addComponent(Entity entity, const T& component) { - getComponentArray()->insertData(entity, component); - } - - template - void removeComponent(Entity entity) { - getComponentArray()->removeData(entity); - } - - template - T& getComponent(Entity entity) { - return getComponentArray()->getData(entity); - } - - template - bool hasComponent(Entity entity) { - return getComponentArray()->hasData(entity); - } - - void destroyEntity(Entity entity) { - for (auto& [_, componentArray] : m_componentArrays) { - componentArray->entityDestroyed(entity); - } - } - }; -``` - -In this step we simply wrapped the `ComponentArray` methods around methods from the `ComponentManager`. -After calling `registerComponent()`, a ComponentArray for the component of your need gets created. -This map then gets stored in another map, which is located in the `ComponentManager` class. - -Using this API could look something like this: -```cpp -int main() { - ComponentManager componentManager; - - // Register needed component types: - componentManager.registerComponent(); - componentManager.registerComponent(); - componentManager.registerComponent(); - - // Create some entities (For now just IDs, unless you have an EntityManager) - Entity player = 1; - Entity enemy = 2; - - // Attach components to your entities - componentManager.addComponent(player, Position{ 100.0f, 200.0f }); - componentManager.addComponent(player, Velocity{ 1.5f, 0.0f }); - componentManager.addComponent(player, Health{ 50 }); - - componentManager.addComponent(enemy, Position{ 150.0f, 100.0f }); - componentManager.addComponent(enemy, Velocity{ 1.5f, 0.0f }); - componentManager.addComponent(enemy, Health{ 20 }); - - // Simple movement "system" - // Imagine you have a list of all entities with both Position & Velocity - for (Entity e : { player, enemy }) { - if (componentManager.hasComponent(e) && - componentManager.hasComponent(e)) { - auto& pos = componentManager.getComponent(e); - auto& vel = componentManager.getComponent(e); - pos.position.x += vel.velocity.x; - pos.position.y += vel.velocity.y; - } - } - - componentManager.removeComponent(enemy); - componentManager.destroyEntity(enemy); -} -``` - -That's quite a lot to read and grasp, right? Don't worry. ECS design can be tricky at first, but once -you grasp the full concept, it will stick in your mind and I will guarantee you that it's awesome. - -All this code made it possible to work with entities in a flexible & drastically improved way. -Also keep in mind that this code only should be used for reference and not blatant copying, -because I left some parts out on purpose to avoid blasting you a couple of hundred lines of code -in your face. - - -# Common Pitfalls -You’ve probably encountered this already, but it’s worth repeating. -Plenty of developers lose their way in the jungle by over-engineering their code and end up abusing -ECS like they do with inheritance or polymorphism. - -Also - don't overuse components. Too many tiny ones can bloat your systems with excessive iterations -and seriously hurt performance. - -# Conclusion -There we are at the end of today's post. You probably need to digest the amount of information you've -just obtained, but that's totally fine. As mentioned previously, even though I showed you the basics -of an ECS, learning a topic like this is messy at first, especially since it requires 'rethinking' -how code can be structured so generically. - -Hopefully, I will see you next time on future posts :) \ No newline at end of file