Game Development Community

Entity/Component System R&D Discussion

by Jeff Raab · in Torque 3D Professional · 07/24/2014 (1:56 pm) · 105 replies

Continued from here: www.garagegames.com/community/blog/view/22794

Per Dan's suggestion, lets move the hardy discussion and specifics into a thread, where the back and forth is more practical

Quote:
Is there a better place we should discuss this? I was thinking... why are interfaces separate from components? AFAICT, the purpose of an interface is to say 'find me a component on which I can call "getVelocity"'. Could components not do that themselves? Since the whole system relies on dynamic_cast anyway, that is. I guess this would look like components inheriting particular interfaces.

The problem with calling anything directly is for it to correctly type, we'd have to know the specific component's class.

So lets say we want to have a few different colliaion components, to handle various circumstances:

BoxCollider, CapsuleCollider, SphereCollider, MeshCollider.

They have the same basic getCollisionInfo(), hasCollided(), etc calls, but the work they do in them is particular to themselves, right?

So if you wanted your Physics component to correctly call the component's function directly, there's 2 ways to do it.

Either we have an intermediate class 'CollisionComponent' all physics components inherit from, which bulks the heirarchy while still having us use virtual functions anyways, or the Physics component just knowing about every type of collision component we have and checking against them all.

So we have our physics component would have to figure out what component it is to correctly call the function in question. It also means we're adding to the number of headers we have to compile in, increasing compile times. If you wanted to add a new collision component, you'd have to add in the header and typing stuff for the new collider as well.

Then lets say we need to have more than one kind of physics component, such as SimplePhysics, RigidBodyPhysics, SoftBodyPhysics, etc, etc.
We multiply the above considerations for each.

Pretty fast you're going to end up with a ton of excess cross-references just so you can call the functions directly. It'd raise compile times and make adding new components kind of a pain.

With the interfaces as-is, it may be kinda ugly, but none of the above are problems.
You standardize the interface and it's functions, and those virtualize to the component's own interpretation of the interface calls, and does it's work.

This way, the components don't at all care what the other components are, and the same for Entity-Component interactions.

The code may be ugly, but the usage is a lot cleaner and robust.

Unless there's away around the nexus-of-pain of cross-references mentioned above?
Page «Previous 1 2 3 4 5 6 Last »
#1
07/24/2014 (2:12 pm)
I'm not sure what you mean by "ugly." Interface classes are pretty tidy and have seen very wide use. I don't see any reason not to use this idiom instead of an actual "full" base class. It means basically that the "base class" is pure virtual and the children are required to implement everything - each has the same interface and each does what it needs with it. So "ugly" is exactly opposite of what I would call it....
#2
07/24/2014 (2:41 pm)
Heh, well, my reference of it being "ugly" comes from Dan mentioning in the blog posts where you have a given generic interface, like

class VelocityInterface : public BehaviorInterface
{
public:
	virtual VectorF getVelocity()=0;
};

And then a given component doing the "implementation"

class velInterface : public VelocityInterface
{
	virtual VectorF getVelocity()
	{
		SimplePhysicsBehaviorInstance *bI = reinterpret_cast<SimplePhysicsBehaviorInstance*>(getOwner());
		if(bI && bI->isEnabled())
			return bI->getVelocity();
		return VectorF(0,0,0);
	}
};

velInterface mVelInterface;

I think it's pretty sufficient to have the interfaces virtualized like that and have the implementation happen in each given component, as that way the usage ends up being pretty straightforward:

BehaviorInterface *bI = mBehaviorOwner->getInterface(NULL, "getVelocity");
if(bI)
{
	VelocityInterface *vI = dynamic_cast<VelocityInterface*>(bI);
	if(vI)
		velocity = vI->getVelocity();
}

And the entity or component doing the call need never know the actually component it's interfacing with. It just knows the entity in question has a applicable interface and that we can do stuff with it.

