#SnakeECS : An entity component system

75 messages · Page 1 of 1 (latest)

true sparrow
limpid niche
#

I noticed in yours you have to specify the component types up front. It can be done without, at the cost of making destruction of entities tricky.

template <class Component>
std::vector<Component> components;

is magic perfectly normal but super neat.

true sparrow
#

Interesting idea, how would I approach identifying that component without any runtime cost? I use a type list currently so that i can index to the list at compile time

limpid niche
#

What do you mean by "identify"?

true sparrow
#

Assigning an index to the component based on what unique component type it is

#

Component A = 0, Component B = 1,… etc

limpid niche
#

I didn't look that closely to your system. I don't see why you would need that.

#

You could use the address of components 🤷

#

I'm not sure if you're familiar with the above code. The trick is that components<int> and components<double> are two independent vectors. There is no need to identify the type of a component, every component in components<Whatever> is a Whatever.

true sparrow
#

Yea i get that however there would be no efficient way to look up a component inside of a pool of components without iterating through each component and type checking

#

Assigning an id to the component allows for O(1) access

limpid niche
#

I think I know what you mean.
I used

template <class Component>
std::vector<std::size_t> ids;

where the implicit rule is that ids<Type>[i] is the id of components<Type>[i] for any i.

#

For lookup for a given ID you have to std::lower_bound (oh, they are also sorted) to find the index.

#

You can probably use std::unordered_map instead of a vector and get an official O(1).

#

Or maybe not 🤔

#

I'm not sure.

#

Having a list of components definitely makes everything easier.

true sparrow
#

That would work however that would require a runtime cost , i did something similar to that in a previous project but i found a bottleneck with this runtime checking so i shifted the identification to compile time instead via the type list

limpid niche
#

How do you find the location of a component at compile time?

#

Actually, you could do

template <class Component>
std::unordered_map<std::size_t, Component> components;

There you go, official O(1) lookup.

true sparrow
#

Check out the utils/type_list file, its not necessary finding location of the component but more so identifying the components at compile time (indexing them)

#

And yes i have a lookup table for components so looking up the LOCATION is already O(1)

limpid niche
#

_dense.push_back(std::pair<size_t, T>(id, elem));
Not moving smh

#

You can't use std::unique_ptr as a component 😛

#

Not sure why there are virtual functions involved in your sparse set.

true sparrow
#

The idea of interfacing the sparse set is so that you can pool all sparse sets of different components together

#

For example the component_pool

limpid niche
#

Hmm, yeah, when you put all your components in a single container you have to identify your type afterwards. I wanted to avoid that.

#

Though, disclaimer, I have no clue if any of my decisions were good or bad.

true sparrow
#

Avoiding that would not be so cache friendly unfortunately but i agree in the sense that it may look pointless at first, ill see what i can restructure to avoid the virtual overhead

limpid niche
#

You'd pool the components of entities together, right? Something like
C1E1, C2E1, C4E1, C2E2, C3E2, ...

#

The thing is, I'm not sure what exactly cache-friendly means here. You're not going to iterate over all components of an entity, you're just going to iterate over a set of components.

true sparrow
#

Yes sort of like a big table in memory

limpid niche
#

And since the set varies, there is no way to make it actually cache-friendly.

#

If I were to iterate over the C2 components in the above example, it would not be very cache-friendly. It's effectively random jumps, though maybe not completely random. At least it always goes forwards.

true sparrow
#

Well thats what the sparse set is for, the point of it is to reduce any gaps by having all these sets of components tightly packed reducing cache misses when iterating through them, your example assumes the components arent next to each other

limpid niche
#

Yes, that's what the order was supposed to express.

#

Maybe it looks more like
C1E1, C2E1, C2E2, C3E2, C4E1, ...
in your system?

true sparrow
#

Yes exactly

limpid niche
#

I see

#

That's what components<Type> also gives you.

true sparrow
#

More like :
C1E1
C2E2 C2E3
C3E1 C3E2
etc…

#

what is components<Type> again?

limpid niche
#

A vector

#

Or an unordered map

#

I used a vector, but in hindsight an unordered map would have been smarter.

true sparrow
#

I see, thats pretty much what im doing instead i have sparse_set<Type> lol

#

I wouldnt recommend a map since hash collisions can make iterating slow

#

And also maps dont typically use contiguous storage as their underlying structure

limpid niche
#

Something that didn't go as I thought is destructing entities. When an entity gets destroyed, so do its components, right? Well, I know that entity ID got destroyed, but I don't know what components it has. components doesn't help, that requires knowing the type of the components, which I don't. I ended up making std::vector<void(*)(std::size_t)> a component and it would carry an instance of

template <class Component>
void clean_up(std::size_t id) {
  components<Component>.erase(id);
  ids<Component>.erase(id);
}

for each component. It felt like huge overhead.

#

You have it easier, just go through the list of types and check.

true sparrow
#

Hmm i feel like you might need to restructure your code a little bit i dont think its a good approach to make that a component that should be a part of your system, but i dont have any context of your ecs so it may or may not be fine

limpid niche
#

It's part of the system, it's just a hidden component that the system gives each entity with one.

true sparrow
haughty pollen
#

flecs seems to implement this iirc

true sparrow
#

Whats the difference between hooks and just any other component? Both exist during or the lifetime of the entity no?

haughty pollen
true sparrow
#

Ohhhh i see

haughty pollen
#

typically the lifetime related hooks are built into the ecs

#

this is what toeger meant i believe

true sparrow
#

Thats an interesting idea im going to implement that i think

haughty pollen
#

its just std::function wrapper

#

:p

true sparrow
#

What types of hooks are there? Is it extendable by the userv

#

?

haughty pollen
#

in other words they are callbacks associated with entity lifecycles

haughty pollen
#

you can see more here. it's a good resource

true sparrow
#

Interesting wow ill definitely look into it when i get home thanks for the idea

haughty pollen
#

👍

limpid niche
#

As for alternatives, besides just listing component types explicitly you could do so implicitly, collecting type-erased checkers for each type. However, that would mean that each component type adds an extra check to the destructor of every entity, which is probably worse.