GithubHelp home page GithubHelp logo

clique_ecs's Introduction

Clique_ECS

Entity-Component-System with Unique Tree-based Sub-Grouping Mechanism

REPORT

A report on the implementation details and decisions of Clique, as well as the naive benchmarking performed on this ECS can be found here:

Report & Description of CliqueECS

EXAMPLE

Please see ECS_src/ExampleECS.h for an example of using this ECS design, and descriptions of its expectations and components.

WARNING: the physics math here is convenient for demonstration but offensively wrong; I leave it as an exercise to correct this.

#define UNIFORM_RAND() (float)(std::rand()) / (float)RAND_MAX

// Not mentioned in the paper: a resource object represents global state for an ECS.
// It'm_str accessible to every system (that wants it), and would require strict synchronization primitives in parallel ECSs
struct ResourceObject {
    std::ofstream out;
    float deltaTime;
    std::clock_t lastFrame;
};

/// *** COMPONENTS ***

// Our position component
struct Position {
    float x, y, z;
};
// Our velocity component
struct DynamicVelocity {
    float x, y, z;
};

/// ******************

// System functions MUST use component pointers.
// References are possible (they may even be faster) and are theoretically easy to implement, however.
// System functions can use a pointer to the global resource object, but this is not required.

#define GRAVITY 9.8f
void ApplyGravity(ResourceObject* res, DynamicVelocity* vel) {
    vel->y -= (GRAVITY * res->deltaTime);
}

void OutputPositions(ResourceObject* res, Position* pos) {
    res->out << "(x=" << pos->x << ",y=" << pos->y << ",z=" << pos->z << "), ";
}

// System functions can also use a tuple to concat components together.
// Naive benchmarking found this to be slightly faster.
void ApplyVelocity(std::tuple<ResourceObject*, Position*, DynamicVelocity*>& ab) {
    Position& pos = *std::get<Position*>(ab);
    const DynamicVelocity& vel = *std::get<DynamicVelocity*>(ab);
    const ResourceObject* res = std::get<ResourceObject*>(ab);
    pos.x += vel.x * res->deltaTime;
    pos.y += vel.y * res->deltaTime;
    pos.z += vel.z * res->deltaTime;
}



// System can also exclude the resource object if it is unneeded. Like this UNUSED wrapping system for the position.
float WrapIn200x200x200Box(float val) {
    float BOX_MIN = -100, BOX_MAX = 100;
    float boxRange = BOX_MAX - BOX_MIN;
    float nudge = std::ceil(-std::min(0.0f, val / boxRange));
    return std::fmod((val + (nudge * boxRange) - BOX_MIN), boxRange) + BOX_MIN;
}
void WrapPosition(Position* pos) {
    pos->x = WrapIn200x200x200Box(pos->x);
    pos->y = WrapIn200x200x200Box(pos->y);
    pos->z = WrapIn200x200x200Box(pos->z);
}


void ExampleECS() {
    const char* outFilename = "ExampleOutFile.txt";
    // Managers first take a template parameter of the 'resource' type; this can be thought of as the global state.
    // Next, the list of all possible component types are passed in as a template pack, wrapped ECS::ComponentList.
    // Do NOT miss a single component type!
    // This will cause classic template errors, since the component type will not correctly get a unique type index.
    ECS::Manager<ResourceObject, ECS::ComponentList<Position, DynamicVelocity>> manager;
    auto* res = manager.GetSharedResources();
    res->out.open(outFilename); // This is our global state, a file to write to.
    res->lastFrame = std::clock();
    res->deltaTime = 0;

    // Groups are created using template packs wrapped in GContains and GNotContains structs,
    // which define their component expression/signature.
    // If a system has a 'tight' group, then it will be processed sequentially in memory, no indirection nor branches.
    manager.MakeGroup<GContains<Position, DynamicVelocity>, GNotContains<>>();

    // Entities should be added AFTER all groups.
    // We create groups by providing a parameter list of pointers for each component the entity will have initially.
    size_t numEntities = 100;
    for (int i = 0; i < numEntities; i++) {
        Position pos{ UNIFORM_RAND() * 10, UNIFORM_RAND() * 10, UNIFORM_RAND() * 10 };
        DynamicVelocity vel{ UNIFORM_RAND() * 15, UNIFORM_RAND() * 30, UNIFORM_RAND() * 15 };
        // AddEntity copies components, there is no scope worry.
        EntityMeta ent = manager.AddEntity(&pos, &vel);
    }

    // Systems should be added AFTER all groups. Order does not matter between entities and systems, however.
    // Similar to groups, the system'm_str component signature is specified by template packs (SContains and SNotContains).
    // Most importantly, Adding a system also requires providing a void function with one of the following signatures:
    // void(Cs*... cmps), void(Res*, Cs*... cmps), void(std::tuple<Cs*...> cmps), void(std::tuple<Res*, Cs*...> cmps).
    // This Cs list of component types is concatenated to the specified SContains type pack for the component signature.
    // The function will be called for each entity which satisfies its signature.
    Base_SystemFunc* sys = manager.AddSystem<SContains<Position, DynamicVelocity>, 
                                             SNotContains<>, 
                                             ApplyVelocity>();
    // AddSystem returns a virtual pointer to the created system,
    // which can be queried for properties such as whether it has all Equivalent groups.
    sys->AssertFullEquiv();

    // The arguments of the provided function to an AddSystem call will be implicitly included in SContains.
    // But it'm_str usually good to include them anyway for clarity.
    manager.AddSystem<SContains<>, SNotContains<>, ApplyGravity>()->AssertFullEquiv();
    manager.AddSystem<SContains<>, SNotContains<>, OutputPositions>()->AssertFullEquiv();

    // Cleanup functions are called at the end of each tick, before committing entity changes (after ALL run systems).
    //  It is likely best to specify them as lambda function, while systems are given as template function pointers.
    //  This is a simple cleanup function which simply prints the number of iterations left and updates a delta time.
    auto cleanup_func = [](ResourceObject* res, decltype(manager)* manager) {
        // We can write results:
        std::cout << "\nTick #" << manager->GetCurrentRunCount() << ":" << std::endl;
        res->out << std::endl; // end line for the outfile
        // Or manage global state:
        std::clock_t endOfFrame = std::clock();
        res->deltaTime = (float)(endOfFrame - res->lastFrame) / (float)CLOCKS_PER_SEC;
        res->lastFrame = endOfFrame;
        // Or prematurely end the ECS:
        // manager->_running = false;
    };
    // Unlike systems, 'cleanup' system signatures are fixed and doesn't need to be given a template parameter.
    // void(ResourceObject* res, decltype(manager)* manager)
    manager.AddCleanupSystem(cleanup_func);

    // The tick limit is the number of ticks which the ECS will run before stopping.
    // If the tick limit is set to 0 then the ECS will run indefinitely, OR until a cleanup system shuts down the ECS
    manager.SetTickLimit(100);

    // This function finalizes all setup and begins running the systems. It is blocking for this single thread design.
    // No changes to the system or groups can be made after calling this function (but components and entities can be).
    manager.RunSystems();

    // That'm_str all folks...
    manager.GetSharedResources()->out.close();

    std::cout << "Wrote output file to " << std::filesystem::current_path() / outFilename << std::endl;
}

clique_ecs's People

Contributors

apeculiarcamber avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.