5
\$\begingroup\$

I am working on an Entity-System-Component engine for games, which basically means an entity is an object that points to components attached to it, and systems retrieve components to modify their data.

I have a class, which is the main data holder, containing the entities, components and system, and methods to dispatch things. The current implementation is straightforward (each entity/component has an idea and there are hashtables containing their references). But it means lots of for loop to retrieve data, and I am wondering if there aren't any more clever way to manage all that.

Here are the most important parts of the code. First, the scene and the data it holds:

var Scene = function (description) {
    this.description = description; // Just debug texts
    this.name = description.name; // Reference for the list of scenes
    this.entitiesCounter = 0; // This is used to assign ids to entities
    this.entities = {}; // Holds every entity, assigned by their id
    this.componentData = {}; // For each type of component, there is a nested json hashtable working like the entities object
    this.componentCounters = {}; // Counters for components IDs (one per component type)
    this.entityComponents = {}; // A hashtable containing the list of components id associated to an entity ID, used for retrieving an entity's data
    this.functionsUsed = {}; // Keeps a list of the functions that systems have, and which systems use them. For example we can have "update" which would have a reference to every system's update function if they are implemented
};

Scene.prototype.init = function (callback) {
    for (var i in componentTypes) {
        this.componentData[i] = {}; // Initializes the component data holders
        this.componentCounters[i] = 0; // Initializes the counters
    }
            // Fills the system functions array
    for (var s in systems) {
        for (var f in systems[s]) {
            if (typeof systems[s][f] === "function") {
                if (typeof this.functionsUsed[f] === "object") {
                    this.functionsUsed[f].push(s);
                } else {
                    this.functionsUsed[f] = [s];
                }
            }
        }
    }
    callback();
};

See the entire file on Github

So then, all this is used like this for example:

  1. The update function is called
  2. The scene searches in its functionsUsed for the list of update systems functions and calls everyone of them
  3. In the system code, I want to modify a certain component type's data for this entity (say renderer)
  4. My system will take the current entity id, and use it to search in the entityComponents table for the renderer component id if it exists, and use its ID
  5. Then if it exists, it will go in the componentData and fetch the component using the component's type renderer and its id
  6. Then the system can modify data

In some scenarios, this implies lots of loops for retrieving the data, for example here. This function takes a list of components type and retrieve any entity that has all of them:

ES.prototype.getEntitiesWithComponents = function (components) {
                    // Empty associative array for holding every correct entity
        var res = {};
                    // Loop through all the entity/components association
        for (var i in this.entityComponents) {
            var valid = true;
                            // For each one, loop through the list of accepted components, to search for a match
            for (var c = 0; c < components.length; c++) {
             // If the entity doesn't have one of the component, let it go
                                 if (!this.entityComponents[i].hasOwnProperty(components[c])) {
                    valid = false;
                    break;
                }
            }
                           // If all the components were found, add it to the answer holder
            if (valid) {
                res[i] = {};
                res[i].entity = this.entities[i];
                for (var comp = 0; comp < components.length; comp++) {
                    var compType = components[comp];
                    res[i][compType] = this.getComponentForEntity(compType, this.entities[i]._id);
                }
            }
        }
        return res;
    };

Entire file with other functions like this one on Github

As you can see, lots of for-loops are needed for most of the data manipulation. And especially since they are not standard array but objects, looping through them is slower (I think, not sure?)

So I'm wondering, is the way I implemented all that efficient, and could it be better?

\$\endgroup\$
5
\$\begingroup\$

I wrote a small method for doing dependency registering/injection a while ago which you may find useful; it's similar to what you're doing here but the implementation is very lightweight. Basically each entity in the system which I would want to use multiple times is registered in a container entity, like so:

var Container = function() {
    this._services = {};
};

Each time you want to register a service you call the register method:

Container.prototype.register = function(service, constructor, options) {
    this._services[service] = {
        constructor: constructor,
        options: options || {}
    };
};

And when you wanted to get it from the container:

Container.prototype.get = function(service) {
    var s = this._services[service].constructor;
    return (typeof s === 'function') ? s(this) : s;
};

The filtering is what I find the most useful though; you can register each dependency with an array of tags to use later on, and then get all of the entities registered with that tag:

Container.prototype.getByTag = function(tag) {
    return Object.keys(this._services).reduce(function(list, service) {
        var tags = this._services[service].options.tags;
        tags && tags.indexOf(tag) > -1 && list.push(this.get(service));
        return list;
    }.bind(this), []); 
};

An example usage:

var Application = function() {};

Application.prototype.helloWorld = function() {
    return 'Hello world!';
};

var c = new Container();

c.register('app', new Application(), { tags: ['app'] });
c.register('app2', new Application(), { tags: ['app'] });
c.register('app3', new Application());
c.register('app4', new Application(), { tags: ['other'] });

console.log(c.getByTag('app'));
// => Array of two applications

I think it's easier to register your entities and return arrays back when you want to use them, it saves you from having to write all of those for loops.

Here's a demo of the above code: http://jsbin.com/nezowapo/1/

I hope this helps.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.