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?
#81
01/03/2015 (4:21 pm)
@Andrew

Yep, idea being to have a shared buffer.
And you're correct, animated meshes would compound that. Until HW skinning is in, it'd be for static shapes only, but after HW skinning support happens it really shouldn't matter much.
#82
01/03/2015 (4:51 pm)
I really need to spend more time researching what you were doing. I need to understand your approach better. The 2 main concerns I had with performance. 1) Andrew brought up with having to walk through each actor then walking each actors components. 2) Having to call getComponent() to retrieve dependencies. Like for instance a KinematicComponent might reference BasicMovement & Gravity components. This is something that can happen in a time critical area. If your interfaces somehow make the number of reasons for calling getComponent() small. That is awesome and solves the problem for me. And as far as multiple components of the same class. I think I would be happy with some very basic functionality from components. In my opinion Unity suffers from an over dependence on components.
It is brilliant what you are doing Jeff. If we don't have hardware skinning then that is gonna show up on the profiler long before your components that is for sure.
#83
01/03/2015 (7:08 pm)
Antony, I intended my brief example with the smart pointers to demonstrate the solution to issue 2. The problem is it falls down if you move the component, e.g. by reallocating or sorting the vector it lives in. I think. Not sure if T3D's built in smart pointers handle moves. So we may need to come up with a better solution for those cases :/.

I'd love to hear you expand on 'over-dependence on components'.
#84
01/03/2015 (7:54 pm)
Of course this is my opinion. In Unity you start with a GameObject, this is similar Torque's GameBase. Unity is only extensible through script or at least for those of us who don't know how to get hold of Unity source. You can only expand on a GameObject through components. There is no derivation so there are a lot of components to add features to a given actor. Most of the components are script-based. This provides massive flexibility and is very easy to get something up and running. Need something... just attach on a component. Unfortunately the component is a black box to the user. Because people don't pay close enough attention to what each component is doing in relation to one another there is a lot of redundancy. Huge performance inefficiencies.

Unreal mixed both derivation and components. This was the best mix for me. I imagine it was much easier for Unreal to implement since they just gave out their source. Really a smart decision.

It's really important that you design a component not as an individual component, but how they relate to the whole.

The reason I bring up performance is because I have seen how components became bottlenecks before. And it is hard to back track once all your code is written with components in mind.

I'm not saying it's a bad idea. Just that we should be careful.

If I could speak to smart pointers and memory. Having an editor is awesome, but we shouldn't let flexibility overshadow the engine's performance.
Sometimes you load memory and you make sure it doesn't move. A build process that organizes data in an optimal way, then loads when the game initializes can help you load large contiguous blocks of memory. Small allocations can cause fragmentation and cache misses.

Just my 2 cents.
#85
01/03/2015 (8:08 pm)
I'm sure this stuff is not new to you guys. I hope i'm not being annoying. Still really excited about Jeff's work. :)
#86
01/03/2015 (8:48 pm)
@Antony
Nope, I don't mind. Always good to have more opinions from people that have actually used E/C systems.

Lots of the R&D talk on the net are about hypothetical or perfect systems(ie, not actual production engines) so they tend to become useless fast.

I see what you mean about it getting black boxed though.
I'm not as concerned in this case simply because everyone has full access to the source. You can implement component behavior in script or at the engine level. I personally like a mix of the two.

Also, because of the way it's set up, absolutely nothing would stop you form taking the Entity class, inheriting from it into a PlayerEntity class or whatnot, and hard-code some assumed behaviors, while letting others be handled via components.

I haven't pioneered that case yet, obviously, but outside of checking so you don't inadvertently overlap functionality, nothing would really stop you from going that route.

I'm targeting the 'everything is components' case as the high-end, and I full anticipate people tweaking the actual end results to work with their needs on a project level.

Speaking on UE4, I'd seen the word components pop up on occasion, but I hadn't looked at how they were handled. Do you have any specifics?

@Andrew, since you've dealt with it as well, do you have a bit more info on their handling of components?
#87
01/03/2015 (10:13 pm)
I am providing this information to the best of my knowledge.

Unreal is really interesting. They actually have 2 different style of components. Their base object is called a BluePrint the BluePrint derives from a C++ class. The BluePrints are a mixture of components and visual language. A component in this case is geometry, a camera, a light. It is like a subscene. tied to these components is the visual language that is the next generation of Kismet. There is a second type of component in Unreal which is more typical. You will find arrays of actor components like we are used to seeing. But instead of it being structured for everybody to use. The implementation is based on the need of the class and is implemented very simply. So each class usually implements it differently. No standardized way. And so there is no grand design. The nice part of all this is that you can mix and match however you want. But it's up to you to worry about implementation and performance.
#88
01/04/2015 (5:11 am)
Antony, not totally clear on your reply RE smart pointers. My point was that you might, for example, store a vector of components of a specific type for efficient iteration (the components themselves, not pointers). If this array ever grows and is reallocated, previous pointers may be invalidated.
#89
01/04/2015 (8:37 am)
@ Dan

