ECS - Renderable, Animation, Physics

Started by
5 comments, last by Hodgman 10 years, 5 months ago

I am running into some road blocks attempting to effectively implement ECS methodology. I have a heavy background in OOP, and I think that is really tainting my perspective.

According to a lot of the information I have gathered regarding ECS, one of the primary benefits is that each system is supposed to work independently, and also, using components should help avoid memory cache issues since components should be stored in contiguous Memory.

The problem that I am experiencing is the following.

Given a particular element for which Rendering, Animation and Physics need all be applied there is a particular sub set of information which must be available and synchronized to all systems, while an overlapping subset of information may not require availability and synchronization.

For the rendering system to work it requires a component containing a Sprite, a position and a rotation.

For the physics system to work it requires a component containing a position, a force and a rotation.

The animation system is slightly more complex, as there are three types of animation

1) Tile: No animation, requires only a sprite

2) Simple Animation: Loops through a single animation, requires a set of sprites

3) State Animation: Infers sprite from a state and a TimeInState, requires a State and a set of sets of sprites.

So it seems that there is the following portions of data required for the ECS as a whole (not partitioned into components):

Sprite - Current sprite representative of entities animation

Rendering system Read

Animation system Write

Sprites - Complete set of sprites available to entity

Animation system Read

State - Current state of entity

<Some System> Write

Animation System Read

TimeInState - How long entity has been in a particular state, to enable mapping to a specific frame of a specific animation

<SomeSystem> Write

AnimationSystem Read

Position - Both physics position and drawing position (while in an idealized situation they would be equal in actuality they are only tightly coupled through some mapping function i.e drawing Y often equals -physics Y, and physics often work of Center of gravity while rendering works on either top left corner, or center of sprite)

Physics system Read/Write

Rendering System Read

Rotation - Both physics position and drawing position (similar issues to position, but possible to avoid)

Physics system Read/Write

Rendering System Read

Force - Physics

<some system> Write

Physics system Read

-----------------------------------------------

As you can see, it becomes a rats nest of inter connectivity destroying the System Independence principle. Also, since each system uses multiple components it also destroys the Contiguous memory access principal as well. Some sources concede that complete system independence is not always possible but the breakage is acceptable as long as the more performance enhancing principal of contiguous memory access is preserved, this is seems would require Data duplication, something that I find great difficulty in accepting as good practice (this may be due to OOP and Database bias).

The animation Components present a new challenge entirely... namely, how to efficiently store jagged arrays in contiguous memory.

In my particular case it turns out to be a jagged array of jagged arrays. I plan to use a image packing software to create image atlases, the image packing software produces a string->box mapping file so a particular sprite can be located by providing the sprites String formatted Name/Id. Also, since various states may have more or less frames than other states, and since some entities may have more or less states than others you can see that the first jagged array is entity to states, followed by states to frames and finally frames to strings.

I suppose the gist of this post is.... ARRRRGGGG, WTF?!?! HELP PLZ!!

Advertisement

You could try packaging the data in some other way to minimize this cross-accessing. It seems you want your AnimationSystem to do a bit much, you could split it up such that one part is doing the logic and then have another System for choosing a texture from some cache based on that data and save a pointer to the current texture and data about how its used inside a texture component.

You could then package the position and rotation into one component and then have the rest of the physics data inside another. Then the rendering system could read the position/rotation component and texture component.

I think parallel reading from one array of components that some other system already wrote to and reading/writing out to the own component array shouldn't be that bad, but its something to be minimized.

Some people says to use the Observer Pattern to do the interaction between those systems. Never tried out but looks like a alternative.

Well, the purpose of an ECS is to get away from OOP and its intertwined calltrees to have a nice clean structure, where you get a fixed list of systems that do straight iterations through full arrays of components in a fixed order. For example, you first call the physics system which updates all positions and then call the render system which reads all positions.

I dont see how the observer pattern, which is basically spaghetti-code repackaged into OOP, can help you on that, when it generates back-and-forth jumping between single components of different types. For example that would mean every single time the physics system updates one of the position components there would be a render component observing it and the code would just jump there, then back, then on the next component again and so on with the contraption.

