Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collect use cases and difficulties from users to improve documentation #21

Open
adngdb opened this issue Dec 30, 2015 · 4 comments
Open
Labels

Comments

@adngdb
Copy link
Owner

adngdb commented Dec 30, 2015

In order to make a great documentation, I think we need a lot of very precise and concrete examples. For that, I would need to know what people struggle to understand or achieve using ensy. That can be something high level related to the concept of Entity Systems or things related to the use of the lib itself.

So, if you read this and have anything to say, please comment! :)

@adngdb adngdb added the doc label Dec 30, 2015
@CodestarGames
Copy link

CodestarGames commented Oct 19, 2016

I have a suggestion for the workflow of Processors. It would be great to be able to register components in the processor's constructor. That way you don't have to search through all entities and components during the update event, processor update functions will only be executed on entities that meet all component requirements. You could also have events fired when entities are added and removed from processors. Here's an example of the workflow I'm thinking of.


var MyProcessor = function (manager) {
    this.manager = manager;
    this.sprites = [];
    this.registerComponent('Position');
    this.registerComponent('Display');
};

MyProcessor.prototype.onAdded(entity) {
    var position = entity.getComponent('Position');
    var display = entity.getComponent('Display');

    //handle regular game entities sprites.
    var sprite = this.game.add.sprite(position.x, position.y, display.spriteImagePath);

    sprite.x = position.x;
    sprite.y = position.y;
    this.sprites[entity.id] = sprite;

};

MyProcessor.prototype.update = function (entity, dt) {

    var position = entity.get('position');

    var sprite = this.sprites[entity.id];
    sprite.x = position.x || 0;
    sprite.y = position.y || 0;
};

MyProcessor.prototype.onRemoved(entity) {
    this.sprites[entity.id].kill();
}

manager.addProcessor(new MyProcessor(manager));

@adngdb
Copy link
Owner Author

adngdb commented Oct 19, 2016

I like some of what you're proposing, but not all of it. I believe it would be a mistake to make processors depend on Component types. Specifically, I've already had processors that would work on a component, and then check if another one was present for an entity and do something special if it was. That would not be possible with your proposal.

The events I like very much though, but I think I would implement them slightly differently. Here's what a Processor would look like in my idea:

class RenderingProcessor {
    constructor(manager) {
        this.manager = manager;
        this._sprites = {};
    }

    on(type, data) {
        switch (type) {
            case 'COMPONENT_CREATED':
                if (data.component === 'Display') {
                    let display = data.state;
                    let position = this.manager.getComponentDataForEntity('Position', data.entity);
                    let newSprite = this.game.add.sprite(position.x, position.y, display.spriteImagePath);
                    this._sprites[data.entity] = newSprite;
                }
                break;
            case 'COMPONENT_REMOVED':
                if (data.component === 'Display' && this._sprites[data.entity]) {
                    this._sprites[entity].kill();
                    delete this._sprites[entity];
                }
                break;
            default:
                break;
        }
    }

    update(dt) {
        let displays = this.manager.getComponentsData('Display');
        for (let entity in displays) {
            let display = displays[entity];
            let position = this.manager.getComponentDataForEntity('Position', entity);

            this._sprites[entity].x = position.x;
            this._sprites[entity].y = position.y;
        }
    }
}

What do you think about that @CodestarGames ?

On a side note, I see that you want to use entities as objects. That is something I really want to avoid, because conceptually, entities are just identifiers. This API is slightly more complicated to use than say Crafty's API, but it is also a lot more flexible and has a bunch of benefits (that I admit ensy doesn't exploit as of yet).

@Hectate
Copy link

Hectate commented Jun 23, 2019

Thanks for some of the quick fixes on the other issues. I had been looking around for an ECS library to use for a roguelike project I recently started and initial concerns with many that I'd found was that they were unmaintained (sometimes for multiple years). I'm glad to see that ensy is alive still; and that makes me want to contribute also. Initially I was using js13k-ecs which is small and simple, but with a limit of 32 components I realized I needed something that could expand with my game.
Starting yesterday, I commented out all my js13k-ecs code and started replacing it bit by bit with ensy. Thankfully the refactor is going well enough. I'll continue to post any issues or suggestions I have as a result. Here are a few thoughts already:

I'm lazy (functions have long names)

In js13k-ecs the calls are much shorter, which I like. I don't want to read or type a sentence when I'm thinking in terms of "do a thing". As a result, I adopted some of the names from js13k-ecs to use in my project with ensy.

    manager.assemble = manager.createEntityFromAssemblage;
    manager.get = manager.getComponentDataForEntity;
    manager.select = manager.getComponentsData;

This means I can type manager.assemble('Player') instead of manager.createEntityFromAssemblage('Player'). Or a simple manager.get('Position', id) instead of manager.getComponentDataForEntity('Position', id).
I am using VSCode, so the IDE does offer prompts, but I still prefer the shorter versions and it flows easier for me.

Iterating through entities

Because js13k-ecs used a 32-bit bitmask to track which components were attached to an entity, it was trivial for it to return a list of entities that belonged to multiple components. Thus, I could easily put ecs.select(Position, Movement) and get back a list of entities that had both of those components. Of course, that bitmask resulted in a limit of 32 components for the entire system (unless it were extended).

With ensy however, there's no built-in function to get a list of entities that meet a criteria of having multiple, specific components. Instead I'm forced into alternatives:

  1. Make an empty hybrid component (e.g. "Position_Movement"). Terrible idea because components might need to be removed and the hybrid could be left behind erroneously.
  2. Nesting loops to build a list manually. Could be acceptable performance if you build the loops carefully.
  3. Create a Group system. For example, I have many entities with a Position component but fewer with an AI component, I could cache a list of entities (using the loops from 2 above) and access that list of entities whenever I need a list of entities that have both Position and AI components attached. Of course, it would have to be updated whenever an entity gains or loses those components. In my case, updating the Group would be manually done after (A) generation is done and all AI entities exist, and (B) after an AI entity is killed and removed from the world. This would still be more performant than running the full set of "build my group" loops on every update.
    I imagine an API like:
let myGroup = manager.addGroup(['Component1', 'Component2', ...]); // registers the Group, returns a Group ID (does NOT populate the Group)
let entities1 = manager.getEntitiesInGroup(myGroup); // returns an Array of Entity IDs in Group
let entities2 = manager.getComponentDataForGroup(myGroup); // returns ComponentsData for all in Group
// Other methods...
manager.addEntityToGroup(entity, myGroup); // manually adds a single entity to the Group; throws Error if entity does not match Group's Component criteria
manager.removeEntityFromGroup(entity, myGroup); // manually removes a single entity from the group; throws Error if entity is not in group
manager.populateGroup(myGroup); // populate the list of entities that belong in the Group
manager.removeGroup(myGroup); // removes the Group from the manager

I'm sure there's more to this that could work, but it's just the thoughts I had. I expect it's also possible to get fancy and have the manager do a lot of the work automatically by simply checking if an entity should be added or removed from a Group each time the user adds or removes a Component. Just register a Group and then use it.

Anyway, sorry for the wall of text; I'll be working on my stuff and as I come across things I'll share them. I'll also look for ways to contribute to ensy. If you want help coding something like the Group idea, let me know. Thanks!

@adngdb
Copy link
Owner Author

adngdb commented Jun 23, 2019

Thank a lot @Hectate for the great feedback! I do like having shorter names as well, and I like some of your propositions. The one I'm not sure about is get because there are several "gets" in the manager. However it is true that getComponentsData is the main use case, and it might make for better understandability to have that method have a short name.

Regarding the group feature, I'll have to think about it more. I understand that it is indeed a desirable feature, but I think I would, at first, do it as simply as possible, so doing something like your 2nd solution.

I have also recently been talking about this with a colleague who's coding his own ECS, and there are some things that I believe I do wrong in here. I'd like to take some time to refine the API in the near future, and also do some performance testing to prove that the way this works is actually suitable for bigger games.

I'll file new issues for each of these things, if you want to help you are very welcome to do so. Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants