Help language development. Donate to The Perl Foundation

Game::Entities zef:jjatria last updated on 2021-11-14

README.md
NAME
====

Game::Entities

SYNOPSIS
========

    given Game::Entities.new {
        # Create an entity with a possibly empty set of components
        $guid = .create:
            Collidable.new,
            Drawable.new,
            Consumable.new;

        # CRUD operations on an entity's components
        .add:    $guid, Equipable.new;
        .delete: $guid, Consumable;
        $item = .get: $guid, Equipable;

        # A system operates on sets of entities in a number of possible ways
        .view( Drawable, Equipable ).each: {
            draw-equipment $^draw, $^item
        }

        # This is the same as above
        for .view: Drawable, Equipable {
            my ( $draw, $item ) = |.value;
            draw-equipment $draw, $item;
        }

        # The block can also receive the entity if you need it
        .view( Drawable, Equipable ) -> $guid, $draw, $item {
            with .get: $guid, Consumable {
                say 'This is equipment you can eat!';
            }
            ...
        }

        # Delete the entity and all its components
        .delete: $guid;

        .clear; # Delete all entities and components
    }

DESCRIPTION
===========

Game::Entities is a minimalistic entity manager designed for applications using an ECS architecture.

If you don't know what this means, Mick West's [Evolve Your Hierarchy](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy) might be a good place to start.

On Stability
------------

This distribution is currently **experimental**, and as such, its API might still change without warning. Any change, breaking or not, will be noted in the change log, so if you wish to use it, please pin your dependencies and make sure to check the change log before upgrading.

CONCEPTS
========

Throughout this documentation, there are a couple of key concepts that will be used repeatedly:

GUID
----

Entities are represented by an opaque global unique identifier: a GUID. GUIDs used by Game::Entities are stored as `Int` objects, and represent a particular version of an entity which will remain valid as long as that entity is not deleted (with `delete` or `clear`).

Each Game::Entities registry supports up to 1,048,575 (2^20 - 1) simultaneous valid entity GUIDs. From version 0.0.4, they are guaranteed to always be truthy values.

Valid entities
--------------

An entity's GUID is valid from the time it is created with `create` until the time it is deleted. An entity's GUID can be stored and used as an identifier anywhere in the program at any point during this time, but must not be used outside it.

Components
----------

As far as Game::Entities is concerned, any object of any type can be used as a component and added to an entity. This includes built-in types like `Int` and `Str` and those defined by the user with keywords like `class` or `role`.

Entities can have any number of components attached to them, but they will only ever have one component of any one type (as reported by `.WHAT`).

Views
-----

A group of components defines a view, which is composed of all entities that have all of that view's components. This means that all the entities for the view for components `A`, `B`, and `C` will have *at least* all those components, but could of course have any others as well.

The main purpose of views is to make it possible to iterate as fast as possible over any group of entities that have a set of common components.

METHODS
=======

new
---

    method new () returns Game::Entities

Creates a new entity registry. The constructor takes no arguments.

create
------

    method create (
        *@components,
    ) returns Int

Creates a new entity and returns its GUID. If called with a list of components, these will be added to the entity before returning.

add
---

    multi method add (
        Int() $guid,
              $component,
    ) returns Nil

    multi method add (
        Int() $guid,
              *@components,
    ) returns Nil

Takes an entity's GUID and a component, and adds that component to the specified entity. If the entity already had a component of that type, calling this method will silently overwrite it.

Multiple components can be specified, and they will all be added in the order they were provided.

get
---

    multi method get (
        Int() $guid,
              $component,
    )

    multi method get (
        Int() $guid,
              *@components,
    )

Takes an entity's GUID and a component, and retrieves the component of that type from the specified entity, if it exists. If the entity had no component of that type, a type object of the component's type will be returned.

Multiple components can be specified, and they will all be retrieved and returned in the order they were provided.

delete
------

    multi method delete (
        Int() $guid,
    ) returns Nil

    multi method delete (
        Int() $guid,
              $component,
    ) returns Nil

    multi method delete (
        Int() $guid,
              *@components,
    )

When called with only an entity's GUID, it deletes all components from the entity, and marks that entity's GUID as invalid.

When called with an additional component or more, components of those types will be removed from the specified entity in that order. Deleting a component from an entity is an idempotent process.

check
-----

    method check (
        Int() $guid,
              $component,
    ) returns Bool

Takes an entity's GUID and a component and returns `True` if the specified entity has a component of that type, or `False` otherwise.

valid
-----

    method valid (
        Int() $guid,
    ) returns Bool

Takes an entity's GUID and returns `True` if the specified entity is valid, or `False` otherwise. An entity is valid if it has been created and not yet deleted.