I would suggest you read the article series at http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/.

In terms of different systems accessing the same components (your "rats nest of inter connectivity"), I don't see this as a big problem. The systems still operate independently.

For instance, the physics system updates the positions of entities, then the rendering system draws them at those positions. The rendering system still doesn't have a dependency on the physics system though. You may need to be very explicit about the order in which systems get updated though (this isn't a new problem that an ECS framework brings to the table though).


Also, since each system uses multiple components it also destroys the Contiguous memory access principal as well.

That is true.

Regarding your animation, I also have an ECS framework with sprites that are animated from a texture atlas. The way it works is:

- The "Sprite" component identifies a texture atlas and a frame from that texture atlas.

- The "Animation" component identifies a sequence of frames (e.g. go from frame 10 to frame 20 in 5 seconds) and a current frame number.

The Animation system is then responsible for advancing the frame number if it's time, and then modifying the Sprite component's frame number. (So it depends on both the Animation and Sprite components).

Anything more complicated than that (such as your "state animation") is handled by custom scripts attached to the entity, which would then create the necessary Animation component to perform whatever animation is needed. If you have a well thought-out declarative way to express your state -> animation mapping, then I would suggest having a separate "StateAnimation" system whose job it is to create/initialize the Animation component on an entity based on its state. (And then the Animation system would take care of the rest).

Well, the purpose of an ECS is to get away from OOP and its intertwined calltrees to have a nice clean structure, where you get a fixed list of systems that do straight iterations through full arrays of components in a fixed order. For example, you first call the physics system which updates all positions and then call the render system which reads all positions.

I dont see how the observer pattern, which is basically spaghetti-code repackaged into OOP, can help you on that, when it generates back-and-forth jumping between single components of different types. For example that would mean every single time the physics system updates one of the position components there would be a render component observing it and the code would just jump there, then back, then on the next component again and so on with the contraption.

I would suggest you read the article series at http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/.

http://en.wikipedia.org/wiki/Entity_component_system

I'll just put this here:
http://en.wikipedia.org/wiki/Talk:Entity_component_system#No_canonical_ECS

Well, the purpose of an ECS is to get away from OOP and its intertwined calltrees to have a nice clean structure, where you get a fixed list of systems that do straight iterations through full arrays of components in a fixed order.

According to a lot of the information I have gathered regarding ECS, one of the primary benefits is that each system is supposed to work independently, and also, using components should help avoid memory cache issues since components should be stored in contiguous Memory.

That depends on which flavour of ECS you're following. Many of the flavours do not address or achieve this goal at all.

As you can see, it becomes a rats nest of inter connectivity destroying the System Independence principle.

Break down the data-flow between systems first. Pretend that you've got a variable that can hold the entire contents of a system:
positionsRotations = ...
sprites = ...
forces = gameplaySystem.Update(positionsRotations)
newPositionsRotations = physicsSystem.Update( positionsRotations, forces )
newSprites = animationSystem.Update( sprites )
renderSystem.Update( newPositionsRotations, newSprites )
This is the overall glue between the systems. If this glue is external to them, as above, then the systems can all be completely isolated and independent. This glue layer creates the dependencies.
In a real usage, you also wouldn't be creating clones of your data (i.e. you wouldn't have positionsRotations and newPositionsRotations), but when you write it out this way, the required order of system updates becomes obvious.

As for ensuring all your data is contiguous, this is a very nice memory optimization... but it's just that -- an optimization. Don't lose sleep over it to begin with. If you've actually got performance problems, then sure, go lose some sleep and pull your hair out... but until then, relax and get things working first.

If a system needs to iterate through two sets of contiguous data, that's still pretty great. It's way better than two sets of random data, or even one set of random data.

As for jagged arrays, you can store them in two contiguous blocks. Have one block store each fixed size header, then have all the variable-sized parts stored end-to-end in another contiguous block. As long as you're never removing items, this is very easy. If you're removing items, you'll have to defragment the second block, and fix-up all your pointers into it...
struct VariableSize { int size; int* data; };
VariableSize index[256];
int allData[2048]; // index[i]->data will always point somewhere into this array

This topic is closed to new replies.

Advertisement