As far as I know there is no smart pointer in torque or STL that survives a vector resize (more generically: I don't know how a pointer to an address in memory can know you moved the object to another spot in memory without a system of notifications, or maybe leaving behind a "I moved" object which is not cool at all haha). I did consider writing a smart vector pointer for torque that would register with the vector class to receive notifications when an item is added/removed or it's resized so it has the ability to keep it's pointer up to date but I decided against pushing Torque's vector class further away from STL.

Speaking of which, have you looked at Torque's vector allocation algorithm? https://github.com/GarageGames/Torque3D/blob/69838bdc8c9bc055b9b1ae76f42b0f28d2a33909/Engine/source/core/util/tVector.h#L329-L336

It just resizes to +1. So, if you know you're going to be pushing 10 items you should make sure to reserve the space first or else you'll go through 10 resizes. Hardly ideal though maybe at the time of writing it it was a smarter choice than the standard "double the size each time" which could have potentially eaten up lots of memory on older hardware.

Anyway, this is why I opt to use preallocated arrays of structs for performance critical lists like render items. No resizing, contiguous in memory, and you can maintain pointers to it. When you're done with an entry just mark it unused/deleted and then when a new item is added you overwrite an unused slot. It has a couple of cons, the biggest one being that you're using a fixed size array. Depending on the context this shouldn't be much of a problem. I'm not too worried about having more than 65k render instances as the graphics card would be melting anyway. Second con being that flagging items as deleted will cause predictive branching fails in loops:

for (U32 n = 0; n < list.size; ++n) { if ( list[n].deleted ) continue; }

If every second item has been deleted, this code would execute much faster if it was sorted before hand to help the CPU with predictive branching. As of yet I've not bothered to optimize for this in my implementation. Render data tends to be allocated in chunks (one mesh might have 10 render data entries) so the worst case seems unlikely. If I did optimize for it I'd just keep track of invalid vs valid entry counts and when invalid > valid just rebuild everything.

Anyway, a lot of this doesn't matter unless Jeff is brave enough to actually rewrite torque's rendering system. Otherwise, all these calls are just leading to torque's standard render instance bin and batching system. This system is already relatively optimized for all the items I've mentioned above. This is part of the reason why it collects render data into renderinstances before iterating through them to submit draw calls. The final iteration is memory, cache, and CPU friendly. If the components are going to remain rather high-level and still rely on torque's underlying render system than I personally think cleanliness, ease of use/understanding and flexibility should be primary concerns. It's 2015, we have 3.0ghz 8-core processors, pre-optimization is a bigger growing concern.
#90
01/04/2015 (9:28 am)
Dan - sorry for the confusion. I wouldn't worry about the moving smart pointers. Just never use pointers to dynamic arrays. Use indices. Like Andrew was saying, know how you want to allocate stuff before you initialize the game. Know the sizes of the allocations before hand so you can allocate large blocks. Know what memory is persistent and what is dynamic and allocate accordingly. I could be wrong but I think smart pointers are a bit of a crutch in some cases because it means someone is not doing a proper notify some where else. It also causes a lot more jumping around in memory. that said sometimes they are awesome. I'm gonna let Andrew speak for me in the future he is much more eloquent than i. :)

#91
01/04/2015 (10:23 am)
Hey, not to throw another random curveball into the conversation, but is it... possible... that this kind of massive rewrite would be an appropriate time to toss around ideas relating to making Torque's entire rendering system optional and substituting in another one (such as Ogre?)

I know, giant can of worms, thread hijack, I'm just really curious about that process because it's been talked about several times and I know Josh did it way the heck back with Minions of Mirth, I just have very little understanding of how in the world you would go about disentangling something so huge from Torque and still have anything left to work with.

I do know however that on this same hardware, way back in 2010 or so I did some playing around with Ogre and was able to run hundreds and hundreds of simple shapes, even animating. Ever since then I've been quite jealous.
#92
01/04/2015 (1:46 pm)
@ Chris

OGRE is not that thin. If you really want to use it for it's full potential it's better to view it as hitching Torque to OGRE. You'd rip out the vast majority of torque's features and replace them with ties to OGRE's existing systems. You'd be faced with a lot of questions like "why rewrite the torque sky system to work with torque-ogre when you can just add in this great working sky system code from ogre forums and wrap a simobject around it."

You see the slippery slope? When you have people simultaneously talking in other threads about replacing torquescript with lua suddenly you realize OGRE already has a lua module. What are you really keeping from torque anymore? You basically just ported Torque GUI and networking to OGRE. You're pretty much just using OGRE. You've also rendered the vast majority of existing torque resources, blogs, tutorials, and forum posts completely useless. It's a lot more feasible of an idea if you were building an engine for internal use, like Minions of Mirth.
#93
01/04/2015 (3:00 pm)
I rather like Torque. :) I hope I haven't steered things in a negative direction. I provide this information to help. I'm really excited about Torque's community you guys are really amazing. Have faith Chris people are doing great stuff. :)
#94
01/04/2015 (3:38 pm)
@Antony
Nah, I think you're fine. ;)

@Chris/Andrew
Yeah, I feel it's probably a lot of inevitably wasted effort to allow hot-swapping something as integral as the rendering engine.

