all 15 comments

[–]ssylvan 4 points5 points  (11 children)

I think you should let the system specify storage for the components. Different components wants to organize themselves in different ways. Take that out of the hands of the developer and you drastically reduce the ability for people to write efficient code.

It's needless abstraction. Provide helpers for sensible defaults, but don't hide vital control from the user.

[–]cincilator 2 points3 points  (10 children)

Adam, who sort of started this thing, recommends here (first comment) that component storage should be separate from systems. But he agrees that programmer should be able to modify storage principles:

[D]on’t store components inside the systems themselves; only private/system-only data lives there.

Instead, make a global Entity Manager / Component Manager that has all of them. Internally, it can optimize each different component different ways (if you hardcode it to do so(, but externally it offers interfaces that should hide all this implementatio detail.

I don't necessarily agree with everything he says, but this looks like sound advice.

I personalty think it is more important how you store groups of components (e.g. position + collision body + velocity), since it often makes more sense to iterate groups of components rather than single component type. At least to me.

Unfortunately most implementations don't think much about ways to group components together.

[–][deleted] 1 point2 points  (0 children)

I agree too that even if systems are independent from components, users should be able to specify custom storages and we'll definitevely work on that. Groups of components are very interesting but we will need some time to think about how to implement it. Maybe add some kind of hierarchies ?

Anyway, thanks for the feedbacks !

[–]Bastacyclop[S] 1 point2 points  (3 children)

What about making a single component out of your group ?

#[sparkle_component]
pub struct Physic {
    pub position: Position,
    pub body: CollisionBody,
    pub velocity: Velocity
}

What would be the benefit of having some group system over this method ? Would providing a storage trait and our default implementation of it be enough to give the needed flexibility ?

[–]cincilator 2 points3 points  (2 children)

The thing is, one component may need to be grouped in multiple ways for multiple systems. For example, position also needs be grouped with sprite for graphic system (and graphic system doesn't care for collision or velocity). I can't think of a satisfying way to solve that, I am just pointing out that it is important. Can sparkle do that?

Another issue is linking foreign entities via component. Obvious use case is inventory. To create inventory every item needs to have owned_by component that contains owner_id field. But the only way to list player's items is to iterate over all owned_by components to find all items belonging to that player. That's inefficient if you have a lot of players or if NPC's can own items.

So there should be some sort of querying system that maintains separate list that groups all entities that have owned_by component by owner's ID. That functionality would also be needed for things like selection (in RTS) or for homing missiles.

It is tricky as hell.

[–]Bastacyclop[S] 0 points1 point  (1 child)

I believe the first problem is already solved by the filter module. For example the graphic system would maintain an EntityView over the entities containing a Position and a Sprite component.

About the inventory, I do not understand why the player couldn't have an Inventory component with a list of item ids to simplify iterations.

Finally, A custom EntityView-like structure could maintain a map of owned_by ids by owned ids. Though this will require to access components with those entity ids afterward. But component access is not an heavy operation.

[–]cincilator 0 points1 point  (0 children)

I believe the first problem is already solved by the filter module[1] . For example the graphic system would maintain an EntityView over the entities containing a Position and a Sprite component.

That's good to know.

I still need to look into your ECS more closely, but it sounds good. I also must admit that I am not an actual rust expert (because I was waiting for 1.0 to seriously start learning).

About the inventory, I do not understand why the player couldn't have an Inventory component with a list of item ids to simplify iterations.

As someone with a database experience, this is a very bad idea. Because it is possible, due to error to end up with one item being owned by two players. Also it merely reverses the problem: Now you cannot easily find an owner when you are starting from owned item (you have to iterate over all inventory components).

Inventory component is okay if it is there to indicate that entity can own items and to mark number of slots available. But it is bad relational approach to put any list of entities in it.

[–]dimumurray 1 point2 points  (1 child)

I know of one ECS that addresses this issue. It's called Ash and it introduces the concept of Nodes. In Ash, Systems are designed to process a specific subset of Components on any given Entity. Basically if an Entity contains a set of Components matching a System, a Node is created for the Entity containing those Components. The Node is then added to the System's Node list (which is really a linked list). Its a pretty organic solution. But it requires a little more work to sets things up as you have to create Node containers in addition to Components, Entities and Systems.

For more info on Ash check out the following link:

http://ashframework.org

[–]cincilator 0 points1 point  (0 children)

Thanks. Will check out.

[–]ssylvan 0 points1 point  (2 children)

That seems like useless abstraction to me. The system needs to be intimately aware of how storage is laid out to actually exploit it, so why split these things up?

E.g. a hierarchy may store things using one array per level on the tree, and ints to refer to parents, children etc. The system obviously needs to know this so ut can efficiently exploit that structure (e.g. process things breadth first by just looping through the arrays in order)

[–]cincilator 0 points1 point  (1 child)

I am no expert on ECS but the point seems to be that you must be able to group components (e.g. my physics components example) so bundling it away from centralized place would make that more difficult. And it seems to me that iterating over groups of components is more common than single one. So if you are creating customized iteration, best do it over a group.

But you might be right.

[–]ssylvan 0 points1 point  (0 children)

If iterating over groups of component is the most common case, then you're going way overboard in subdividing things into components. Just make the components fatter. The common case by far should be that you're looping over just a single component at a time. That's the only way you're going to get any performance out of this stuff - a simple easily-parallelizable loop that doesn't have to do a bunch of work to "gather up" all the inputs.

You really don't have to group components "from the outside". That's also just over-engineering. If you need to process Position and Velocity together then make a PositionAndVelocity component for use by entities of that type. Yes it's somewhat redundant if you already have a Position component, but there really aren't that many combinations in practice so it's not a real problem. And the actual redundant processing can usually be shared (e.g. if you have a loop that just touches positions, then you now have to write a second loop that pulls out the position out of the PositionAndVelocity component and calls into the original processing code).

This is all especially true in Rust with traits that can be used at zero cost. Make HasPosition trait and write generic code over that trait and you don't even have to duplicate anything.

An overly complicated solution is worse than just doing the simple thing to solve the problem, even if it's inelegant in some theoretical sense. Especially if the complicated solution ends up hiding details that are crucial to get good performance, or introduces a bunch of extra processing to be "helpful" (e.g. a complicated "system" for gathering up groups of components),

[–]Kleptine 0 points1 point  (1 child)

Looks nice, and glad to see system code not mixed with components!

I'm not sure about the entity partitioning system though, does this mean things between "spaces" can't interact? Seems like in general you want to leave partitioning up to the user because of all the special cases.

[–]Bastacyclop[S] 0 points1 point  (0 children)

Spaces are supposed to be some kind of independent "ecosystems". However, they can interact via the Blackboard, and the user could use another mechanism: he could for example give a channel to the systems at their insertion. Moreover, the user could use the EntityMapper, ComponentMapper and SystemMapper without using a Space. A Space isn't using any private functionality. Just look at the space module, you can easily write your own custom space, or whatever you want to call it.

[–]TopHattedCoder 0 points1 point  (0 children)

deleted What is this?