That said, I think it could probably be cleaned up a little bit(and make sure it's used consistently, as I think some parts are still using old ways to go about it) but overall it's light weight and to use a given interface, you need only reference the header containing the generic, virtual interface, instead of every component you may interface with.
#3
07/24/2014 (2:46 pm)
The key is just keeping it cleanly formatted - if it's easy to read, it's easy to understand. I'd use more verbose names for variables - longer names don't cost any more than short names and they're easier to read and follow. That's about all I have to offer on what you've got - looks pretty good to me.
#4
07/24/2014 (5:37 pm)
Right, I didn't go into detail on my idea to merge behaviors and interfaces. AFAICT, the existing code when it looks for interfaces does a loop with dynamic_cast inside. We could do the same (with C++11 just so I can type less):
class VelocityInterface {
   public:
      virtual Point3F getVelocity() const = 0;
};

class SimplePhysicsBehavior: public VelocityInterface {
   public:
      Point3F getVelocity() const { ... }
};
And to get a reference to a velocity component on an object:
template<class T> T* Something::getComponent() {
   for(auto c: mComponents) {
      if(T* com = dynamic_cast<T*>(c)) {
         return com;
      }
   }
   return NULL;
}

if(auto v = getComponent<VelocityInterface>()) {
   v->getVelocity();
}
Right? So what I mean is make behavior interfaces into actual interfaces? I don't like having to do a dynamic cast loop, but we do already anyway. I think. Don't we? Anyway, this just seems to involve one less layer of indirection, and gets rid of those repetitive inner class definitions :P.
#5
07/24/2014 (7:34 pm)
Ah, so you're suggesting basically turning the interfaces into systems, and move the functionality out of the components?

I suppose that could work, though that could make the interfaces themselves kinda bulky with all the necessary supporting functions. Hmm.

Think there'd be any performance difference one way or the other?
#6
07/25/2014 (2:53 am)
No no... the interfaces basically stay exactly the same as they are, but they're used differently. They can only contain virtual functions anyway, since they don't inherit from anything so they can't access any data. I might see if I can successfully refactor into this format and show you what I mean.

I also think we shouldn't really pursue a DOD architecture right now. One major point the guy made in the video Anders posted was that if you're not running into performance bottlenecks in your entity processing, then you probably shouldn't bother with it. We should think about seeing if we can implement certain components in a DOD way though - for example, if we could do it for animation, then we could finally (maybe) parallelise skinning, which people have noted as being a performance issue.
#7
07/25/2014 (4:33 am)
For anyone coming to this thread new and wondering what we are discussing, it is an continuation of the comments on the blog post Entity/Component System Released!. (Might be an idea to link to it in the top post.)

I haven't had any time to look at any code yet. Will do that so I can contribute to the discussion more specifically about T3D and ES.
#8
07/25/2014 (5:18 am)
@Dan
Yeah, I didn't think we should go with the full DOD style either, hence why I was asking if there would be a performance improvement you could see.

If you could provide a bit more of an example(the example refactor would be awesome) that'd help.

As for parallelized skinning, I'd mentioned it in chat while I was thinking out loud, but one component I was going to look at writing here shortly is a BatchRenderShape component. You'd create various 'bins'(probably akin to the Tags system) and all entities that share a bin would pool their shape instances into a mini-manager. This would pull the geometry and material data, and store them into buffers, seperated by material.

That way we can use whatever models we want, and batch all similar material-using geometry into one(or if there's too many polies, as few as possible) draw call. This would be amazing for cities or forests, where we reuse lots of the same materials but on different models.

However, it wouldn't work on animated shapes as current, so it had occured to me that, really, the Animation components could just have a hook into the Shadergen. When you add an animation component, we add a Shadergen element that'll implement hardware skinning into the vert shader creation.

Suddenly, bam. Batched rendering however we like, and even faster animation support to boot.

@Anders
Haha, total miss on my part, I've linked to it in the OP now.
#9
07/25/2014 (12:08 pm)
Can you comment on the differences between these two methods?
void Entity::getCameraTransform(F32* pos,MatrixF* mat)
{
	BehaviorInterfaceList iLst;

	if(getInterfaces( &iLst, NULL, "getCameraTransform", NULL))
	{
		// Lets process the list that we've gotten back, and find the interface that
		// we want.
		CameraInterface *scQueriedInterface = NULL;

		for( BehaviorInterfaceListIterator i = iLst.begin(); i != iLst.end(); i++ )
		{
			scQueriedInterface = dynamic_cast<CameraInterface *>( *i );

			if( scQueriedInterface != NULL )
				if(scQueriedInterface->getCameraTransform(pos, mat))
					return;
		}
	}
}

void Entity::getMountTransform( S32 index, const MatrixF &xfm, MatrixF *outMat )
{
	TSShapeInstanceInterface* tsI = getInterface<TSShapeInstanceInterface>();

	if(tsI)
	{
		tsI->getShapeInstance()->animate();
		S32 nodeCount = tsI->getShapeInstance()->getShape()->nodes.size();

		if(index >= 0 && index < nodeCount)
		{
			MatrixF mountTransform = tsI->getShapeInstance()->mNodeTransforms[index];
			mountTransform.mul( xfm );
			const Point3F& scale = getScale();

			// The position of the mount point needs to be scaled.
			Point3F position = mountTransform.getPosition();
			position.convolve( scale );
			mountTransform.setPosition( position );

			// Also we would like the object to be scaled to the model.
			outMat->mul(mObjToWorld, mountTransform);
			return;
		}
	}

   // Then let SceneObject handle it.
   Parent::getMountTransform( index, xfm, outMat );      
}
The first one finds all interfaces that can "getCameraTransform", then iterates over all of those to find the one that is of type CameraInterface. The second piece calls getInterface directly, which internally does a similar dynamic_cast loop. Did you implement both these systems? What do you see as the tradeoffs? I'm in favour of eliminating the first method entirely and always using the second.

EDIT: For the record, I think we should try to move this sort of functionality (by which I mean, looking for certain specific components in order to implement component-specific functionality) out of Entity, but I'm not sure exactly how we will do that :/.

EDIT: Rargh I always forget about scripts. Basically I think we could use a less-granular version of the first method for scripts - i.e. %obj.getComponentWithInterface("Velocity"). Where "Velocity" is some static property of the VelocityInterface. Or something. Sigh.

The reason I'm suggesting these changes is because the current system seems over-engineered, especially where interfaces are concerned, and all the string searches that are going on. I think we should avoid that if at all possible - just makes it too easy to introduce typos, and it doesn't remove the need for RTTI casts everywhere anyway.
#10
07/25/2014 (1:24 pm)
Ugh, yeah, the first one.

That was basically the original implementation, I just had not gotten through to update everything to use the newer internal templated search yet.

The biggest problem with trying to move most of the functionality that is currently there in Entity is that we...can't, basically. Not without additional supporting work.

It'd definitely be ideal, but we'll be doing additional overhauls and guttings to make it happen, such as getting our GameConnection to directly assume objects are Entities and try and access interfaces themselves instead of going through Entity via the normal 'getCameraTransform' or whatever.

I think it's where we need to go, but we can do that as the next step- where we further pull out engine reliances on gamebase and the like.

And yeah, cleaning it up to use one method is where I was going with it(this is the danger of rewriting systems as you go, haha), just hadn't gotten there yet.

For script, we don't have to over-engineer as much, because in script, nothing stops us from just walking over and grabbing something if we know it's there.
However, if we didn't know what it was, we could definitely do something like a 'getComponentByProperty()' or something similar. I think that'd be something worth looking into.

TL;DR, yes, I agree we need to pull the first method out and use the newer getInterface<>() and a getInterfaces<>() that just straight returns a BehaviorInterfaceList

That'd be cleaner, standardized, and easier to understand how to use.
#11
07/25/2014 (3:12 pm)
I understand now - as I went through to replace the interfaces I saw why it includes all that SceneObject stuff. This is what I'm looking at currently - I destroyed the entire interface class and now components inherit directly from interfaces. Currently I've just got a crappy solution for getting a Vector of all components with a given interface, which you can see in action all over Entity.cpp. The code is less verbsose, (but ohhhhh my gosshhhh I want to use auto) but I want to refactor it to return a lazy iterator instead of a list of results. Less temp objects = good.

One side-effect is that getInterface and getBehavior are now basically the same thing. So if you want a specific behavior you can getBehavior<SimplePhysicsBehaviorInstance>, but if you just want any velocity interface you can getBehavior<VelocityInterface> (or, probably, getBehaviors<VelocityInterface>).

Eventually, I want the code to look like this:
VelocityInterface *vel;
CollisionInterface *col;
for(auto it = getBehaviorIterator()
                 .with<VelocityInterface>(&vel)
                 .with<CollisionInterface>(&col);
         *it; it++) {
   VectorF velocity = vel->getVelocity();
   col->doSomething();
}
Which, as it looks like, iterates over every component with both a collision and velocity interface and assigns the correct pointers so you can access the component in a typesafe restricted fashion. The idea is that you should never access a component except through its interfaces.

I think this sort of iteration would help us when we come to replace more of the class hierarchy with components. I dunno. Thoughts? On the other hand, implementing it will involve more temp objects to make the iterator work properly :P. Ugh.

EDIT: I wonder if type inference works well enough to deduce the template parameter, so (with even more C++11!) that loop would look like
for(auto it : getBehaviorIterator().with(&vel).with(&col)) {

EDIT: Also, I propose we rename behaviors to components. Just putting that out there.
#12
07/25/2014 (3:28 pm)
Pragmatic query using the same example:

1) How do you envision VelocityInterface interacting with forward motion, gravity, and a collision-responder? (IE: throw the ball, it bounces on the ground, you want to retrieve the vector after the bounce.)
2) How do you envision the same system "in Spaaaaace!"? (ie not using an affectedByGravityBehavior.)
#13
07/25/2014 (3:36 pm)
Good question. I haven't actually looked in detail at how the existing components interact yet. From first principles, I think you'd have a 'mass' component that deals with velocity (rather than having a velocity component on its own) and forces. Then a gravity component would add a constant force. You'd then have to add a collision component to detect collisions, and a physics component to tie them together by either adding forces, or directly setting velocity, based on collisions.

Unfortunately, stock physics is a bit more tightly-coupled than that (see Player::_updatePos, I think), and I don't know how this would interact with physics plugins.

Speaking of which, we need to discuss component dependencies :P. My first idea is basically to have components depend on interfaces. So, after components have been added to an object, they check whether they can find a component with the interface they need. If they can, then it's all good.
#14
07/25/2014 (3:48 pm)
Perhaps then a good starting point for testing interop would be the projectile subsystem, since it's got that isBallistic flag that handles all of that internally, and lays high enough up the tree to inherit functionality.
#15
07/25/2014 (4:20 pm)
Not sure how I feel about how components basically being interfaces.

That just seems wrong, somehow? I dunno. I guess in a practical fashion it makes sense, but it feels like a weird approach to it.

As for the physics and cross interaction, before doing further overhauling of interfaces, I'd recommend looking at the SimplePhysicsBehavior and BoxColliderBehavior.

Those are basically exactly what you guys are talking about, and it actually does work. Looking over the code you're gutting is usually a good idea ;D

As for dependencies, I figured it'd basically be what you said. You'd list interfaces it needs, and once all component have been added, you'd do a run-through to check if we have anything that works. If not, we automatically disable the component and it can't be enabled until the dependencies are provided. I had a script-based implementation that would even change the tooltip in the editor telling you what dependencies were failing on a given behavior.

As for the rename, I've been attempting to keep the vernacular as Engine Side = Component, Script Side = Behavior. Would that be acceptable?

#16
07/25/2014 (4:35 pm)
Really? I think the merge makes much more sense. An interface is... meaningless except as a way to describe a component. I would definitely believe that having interfaces as separate objects had some benefit I've overlooked, but at this point I'm just fooling around trying to make the code nice :P. Also this just seems more efficient. For what difference it'll make (i.e. none :P).

Haha I figured I'd have to do some component reading with my refactor, but it turns out that the abstraction is good enough that stuff appears to still work fine :P. I'll need to do some testing, but...

(Oh, in my haste I did basically do away with all the isEnabled checks the interfaces used to do. I'll re-implement those sometime...)

I think it's best to have the same nomenclature between scripts and engine. Anything else is just asking for trouble :P. I just feel component is a more standard term. Not sure where I got that bias.

Anyway, my next step should probably be to start playing with components in script and figuring out how they're supposed to be used. t3d-bones here I come!

Oh, and Az, yeah, I was planning on teaching myself how the components work by implementing 'projectile physics', which is even simpler than the stock physics behavior, I think. It just has gravity and bounces.

EDIT: Jeff, any idea why this was causing me trouble? I used the ECS build of the engine to run stock t3d-bones and it always crashed in this part of the code. But it never did when I was running your template. with this commit, it runs both t3d-bones and your template fine.

EDIT: Having a really hard time figuring out how I get a BehaviorInstance to network.
//
   new Entity(TheCamera) {
      position = "3.18671 1.12282 3.06521";
      rotation = "0.167731 0.215394 -0.962015 106.324";
      scale = "1 1 1";
      canSave = "1";
      canSaveDynamicFields = "1";
      persistentId = "ecd1ea52-e861-11e3-9c86-9deffc8b199a";
      eulerRotation = "25.2406 0 -104.183";
      new CameraBehaviorInstance() {
         template = new BehaviorTemplate() {
            networked = true;
         };
      };
   };
   TheCamera.setTransform("0 0 2 1 0 0 0");
It seems to be added fine, but it never gets sent across the network, due to isBehaviorPackable failing, because there's no ghost index.
#17
07/25/2014 (8:59 pm)
Yeah, it works well enough, I guess I'm just used to the other way enough that thinking of interfaces AS components feels weird to me.

And yeah, isEnabled will be pretty important, so that'll need to get back in there.

Yeah, a projectile would be even easier than the SimplePhysics component, I just wanted to point out the existing inter-operability example before you flew in there blind ;)

Also, don't forget there's the EC template in there, which has this stuff already built in, but is still super light. That's the template build I'd talked about that was basically T3D-bones, but with menus, client/server and the editors.

It's basically a "Game-bones" build. It has some extra chaff that needs to be cleared out though(the extra player models, and other content)

As for the edits:
For the first one, I implemented that for the modified prefabs. I needed it to process logic after everything in the prefab was created, without fully leaving the creation stack. I had it in there before the other stuff to make sure we didn't switch to a new object before actually calling the onPostAdd call.
No idea why it was making things crash though. I'd have to run it against a t3d-bones build to figure out where the hangup would be.

For edit 2: I don't know why you're creating a new template while creating an instance.
That kinda defeats the purpose of having a template.

You're basically going "Create a CameraBehaviorInstance, but don't make it using a CameraBehavior template, use a stock template.
Like, it would TECHNICALLY work, but none of the fields particular to the CameraBehavior would be initialized.

Think of the templates as hands-off datablocks, I guess. A datablock hard-sets everything, and classes are written to just defer to it because it'll never change.

For the component templates, they're more for setting up the instance, and then assuming the instance is smart enough to do what it needs to do and lets it on it's way. It'll set up the behavior fields and other default parameters(like being networked), but it won't override an instance's settings if they're already set(such as if we're loading from a file or prefab)

So it'd look more like:
new Entity(TheCamera) 
{
   position = "0 0 1";
   rotation = "1 0 0 0";
   scale = "1 1 1";
   eulerRotation = "0 0 0";

      new CameraBehaviorInstance() {
         template = "CameraBehav";    //<-- CameraBehav is a CameraBehavior Template which is defined somewhere else in script
      };
      new BehaviorInstance() {
         template = "ControlObjectBehavior";  //<-- This is actually a pure-script behavior, defined off of a BehaviorTemplate
         enabled = "0";
      };
      new BehaviorInstance() {
         template = "SpectatorControls";  //<-- Same here
         enabled = "0";
      };
};

The templates also give the namespace to the interfaces in script(as well as, currently, let you find behaviors an entity has by them)

That, and I'm pretty sure TS has never allowed you to set a field on a object you're creating WITH an object you're creating at the same time:

new SimObject(Foo)
{
   dynamic = new SimObject(Bar){};
};

Has that ever worked either?

Maybe the template classes need to be renamed to have 'Template' in them, but yeah, if you look at the EntityComponent project template, you'll see where we have a:

new CameraBehavior(CameraBehav)

and then later use that to initialize instances via:

new CameraBehaviorInstance(Whatever}
{
   template = CameraBehav;
   clientOwner = 3;
};

This makes 'Whatever' inherit any custom fields and default settings we have for it(unless you've overriden any in the above object creation, like our clientOwner field. This would normally be set by our CameraBehav template, but as our instance already has it set, the template doesn't override that)

Make sense at all?
#18
07/26/2014 (5:02 am)
I'm pretty sure it's syntactically valid, unless TS has even more of a stick up its @$$ than I thought, but I'll try instantiating the template on the server-side when datablocks are created. I do understand that we don't want to have a new template for every instance, but in this case the camera was only being created once and I just wrote the quickest thing I thought would have a chance of making it work :P. Obviously I was mistaken.

Also, I like the direction your game template is going in, but it's not barebones enough for me ;P. I prefer to learn in the t3d-bones environment, especially since I am going to tutorialise this. For example, your demo already has a bunch of stuff that happens automagically - like calling onClientConnected on components, for example. I want to strip it right down.
#19
07/26/2014 (6:09 am)
Fair enough, was just making a point that there's the template there that has all that junk working. Would be a fair point of reference.

Also, yeah, I want to go through and comment the ever-loving crap out of the EC template so you can readily follow how it's doing stuff. It's still a bit of a mess (like, why is GameConnection::onEnterGame() still in main? Aaaaarrrg ;) )

And yeah, the component templates are designed to be set up on the server and passed to the client as needed. The networked flag deigns if anything about it is thrown to the client. If it's not networked, the client never sees it.

I originated them server-side due to being able to load stuff out of the mission file, which requires the server to have all that stuff set up already if you want to use them. It's part of the reason I hadn't figured out how to do pure client-side components yet.
#20
07/26/2014 (2:10 pm)
Okay, so I got it working by placing that Entity chunk in onStart instead of onEnterGame. Hmm. Pretty sure this is not ideal? I also tried placing the template out-of-line, and while the template then successfully found itself a ghost index, the newly-created behavior instance still didn't have a ghost index, so it was not sent.

Yeah, the reason I started t3d-bones was so that all the magic boilerplate done by the script templates was getting in the way of understanding the engine. It's the same here for me - I want to understand exactly what is the minimum amount required to have a component/entity object in the game. All that stuff in the templates might make me more productive and have to fuss around less, but I'm not trying to be productive, I'm trying to learn.

EDIT: Aha!
//
   new Entity(TheCamera) {
      position = "3.18671 1.12282 3.06521";
      rotation = "0.167731 0.215394 -0.962015 106.324";
      scale = "1 1 1";
      canSave = "1";
      canSaveDynamicFields = "1";
      persistentId = "ecd1ea52-e861-11e3-9c86-9deffc8b199a";
      eulerRotation = "25.2406 0 -104.183";
   };
   %ci = new CameraBehaviorInstance() {
      template = CameraTemplate;
   };
   TheCamera.addBehavior(%ci);
   TheCamera.setTransform("0 0 2 1 0 0 0");

   %ci.scopeToClient(%client);
   TheCamera.scopeToClient(%client);
   %client.setControlObject(TheCamera);
   GameGroup.add(TheCamera);
I had to call scopeToClient on the behavior instance as well as the entity. Now I'm going to go check where you do that in your template, if you do :P.

EDIT: Do you think templates could be made optional?
Page «Previous 1 2 3 4 5 6 Last »