You could probably make a case for replacing GFX with something like libgdx or bgfx, but making it swappable would likely generate a loooot of additional complexities, as you're basically creating a abstraction layer for your render abstraction layers.

Gets complicated fast :o
#95
01/04/2015 (9:25 pm)
Alright guys.

Dan's been nagging me about it, so I went ahead, cleaned it up a little and pushed it to a secondary WIP branch, but here's the next iteration on the E/C stuff:

github.com/Areloch/Torque3D/tree/EntityComponent_Experimental_WIP

Be aware, there are bugs I have't squashed, the editors haven't been fully updated yet, and most of the components aren't technically usable yet.

So far, Camera, ControlObject and Spectator Controls are the functioning ones.

So what's different? I moved away from the template/instance system. It created a LOT of additional backend complexity, made naming conventions and namespaces messier and was basically less than ideal.

So, as it stands now, you have a configuration like this:

registerComponent("SpectatorSpawner", "Component", 
			"Spectator Spawner", "Game", false, "When a client connects, it spawns a spectator camera for them and attaches them to it");

function SpectatorSpawner::onClientConnect(%this, %client)
{
	//Currently, map requires 1 pre-existing
	//make an entity for them!
	
	if(%this.clientCount $= "" || %this.clientCount == 0)
		%this.clientCount = 1;
	
	%spectator = new Entity()
	{
		position = "0 0 5";
		rotation = "0 0 0";
		
		new CameraComponent() 
		{
			clientOwner = %this.clientCount;
        };
		new Component() 
		{
			template = ControlObjectComponent;
			clientOwner = %this.clientCount;
		};
		new Component() 
		{
			template = SpectatorControls;
		};
	};
	
	MissionCleanup.add(%spectator);
	
	switchControlObject(%client, %spectator);
	switchCamera(%client, %spectator);
	
	%this.clientCount++;
}

function SpectatorSpawner::onClientDisConnect(%this, %client)
{
	
}

function SpectatorSpawner::getClientID(%this)
{
	return ClientGroup.getObject(%this.clientOwner-1);
}


The registerComponent function at the top is how you prep the component for usage and register it to the editor. It more or less takes the place of templates.

What it does is takes the parameters you pass into it, and creates a single dummy instance so you can use that for the template field when creating new components.

As you can see when it creates the spectator entity programmatically, you just simply have Component or whatever engine-level component class name(like the CameraComponent).

If you want to inherit off a certain registered component as your template(and thus make your new component share it's namespace for script purposes), you just set that as the template field. It'll prep the component fields for you, set up the namespace and the like.

There's quite a few other changes, such as clearing out ComponentObject entirely and cleaning up all the backend management code so you just have Entity.

That means the hierarchy of classes is SceneObject -> GameBase -> Entity. (Eventually, we'll kill off Gamebase and potentially SceneObject as well).

So yeah, it's still kinda volatile, hence the new branch, but feel free to grab it and start poking at it. Feedback is most appreciated as it's still not concrete yet, but I think this is a fairly good direction to go with it.


#96
01/04/2015 (9:32 pm)
Andrew - don't forget the gameplay classes. Unless you also plug in an OGRE game engine :P. But this is a real discussion that was brought up when the ECS was back in the proposal stage - what makes Torque itself? How much do you have to replace before it's not actually Torque any more? Anyway, we're starting to digress.

EDIT: just saw Jeff's post. I love Jeff. Also, I can be really, really annoying.
#97
01/05/2015 (10:00 am)
@Andrew: right on, that's kinda what I thought, just wanted to hear more informed opinions on the matter. Seems like an incredible amount of work... but anyway I'll drop the digression, thanks for the info.

@Jeff: right on, man, *thank you* for all your hard work!


#98
01/14/2015 (9:27 pm)
'Nother update.

With Dan's help, we got a more abstracted version of the interfaces set up, and a few of them are making use of it now.

In short, when the component is created, certain interfaces will store a pre-tyed version of the component in a static list.

So if you have a component that utilizes the PrepRenderInstance interface, for example, that stores a reference of the component as the PrepRenderInstance interface in a static list.

When we do our RenderObjects() function call, we then directly iterate through that list and call directly to the interface(and thus the components). So we skip having to go through the parent entity first, and we also skip needing to do additional dynamic_casting to make sure the component is typed correctly.

Currently, the UpdateInterface and PrepRenderInstance interface are utilized in this manner. The UpdateInterface for the main processTick() calls, and the PrepRenderInstance for the render call, naturally.

This should hopefully make things a little more efficient, and start our foothold for moving the E/C stuff more low-level down the road.

Also started proper implementation of Entity networking. It networked values down, but it didn't do any interpolation, so stuff got real choppy real fast.

It's not 100% there, but it's most of the way there currently. Should just need to do cleanup and lots of testing to ensure it interpolates like it should.

As last time, this is in the WIP branch if you wanted to nab it.
#99
01/14/2015 (10:19 pm)
This is very exciting Jeff! Thank you.
#100
01/15/2015 (1:53 pm)
So cool! Massive thanks to both you guys. This is the most exciting thing to happen to Torque since the Linux branch! :-)