Author Topic: Playing around with a new kind of program architecture: "component system"  (Read 38093 times)

mike3

  • Rogueliker
  • ***
  • Posts: 125
  • Karma: +0/-0
    • View Profile
    • Email
@requerent:

Thanks. I'm wondering, though: isn't the system supposed to contain a list of components (it was mentioned that a rendering system has a list of graphics components in it and that's how it is in my newest MiniRL program)? Yet update doesn't update by component, but by entity. Yet if all the system contains is components, how does it update by entity? Or does update(entity) go through the list of components of the entity and work only on those components that are relevant to that system?

Nymphaea

  • Rogueliker
  • ***
  • Posts: 74
  • Karma: +0/-0
  • Maria Fox
    • View Profile
    • Nymphaea.ca
It depends how your system is set up. Mine personally has the entities in the system, and it just pulls the components it needs itself. For a by entity approach when the system contains components, you could have the components keep a reference to the systems that use them, so that calling an update method on the entity would go through the components and call on the systems they have. Just remember to either keep track of which systems were already called, or have the system only add itself to one of the components it uses.

requerent

  • Rogueliker
  • ***
  • Posts: 355
  • Karma: +0/-0
    • View Profile
@requerent:

Thanks. I'm wondering, though: isn't the system supposed to contain a list of components (it was mentioned that a rendering system has a list of graphics components in it and that's how it is in my newest MiniRL program)? Yet update doesn't update by component, but by entity. Yet if all the system contains is components, how does it update by entity? Or does update(entity) go through the list of components of the entity and work only on those components that are relevant to that system?

I thought that might've been confusing.


Say we store our entities in a Hash Map or a Dictionary. The Key used for the dictionary represents the entity and it points to a list of components.


A system only need to know of components that deal with it specifically, but it also needs to know of the entity so that it can add new components if necessary. We get an access-time speed-up by keeping a local array of references in the System, and we can use the EntityID as a way to keep multiple arrays of components parallel. For example, when we evaluate Position and Velocity, we need to know which velocity and position represent an entity- we can keep an internal parallel array to do this. For a turn-based game, we need to ensure that each System's internal array of component references are ALL parallel, so using a unique EntityID is the solution. Alternatively, you could pass the entity and parse the entity data each system update, but I think that's poor data management.

mike3

  • Rogueliker
  • ***
  • Posts: 125
  • Karma: +0/-0
    • View Profile
    • Email
So does the System store copies of the components itself or just take references (IDs) to the entities and look them up in the entity database and work on what's in there, or something else?

requerent

  • Rogueliker
  • ***
  • Posts: 355
  • Karma: +0/-0
    • View Profile
So does the System store copies of the components itself or just take references (IDs) to the entities and look them up in the entity database and work on what's in there, or something else?

Do whatever makes sense to you.


I think having a local array of references that use the entity ID makes the most sense to me. However, there isn't always a 'right' way to manage your data. Do what makes sense to you.

mike3

  • Rogueliker
  • ***
  • Posts: 125
  • Karma: +0/-0
    • View Profile
    • Email
Ah, OK. Thanks.

mike3

  • Rogueliker
  • ***
  • Posts: 125
  • Karma: +0/-0
    • View Profile
    • Email
So does the System store copies of the components itself or just take references (IDs) to the entities and look them up in the entity database and work on what's in there, or something else?

Do whatever makes sense to you.


I think having a local array of references that use the entity ID makes the most sense to me. However, there isn't always a 'right' way to manage your data. Do what makes sense to you.

I've come back to this recently and am confused now: "local array of references" to what, exactly? To components? And by "local", you mean local to the system, right? If so, don't we still have to loop through the main entity data on every "update(entityID)" call to keep that local (to the system) list of components "up to date" (i.e. if components were added or removed, we have to catch that)? If so, then why bother with the local array of references to components? You're still looping through the entity data to get the relevant components anyway...
« Last Edit: May 30, 2013, 06:26:06 AM by mike3 »

requerent

  • Rogueliker
  • ***
  • Posts: 355
  • Karma: +0/-0
    • View Profile
It just depends on how you feel comfortable organizing your data. Just do what makes sense man. You should avoid iterating through components for each entity though- that's just a lot of wasted looping/checks.

Local references of parallel arrays is one way, unique keys for component-types is another, and I'm sure there are some other clever ways to do it. Looping through every component for every entity for every system, however, isn't a good idea.

An optimization like this, however, can be accomplished later. Make a game first with what makes sense, then you can speed things up in whatever way makes sense for your application.

mike3

  • Rogueliker
  • ***
  • Posts: 125
  • Karma: +0/-0
    • View Profile
    • Email
It just depends on how you feel comfortable organizing your data. Just do what makes sense man. You should avoid iterating through components for each entity though- that's just a lot of wasted looping/checks.

Local references of parallel arrays is one way, unique keys for component-types is another, and I'm sure there are some other clever ways to do it. Looping through every component for every entity for every system, however, isn't a good idea.

An optimization like this, however, can be accomplished later. Make a game first with what makes sense, then you can speed things up in whatever way makes sense for your application.

Thanks again.

mike3

  • Rogueliker
  • ***
  • Posts: 125
  • Karma: +0/-0
    • View Profile
    • Email
WOW! Just hours after that last "thanks" post ...

... MiniRL 2.0 is finished! (Architecture practice/test program)

See here:
http://www.mediafire.com/download/uv6paykwdy2qrgr/minirl-2.0.tar.gz
(edit: made a last-minute correction to the program)

Is this better now? I'm now wondering about:

1. Time management -- If one "turn" is counted as one loop through all the entities, then how do you handle, say, monsters that can act more than once per turn ("fast" monsters that get multiple attacks per turn, etc.)? Can the usual time-management techniques be combined with the ECS architecture? For example, would it be "OK" to, for example, employ some traditional roguelike time-management technique like the "speed queue" mentioned here:

http://forums.roguetemple.com/index.php?topic=3206.msg26406#msg26406

with ECS? Namely, when something has a zeroed timer, you then call all the system updates for that entity, or something along these lines.

2. Collisions -- how do we handle them? Namely, the difficulty arises with stuff like this:

Code: [Select]
...@..@...
 -->  <--

....@@....
  --><--

*COLLISION!*

This is OK. BUT...

Code: [Select]
     -->
....@@....
    -->

Both entities are to move 1 step in the indicated direction this turn, simultaneously. So they shouldn't collide. Yet suppose that the left entity comes before the right one in the update order (since the updates are done in a sequence, one has to come before the other). Then it gets its movement and collision system calls first and they register a collision with the right entity, then the right entity gets updated and moves to the right as well. Since both were supposed to be moving simultaneously, this collision is spurious (a bug). How should one do the collision detection so as to avoid this? Also, another problem: it seems the movement system must not try to move the entities in scenario 1 on top of each other, but must stop for the collision, which means the movement system "knows about" the collision in some sense. Would it be "proper" for the movement system to then raise the collision flag itself? If not, then how is the collision system supposed to know that an entity "right next to" another on the map has actually collided and not just "brushed by"?

Also, for efficiency reasons, there needs to be some way to quickly know whether or not an entity is in a given tile on the map. Yet entity positions are not stored as markers on the map, but as coordinates in position components (and if we did store markers on the map, then we'd have to somehow guarantee they are updated every time the position component is modified. Though that may not be so much of a problem -- the movement system knows of the map anyways, so if it's the only thing that changes entity positions (it should be, I'd think -- movement should be the responsibility of that particular system, no?), then it can also tell the map to update its markers). What should one do to make the check-for-entity process involved in collisions efficient?

« Last Edit: May 31, 2013, 10:08:23 AM by mike3 »

requerent

  • Rogueliker
  • ***
  • Posts: 355
  • Karma: +0/-0
    • View Profile
Oh.... Heh.... Some of my previous advice may not be entirely correct. Excuse me-- You don't loop through each system per entity for a turn-based game, you only allow one entity to take an action per looping of each system.

The system that determines which action, if any, that an entity takes (inclusive of the player and AIs), will reschedule that entity's next opportunity to take action within a priority queue (the priority being the speed-cost of the action taken). Otherwise, the game updates somewhat like a real-time game. There are plenty of solutions here, but a priority queue is elegant. You just want to make sure you only get one entity's action per turn.

There are a couple of ways to approach turn-based physics. You can have everyone pick actions synchronously and simulate, but this can make it very difficult for a player to understand what is going on. I think it's best to have a single entity perform an action per game update.

This doesn't really cause problems for realistic physics so long as you have a little foresight. You can use 'idle' states and 'moving' states to indicate that an entity moved last turn or stayed in the same position. That way, if two enemies are charging at one another, you can resolve it in a way that is consistent with their actions. Alternatively, you can store a velocity or force on an entity to be used when evaluating collisions, you just clear it out the next time the entity moves.

Your entities should be making decisions in relation to the state of the game- inclusive of the positions of other entities. In this sense, you probably want to keep a map of entities/properties cached. This should make detecting hits trivial.

Note, a Map IS also an entity that stores the relative locations of other entities. When they move, the state of the Map is also updated.

mike3

  • Rogueliker
  • ***
  • Posts: 125
  • Karma: +0/-0
    • View Profile
    • Email
Oh.... Heh.... Some of my previous advice may not be entirely correct. Excuse me-- You don't loop through each system per entity for a turn-based game, you only allow one entity to take an action per looping of each system.

You mean for every update call for every system, the entity takes an action? That doesn't seem to make sense. Also, does this mean only one entity acts per turn?

The system that determines which action, if any, that an entity takes (inclusive of the player and AIs), will reschedule that entity's next opportunity to take action within a priority queue (the priority being the speed-cost of the action taken). Otherwise, the game updates somewhat like a real-time game. There are plenty of solutions here, but a priority queue is elegant. You just want to make sure you only get one entity's action per turn.

So then the "action system" not only stores a list of "action components" (see game code), but should also store a priority queue with the speed-based timers?

And what do you mean, "you only get one entity's action per turn"? It sounds like only one entity acts per turn.


There are a couple of ways to approach turn-based physics. You can have everyone pick actions synchronously and simulate, but this can make it very difficult for a player to understand what is going on. I think it's best to have a single entity perform an action per game update.

Does one "game update" equal one "turn"? If so, then wouldn't that mean, e.g. only one monster moves every turn? (?!)

This doesn't really cause problems for realistic physics so long as you have a little foresight. You can use 'idle' states and 'moving' states to indicate that an entity moved last turn or stayed in the same position. That way, if two enemies are charging at one another, you can resolve it in a way that is consistent with their actions. Alternatively, you can store a velocity or force on an entity to be used when evaluating collisions, you just clear it out the next time the entity moves.

Your entities should be making decisions in relation to the state of the game- inclusive of the positions of other entities. In this sense, you probably want to keep a map of entities/properties cached. This should make detecting hits trivial.

Note, a Map IS also an entity that stores the relative locations of other entities. When they move, the state of the Map is also updated.

Head-on collisions (charging at each other) weren't the problem, the problem was "phantom" collision caused by the one-entity-at-a-time update when you have two entities next to each other moving in the same direction.

requerent

  • Rogueliker
  • ***
  • Posts: 355
  • Karma: +0/-0
    • View Profile
Most games, if not all, are a simulated function of time. Turns are NOT the unit of time that a turn-based game is working with. A 'turn' is simply whenever an entity gets to perform an action. Most TB games use 'ticks' as their unit of time and a turn has a tick-cost associated with it. A player will experience time relative to how frequently s/he gets to make decision, but your game will simulate in ticks.

From a player's point of view, an enemy may move two spaces per turn. But in reality, the tick-cost for moving is simply half that of the player. The enemy's cost to move may be 50, whereas the player's may be 100-- but the game updates relative to ticks, not the player's idea of what a turn is. In this sense, your game is updating everything every single tick.

Now-- we need a system that manages who gets to take a turn. However you do it, you'll simply be waiting however many ticks before an entity gets to act, based on their action, make them wait however many ticks again. Ticks give us a lot more flexibility than basing our game on turns, but it isn't wrong to do the latter.

You avoid phantom collisions by updating the entire game every time an action is made. That way we'll know that the space is occupied before making a decision.

mike3

  • Rogueliker
  • ***
  • Posts: 125
  • Karma: +0/-0
    • View Profile
    • Email
Most games, if not all, are a simulated function of time. Turns are NOT the unit of time that a turn-based game is working with. A 'turn' is simply whenever an entity gets to perform an action. Most TB games use 'ticks' as their unit of time and a turn has a tick-cost associated with it. A player will experience time relative to how frequently s/he gets to make decision, but your game will simulate in ticks.

From a player's point of view, an enemy may move two spaces per turn. But in reality, the tick-cost for moving is simply half that of the player. The enemy's cost to move may be 50, whereas the player's may be 100-- but the game updates relative to ticks, not the player's idea of what a turn is. In this sense, your game is updating everything every single tick.

Now-- we need a system that manages who gets to take a turn. However you do it, you'll simply be waiting however many ticks before an entity gets to act, based on their action, make them wait however many ticks again. Ticks give us a lot more flexibility than basing our game on turns, but it isn't wrong to do the latter.

You avoid phantom collisions by updating the entire game every time an action is made. That way we'll know that the space is occupied before making a decision.

Oh, so in this case one then does an update for each system every tick like in a real-time game, not "per entity". So then our "update" functions on our Systems would not take an entity parameter, and instead they would update every entity (or at least, every entity that has been entered into that system with the addEntity() functions), just like in real-time. And each batch of updates (one per system for every system) constitutes one "tick" of the simulation. And only one entity acts per tick (or, perhaps, only those entities which have had their time-to-next-action (on the priority queue in the action system) hit zero). Is this right?

But you say "update every time an action is made". So does this mean we need a loop with core like this:

Code: [Select]
if(actionSystem.update()) // returns true iff some entity acted
{
  // update all other systems
  ...
}

?
« Last Edit: June 02, 2013, 03:22:25 AM by mike3 »

requerent

  • Rogueliker
  • ***
  • Posts: 355
  • Karma: +0/-0
    • View Profile
You could do it like that.

Just realize that everything is an entity that could take actions- including propagating gas.