I have spent some time working with ECS-like approaches and can now write the conclusion.
Making complex queries for your entities is alright (and necessary) as long as they are not a bottleneck (which they won't be if you know in advance what you will be querying for).
Let's use name Aspect for a selection filter you use to query for entities. Aspects are created with constructors like Aspect.all([TransformComponent, RenderableComponent, ...]), Aspect.any([...]) and so on, whatever you actually need. Aspect.all instance can be efficiently represented by a bitfield, and we can have a lookup table:
HashTable<Aspect, Set<EntityId>>
It gives us O(1) querying for a set of entities having all the specified components. This table is updated on inserting/removing entities and their components. Aspects we query for should also be known beforehand to be efficiently tracked (I make calls like entityManager.registerAspect(Aspect.all([...])) during initialization stage, before creating any entities). EntityId sets for Aspect.any and other kinds of Aspect can also be cached or constructed on the fly depending on memory and performance requirements.
As for systems and defining their dependencies, this is a matter of preference and convenience. It is mostly unrelated to the rest of your infrastructure, but you can make some handy helpers. Systems are basically anything that fetches your entities and operates on them. If you like the declarative approach and defining dependencies in your class, you can implement a base class for some (not necessarily all) systems and use it like this:
class Renderer extends AutoQueryingSystem {
aspects: {
renderables: Aspect.all([
TransformComponent,
Aspect.any([SpriteComponent, SimpleShapeComponent])
]),
cameras: Aspect.all([TransformComponent, CameraComponent])
},
// AutoQueryingSystem automatically quieries for its aspects
// and passes the results into its update function
update: function(dt, entities) {
for (camera of entities.cameras) {
for (renderable of entities.renderables) {
// Do rendering...
}
}
}
}
But you can do the same thing manually just as well:
var renderableAspect = Aspect.all([
TransformComponent,
Aspect.any([SpriteComponent, SimpleShapeComponent])
]);
var cameraAspect = Aspect.all([TransformComponent, CameraComponent]);
...
update: function(dt) {
var cameras = entityManager.getEntities(renderableAspect);
var renderables = entityManager.getEntities(cameraAspect);
// Do rendering...
}
As Sean Middleditch notes, you may not even have separate System classes for third-party libraries, in which case the abovementioned declarative approach would be harder to implement. Simply calling entityManager.getEntities can be done anywhere, though.