Skip to content
/ divvy Public

A lightweight, single-header, component entity framework in C++

License

Notifications You must be signed in to change notification settings

puradox/divvy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Divvy - A lightweight Component Entity framework Build Status

Current version: v0.8

Divvy is a lightweight component entity framework made in C++11. Licensed under the MIT license and designed to be extremely easy to integrate, it's purpose is to ease development where big, monolithic class structures would normally be the answer. See all of the details, benefits, and drawbacks of the Component pattern here.

Features

  • Simple, no macros or unneccessary clutter
  • Extendable, Components can be created for any purpose and are easy to make
  • Lightweight, only three fundimental parts (Component, Entity, and World)
  • Easy to integrate, all of Divvy is implemented in a single header file (no linking!)
  • Fast, contiguous memory storage of Components allows for faster iterations
  • Type-safe, std::enable_if ensures that Components can't be mixed up

Purpose

Divvy was made in mind for people that desire a simple, lightweight way to utilize the Component pattern in their program. Most other Component Entity libraries are large and requires users to download, compile, then link against their library. With the introduction of Divvy, all users have to do is copy and paste the single header file into their project and start using the Component pattern without additional hassle!

Divvy was originally geared towards game development; however, it can be used for other areas of development that could benefit from interchangable programming bits.

Quick Example

#include <iostream>
#include <string>

#include "divvy.hpp"

class Nametag : public divvy::Component
{
public:
    Nametag() {}

    Nametag(const std::string& name) : m_name(name) {}

    virtual void update()
    {
        std::cout << "Hello! My name is " << m_name << ".\n";
    }

    virtual void clone(const divvy::Component& other)
    {
        m_name = divvy::cast<Nametag>(other).m_name;
    }

    void setName(const std::string& name) { m_name = name; }

private:
    std::string m_name;
};

int main()
{
    divvy::World world;
    world.add<Nametag>();

    divvy::Entity hero(world);
    hero.add<Nametag>("Mario");

    world.update(); // OUTPUT: Hello! My name is Mario.

    hero.get<Nametag>().setName("Luigi");

    world.update(); // OUTPUT: Hello! My name is Luigi.
}

More Examples

To view more examples, visit the examples folder in this repository.

Dependencies

Divvy is dependant on the C++11 STL, so make sure to use a C++11 compliant compiler when using Divvy. Don't forget to compile your C++ program with the -std=c++11 or equivalent flag!

Integrating Divvy into your project

To use Divvy, simply copy over the single header file include/divvy.hpp of this repository into your project.

Testing

Unit tests are ran on the GoogleTest framework; however, you don't have to download/install GoogleTest in order to run the tests, since it is a git submodule of this repository.

To clone and test this repository with GoogleTest on Linux:

git clone --recursive https://github.com/puradox/divvy
cd divvy && mkdir build && cd build
cmake ..
make && make test

For a more detailed test with additional information and time log:

./test/divvy_test

Documentation

Divvy is based on the usage of three different classes types, each will be further explained in their own section.

  • Component: The base class that all Components have to derive from.
  • Entity: Identifier that unifies a collection of Components. An interface to manipulate Components.
  • World: Container for all Components and Entity associations.

Quick Reference

Here is the full list of public constructors and methods available, by class.

Component Function Description
const T& cast<T>(const Component& other) Cast base to derived Component
Entity Constructor Description
Entity() Create an invalid Entity
Entity(World& world) Create an Entity in the specified World
Entity(Entity&& other) Move an Entity
Entity(const Entity& other) Clone an Entity in the same World
Entity(const Entity& other, World& world) Clone an Entity in the specified World
Entity Method Description
Component& Entity.add<Component>(...) Assign a Component
Component& Entity.get<Component>() Retrieve a Component
bool Entity.has<Component>() Check if a Component is assigned
void Entity.remove<Component>() Remove a Component
void Entity.reset() Corresponding reset method for every constructor
Entity.valid() Check if an Entity is valid
World Method Description
void World.add<Component>() Register a Component type
bool World.has<Component>() Check if a Component type is registered
void World.remove<Component>() Unregister a Component type
void World.clear() Clear all Entities and Components
void World.update() Update all Components

Component

Components are essential to decoupling code and forming a modular codebase. Component is meant to be inherited into your own component type. To create a valid component, we must adhere to the following rules:

  1. Publicly inherit from divvy::Component
  2. Implement a default constructor that takes no arguments

Component contains two pure virtual methods that you have to implement when creating your own component:

  • update: provides functionality to your Component
  • clone: provides copy semantics to your Component