created
-------

    method created () returns Int

Returns the number of entities that have been created. Calling `clear` resets this number.

alive
-----

    method alive () returns Int

Returns the number of created entities that are still valid. That is: entities that have been created and not yet deleted.

clear
-----

    method clear () returns Nil

Resets the internal storage of the registry. Calling this method leaves no trace from the previous state.

sort
----

    method sort( $component, $parent );
    method sort( $component, { ... } );

*Since version 0.1.0*.

Under normal circumstances, the order in which a particular set of components is stored is not guaranteed, and will depend entirely on the additions and deletions of that component type.

However, it will sometimes be useful to impose an order on a component set. This will be the case for example when renderable components need to be drawn back to front, etc.

This method accommodates this use case.

Given a single component, and a `Callable` to use as a comparator, the specified component will be sorted accordingly. The comparator behaves just like the one used for the regular [sort](https://docs.raku.org/routine/sort), and can take one or two parameters. The arguments to the comparator will be the components being compared.

Alternatively, if given another component (`B`) to use as a parent instead of a comparator, the order of the first component (`A`) will follow that of `B`. After this, iterating over the entities that have `A` will return

  * all of the entities that also have `B`, according to the order in `B`

  * all of the entities that *do not* have `B`, in no particular order

Sorting a component pool invalidates any cached views that use that component.

The imposed order for this component is guaranteed to be stable as long as no components of this type are added or removed.

view
----

    multi method view (
        Whatever,
    ) returns Game::Entities::View

    multi method view (
        $component,
    ) returns Game::Entities::View

    multi method view (
        *@components,
    ) returns Game::Entities::View

Takes a set of one or more components, and returns an internal object representing a *view* for that specific set of components. Entities in the view are in no specific order, and this order is not guaranteed to remain the same.

As a special case, calling this method with a `Whatever` will generate a view which is guaranteed to always have all valid entities.

Once a view has been created, it should remain valid as long as none of the components in the view's set are added or deleted from any entity. Once this is done, the data returned by the view object is no longer guaranteed to be accurate. For this reason, it is not recommended to keep hold of view objects for longer than it takes to run an iteration.

The view object implements an `iterator` method which returns a list of `Pair` objects with the GUID of each entity in the view as the key, and a `List` of components as the value. This means that using the view in eg. a `for` loop should behave as expected, as illustrated below:

    for $registry.view: |@components {
        my $guid       = .key;
        my @components = .value;
        # Do something with the GUID and components
    }

Apart from this, the interface for this object is documented below:

### each

    method each ( &cb ) returns Nil

Takes a block of code which will be called once per entity in the view.

The block of code will be called with a flat list with the GUID for the current entity as an `Int`, and the components in the requested set in the order they were specified. If the block cannot be called with these parameters, the GUID will be skipped and only the list of components will be used. Using a block that does not accept either of these is an error.

As the block of code is called within a loop, using control flow statements like `last`, `next`, and `redo` is supported and should behave as expected.

If the view was created with a `Whatever`, the view will be considered to have an empty component list, which means the block of code will *always* be called with the GUID of the current entity.

Within the callback, it is safe to add or delete entities, as well as to add or remove components from those entities.

### entities

    method entities () returns List

Returns a list of only the GUIDs of the entities in this view.

### components

    method components () returns List

Returns a list of `List` objects, each of which will hold the list of components for a single entity in the view. The components will be in the order provided when the view was created.

Useful for iterating like

    for $registry.view( A, B, C ).components -> ( $a, $b, $c ) {
        ...
    }

PERFORMANCE
===========

Game::Entities aims to implement a simple entity that is as fast as possible. Specifically, this means tht it needs to be fast enough to be used in game development, which is the natural use case for ECS designs.

To this end, the library caches component iterators which are invalidated every time one of the components relevant to that iterator is either added or removed from any entity. This should make the common case of systems operating over sets of components that tend to be relatively stable (eg. across game frames) as fast as possible.

The distribution includes two tests in its extended suite to test the performance with iterations over large number of entities (`xt/short-loops.t`), and many iterations over small numbers of entities (`xt/long-loops.t`). Please refer to these files for accurate estimations.

SEE ALSO
========

  * [EnTT](https://skypjack.github.io/entt)

    Much of the design and API of this distribution is based on that of the entity registry in EnTT (famously used in Minecraft). A significant part of the credit for the algorithms and data structures used by Game::Entities falls on the EnTT developers and [the blog posts](https://skypjack.github.io) they've made to explain how they work.

COPYRIGHT AND LICENSE
=====================

Copyright 2021 José Joaquín Atria

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.