Additionally, Components hold a pointer m_entity to the Entity that is assigned to them. Later on, we will see how this is useful and where you could possibily use it. (See Checking For Components)

Note that divvy::cast<T>(other) is the exact same as static_cast<const T&>(other). The cast function was added in v0.6 to help readability when implementing the virtual clone method of Component, there is no extra functionality behind it.

World

A World contains all of the possible component types that you can add to an Entity.

divvy::World world;

Adding Component Types

It is important that we add all the component types that we want into the World before we assign any components to an Entity.

world.add<Nametag>();

Checking Component Types

world.has<Nametag>();

The has method checks whether a specific component type exists in a World. It returns true if the component type is included in the World, false otherwise.

Updating

Whenever a World is updated, all of the Components that are active inside the World are updated as well.

world.update();

Removing Component Types

We can remove component types in the same manner in which we added them.

world.remove<Nametag>();

Any Entities that have the removed Component assigned will have it removed.

Clearing

world.clear();

Clearing a World results in the deactivation of all the Components and Entities that are associated with it. This means that any existing Entities that operate under the cleared World will become invalid.

Entity

Entity is the interface to add, remove, and retrieve components. To act as this interface, Entities have to be assigned to a World, since the World is what holds all of the Components. If there is no World assigned, the Entity is considered to be invalid and won't be of any use. Trying to use an invalid Entity will result in an exception being thrown.

To create a valid Entity, either pass a World to the constructor,

divvy::Entity hero(world);

or pass a World to the reset(...) method.

divvy::Entity hero;
hero.reset(world);

Although these two ways are equivalent, the reset method is most useful when creating an array of Entity. In which case Entities would be created using the default constructor, which doesn't assign a World. So to combat this, you could run the reset method on all of the elements in the array to assign a World. Note: there is a corresponding reset method for every constructor of Entity.

Checking validity

The validity of an Entity depends of whether or not it is associated with a World. You can check the validity of an Entity with the valid method, which returns either true or false.

hero.valid();

Adding Components

hero.add<Nametag>("Mario");

When Components are added to Entities, the constructor that matches the parameter list of add will be called. This allows for overloaded constructors to be utilized.

Retrieving Components

The Entity interface also makes retrieving components as easy as adding them.

hero.get<Nametag>().setName("Luigi");

world.update(); // OUTPUT: Hello! My name is Luigi.

Copying/Moving

Entities have the additional functionality of being copable and movable. However, it is important to remember that copying and moving are fundamentially different.

  • Copying: calls the clone method of each Component, which copies the specified variables
  • Moving: moves the reference of the Entity, leaving the other Entity invalid
// Copying
divvy::Entity enemy(hero);
enemy.get<Nametag>().setName("Bowser");

// Moving
divvy::Entity princess = std::move(hero);
princess.get<Nametag>().setName("Peach");

// Check if the move was successful
if (princess.valid() && !hero.valid())
{
    world.update();
}

/* OUTPUT:
Hello! My name is Peach.
Hello! My name is Bowser.
*/

Copying Between Worlds

If a situation appears in which you would want to copy Entities between two different worlds, there are two different ways to approach it.

You could use the overloaded constructor,

divvy::Entity otherHero(hero, otherWorld);

or corresponding reset method.

otherHero.reset(hero, otherWorld);

Here is a full example using the Nametag component in the Quick Example.

divvy::World earth;
world.add<Nametag>();
divvy::Entity human(world);
human.add<Nametag>("astronaut");

divvy::World mars;
mars.add<Nametag>();
divvy::Entity martian(human, mars);

mars.update(); // OUTPUT: Hello! My name is astronaut.

When copying entities between two different worlds, only the component types that exist in both worlds will be copied over.

Checking for Components

if (enemy.has<Nametag>())
{
    std::cout << "Enemy has a name! \n";
}

It can serve useful to check whether an Entity has a specific Component. The has method allows for such a check to be possible. This is especially useful to check whether an Entity has the required Components before adding another Component.

class Physics : public divvy::Component
{
    Physics()
    {
        if (!m_entity->has<Transform>() || !m_entity->has<Mass>())
        {
            std::cout << "Physics requires Transform and Mass to already be present. \n";
            m_entity->remove<Physics>();
        }
    }

    ...
}

Again, to avoid confusion, m_entity is a protected pointer built into Component that points to it's assigned Entity.

Removing Components

enemy.remove<Nametag>();

This immediately deactives the Component and removes it from the Entity that it is assigned to.

That's all!

If you have any more questions about Divvy and how it works, you could either

About

A lightweight, single-header, component entity framework in C++

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published