ecs服务器第一次登录_一点点做:ECS标识符

ecs服务器第一次登录

Disclaimer: I am the author of Flecs, an Entity Component System for C99. Discord: https://discord.gg/ZSSyqty

免责声明:我是Flecs (C99的实体组件系统)的作者。 不和谐: https//discord.gg/ZSSyqty

If you are writing your own ECS framework (or are about to), there is a good chance you are designing your entities as unique integer values. One of the simplest ways of implementing an ECS is to create an array for each component, and to use the entity identifier as index in each of these arrays (often combined with a bitset to test if the entity has the component). Other, more sophisticated approaches exist that also rely on plain integers, such as archetype- and sparse set based implementations.

如果您正在(或即将)编写自己的ECS框架,则很有可能会将实体设计为唯一的整数值。 实施ECS的最简单方法之一是为每个组件创建一个数组,并将实体标识符用作这些数组中每个数组的索引(通常与位集组合以测试实体是否具有该组件)。 存在其他更复杂的方法,这些方法也依赖于纯整数,例如原型和基于稀疏集的实现。

Using a simple integer to identify your entities sounds simple enough. Yet they can be used to enable many advanced features, at very low cost. In this blog I’ll go over the tricks I used in Flecs to make the most out of my identifiers.

使用一个简单的整数来标识您的实体听起来很简单。 然而,它们可以以非常低的成本用于启用许多高级功能。 在此博客中,我将介绍我在Flecs中使用的技巧,以最大限度地利用标识符。

First, let’s start with:

首先,让我们开始:

组件标识符是实体标识符 (Component identifiers are Entity identifiers)

If you saw any of my previous posts, you have probably read this a dozen times, but it is worth repeating, as much of what comes next depends on this simple rule.

如果您看过我以前的任何文章,您可能已经阅读了十二遍,但是值得重复,因为接下来的大部分内容都取决于此简单规则。

An ECS framework needs not only a way to identify entities, but also to identify components. ECS frameworks (at least generic ones) cannot know at compile time which component types an application will use, and since they cannot rely on facilities of the programming language, they need their own mechanism to uniquely identify components. The most straight forward way to do this is is to generate a unique integer identifier per component id.

ECS框架不仅需要标识实体的方法,还需要标识组件的方法。 ECS框架(至少是通用框架)无法在编译时知道应用程序将使用哪种组件类型,并且由于它们无法依赖编程语言的功能,因此它们需要自己的机制来唯一标识组件。 最直接的方法是为每个组件ID生成一个唯一的整数标识符。

Whenever we want to add a component to an entity, we provide this unique identifier to an “add” operation, either directly or with the help of some meta-programming. Everything an entity “has” can then simply be described by a vector of integers. For example, if component Position has id 1, and component Velocity has id 2, the contents of an entity that has both components could simply be described with this vector:

每当我们想要向实体添加组件时,我们都可以直接或借助一些元编程将唯一标识符提供给“添加”操作。 一个实体“拥有”的所有东西都可以简单地用一个整数向量来描述。 例如,如果组件Position的ID为1,而Velocity组件的ID为ID 2,则可以使用此向量简单地描述同时具有两个组件的实体的内容:

[1, 2]

But why stop there? Why should an entity only be allowed to contain components? Indeed, many ECS frameworks support the notion of a “tag”. which is a component that is not associated with a datatype. A tag is nothing more than a simple unique identifier. We could have a tag called “Enemy” with id 3, and add it to our entity. The vector would now look like this:

但是为什么要停在那里? 为什么只允许实体包含组件? 实际上,许多ECS框架都支持“标签”的概念。 这是与数据类型无关的组件。 标签不过是一个简单的唯一标识符。 我们可以使用一个名为“ Enemy”的ID为3的标签,并将其添加到我们的实体中。 向量现在看起来像这样:

[1, 2, 3]

We have to store somewhere what these identifiers mean, as we obviously do not just want to store what the entity has, but also the actual component values. We need to be able to attach some data to each of these unique identifiers, so that we know that 1 belongs to a value of type Position, and 3 does not point to anything but 3. Lets design a few data structures for what we have so far, without paying too much attention to performance:

我们必须将这些标识符的含义存储在某个地方,因为我们显然不仅要存储实体所具有的内容,而且还要存储实际的组件值。 我们需要能够将一些数据附加到这些唯一标识符的每一个上,以便我们知道1属于Position类型的值,而3则不指向除3之外的任何值。让我们为已有的数据设计一些数据结构到目前为止,无需过多关注性能:

using ComponentId = uint64_t;
using ComponentSize = map<ComponentId, size_t>;using EntityId = uint64_t;
using EntityType = vector<ComponentId>;
using EntityContents = map<EntityId, EntityType>;

We store the size of the component type in the ComponentSize map, which is really all an ECS framework needs to know. For a tag we can simply store size 0. This seems to satisfy our requirements, yet doesn’t spark joy. There is a fair amount of repetition in the design, for no good reason. Let’s revisit what we are actually trying to accomplish here:

我们将组件类型的大小存储在ComponentSize映射中,这实际上是ECS框架需要知道的所有内容。 对于标签,我们可以简单地存储大小0。这似乎满足了我们的要求,但并没有引起喜悦。 没有充分的理由,在设计中会有很多重复。 让我们在这里回顾一下我们实际要完成的工作:

“We need to be able to attach some data to each of these unique identifiers”

“我们需要能够将一些数据附加到这些唯一标识符中的每一个上”

That should sound eerily similar to the definition of components (“some data”) and entities (“unique identifiers”). Can we store our components as entities? It turns out we can, and it makes the design a lot simpler:

这听起来与组件(“某些数据”)和实体(“唯一标识符”)的定义非常相似。 我们可以将组件存储为实体吗? 事实证明我们可以做到,并且使设计更加简单:

using EntityId = uint64_t;
using EntityType = vector<EntityId>;
using EntityContents = map<EntityId, EntityType>;struct Component {
size_t size;
};

The size of a component is now simply stored as a component called “Component” (read that sentence three times), and we use entity identifiers to identify our components. To register a new component with our framework, we simply do:

现在,将组件的大小简单地存储为一个称为“组件”的组件(读取该语句三遍),并且我们使用实体标识符来标识我们的组件。 要在我们的框架中注册一个新组件,我们只需执行以下操作:

EntityId position = ecs::new();
position.set<Component>({.size = sizeof(Position)});

After that, we could add our position component to an entity like this:

之后,我们可以将位置组件添加到这样的实体中:

EntityId e = ecs::new();
e.add(position);

In a real framework we probably want to provide convenience functions to wrap this code, but in essence this is what would need to happen.

在真实的框架中,我们可能希望提供方便的功能来包装此代码,但是从本质上讲,这是需要发生的事情。

But wait, there is more. If components use entity identifiers, does that mean that we can also add entities to entities? There is no reason we cannot:

但是,等等,还有更多。 如果组件使用实体标识符,是否意味着我们也可以将实体添加到实体? 没有理由我们不能:

EntityId a = ecs::new();
EntityId b = ecs::new();
a.add(b);

This is a capability we got entirely for free, and one that as it turns out is extremely useful for creating different kinds of relationships between entities.

这是我们完全免费获得的一项功能,事实证明,这项功能对于在实体之间创建各种关系非常有用。

So far we haven’t done anything to the entity identifier itself yet besides expanding the scope of their applicability. In the next section we’ll look at how we can annotate identifiers to allow for expressive entity relationships.

到目前为止,除了扩展其适用范围之外,我们还没有对实体标识符做任何事情。 在下一节中,我们将研究如何注释标识符以允许表达实体关系。

类型角色 (Type Roles)

At this point you may wonder: why. Can’t we just use a dedicated structure for components, not worry about strange recursive definitions and be done with it? Is adding entities to entities really that useful? I would forgive you for thinking the answer is no, as I haven’t done a great job yet at explaining why this is useful. As it turns out, adding an entities to entities by itself is meaningless, just another id in the entity type vector.

此时您可能会想: 为什么 。 我们不能只为组件使用专用的结构,不担心奇怪的递归定义并完成它吗? 向实体添加实体真的有用吗? 我原谅您认为答案是否定的,因为我在解释为​​什么这样做有用方面还没有做得很好。 事实证明,将实体本身添加到实体是没有意义的,只是实体类型向量中的另一个id。

But, what if we could add meaning? That is where type roles come in.

但是,如果我们可以增加意义呢? 这就是类型角色出现的地方。

A type role allows us to specify which role an entity plays in a type. Type roles can be anything, but I found two particularly useful applications for them, which are hierarchies and instancing. What if I could express that an entity is a parent of another entity? That would make it possible to store hierarchies in ECS, which in many frameworks requires developers to bend over backwards to implement something that still does not perform.

类型角色使我们可以指定实体在类型中扮演哪个角色。 类型角色可以是任何东西,但是我发现对于它们来说两个特别有用的应用程序是层次结构和实例化。 如果我可以表示一个实体是另一个实体的父级怎么办? 这样便可以在ECS中存储层次结构,在许多框架中,这要求开发人员向后弯腰以实现仍然无法执行的功能。

Imagine we have an entity for which the type looks like this. I’ll be using names instead of integer identifiers, since it makes for an easier read:

假设我们有一个实体,其类型如下所示。 我将使用名称而不是整数标识符,因为这样可以更容易阅读:

[Position, Velocity, CHILDOF parent_entity]

Here, CHILDOF is a type role that tells the ECS framework that parent_entity is not just an identifier that is added to the entity, but that it should be interpreted as the parent of the entity.

在这里, CHILDOF是一种类型角色,它告诉ECS框架parent_entity不仅是添加到实体的标识符,而且应将其解释为实体的父代

“Wait”, I hear you say, “isn’t a type just a vector of integers? How can you store this extra information?”. We would not want to complicate the element type of our EntityType with fields that most of the time are not used! To solve this, we need to reserve a few of the bits of our identifier to store the type role.

我听到你说:“等等”,“类型不仅仅是整数的向量吗? 您如何存储这些额外信息?”。 我们不希望将EntityType的元素类型与大多数情况下不使用的字段复杂化! 为了解决这个问题,我们需要保留标识符的一些位来存储类型角色。

We can defineCHILDOF as 1 << 63, or in plain English, assign it a value in which we only set a single bit to 1 (in this case, the MSB). Now we can simply annotate an entity identifier with this role by applying a bitwise OR:

我们可以将CHILDOF定义为1 << 63 ,或者用简单的英语为其分配一个值,在该值中我们仅将一位设置为1(在本例中为MSB)。 现在,我们可以通过应用按位OR来简单地对此角色添加实体标识符:

e.add(CHILDOF | parent_entity);

This of course does not provide us with full support for hierarchies, as there needs to be some code or feature that interprets the CHILDOF flag. However, we did create a mechanism that allows us to store entity hierarchies in an extremely simple data structure, which is no small feat.

当然,这并不能为我们提供对层次结构的完全支持,因为需要一些代码或功能来解释CHILDOF标志。 但是,我们确实创建了一种机制,该机制允许我们实体层次结构存储在极其简单的数据结构中,这并非易事。

To get back our parent id, we could define a constant, lets call it ENTITY_MASK, which sets all bits to 1, except the ones that can be used for type roles. We could now write this (psuedo) code to find the parent of an entity:

要获取我们的父ID,我们可以定义一个常量,将其ENTITY_MASK ,它将所有位设置为1,可用于类型角色的位除外。 现在,我们可以编写以下(伪)代码来查找实体的父代:

e.type().each((e) => {
if (e & CHILDOF) {
print "parent = " + e & ENTITY_MASK;
}
});

We can have as many type roles as the number of bits (64) in an entity identifier allows us, though we of course want to reserve some space for the actual entity / component identifiers. Instead of using a single bit per type role, we could use more efficient approaches, such as reserving the most significant byte for type roles, which could give us 256 different roles.

我们可以拥有与实体标识符中允许的位数(64)一样多的类型角色,尽管我们当然希望为实际的实体/组件标识符保留一些空间。 我们可以使用更有效的方法,而不是为每个类型角色使用单个位,例如为类型角色保留最高有效字节,这可以为我们提供256个不同的角色。

A benefit of type roles is that instead of limiting the addressing space of all entities, they only limit the addressing space of entities we can add to other entities, which is a small price to pay for the features they enable. It also doesn’t seem overly constraining, given that this still leaves us with 2⁴⁸ possible component / entity identifiers.

类型角色的一个好处是,它们不仅限制了我们可以添加到其他实体实体的寻址空间,而且没有限制所有实体的寻址空间,这对于为其启用的功能付出了很小的代价。 由于这仍然给我们留下了2个可能的组件/实体标识符,因此它似乎也不过分约束。

类型特征 (Type traits)

A hard constraint in ECS frameworks is that you can add a component only once. Often this is not really a limitation, as there rarely is a reason to add a component multiple times: why would you want to have an entity with two Positions? Turns out that while this is not common, it is not unthinkable.

ECS框架中的一个严格限制是,您只能添加一次组件。 通常这并不是真正的限制,因为很少有理由多次添加组件:为什么要有两个位置的实体? 事实证明,虽然这并不常见,但并非不可想象。

Systems in ECS work because components have meaning. Position is not just a vec3, it means something: it is the position of an entity in the world. Perhaps Velocity is also a vec3, but it is a different vec3, with a different meaning. This is why ordinarily, entities don’t have multiple instance of the same component, even though component types can be very similar.

ECS中的系统起作用是因为组件具有意义。 位置不仅是vec3,还意味着某些东西:它是实体在世界上的位置。 也许Velocity也是一个vec3,但是它是一个不同的vec3,具有不同的含义。 这就是为什么实体通常没有相同组件的多个实例的原因,即使组件类型可能非常相似。

Adding a component multiple times seems like it would violate this core principle. If I were to add Position to an entity two times, what should a system do with this? Are the two Positions equivalent? It gets confusing quickly, and that is why adding components multiple times in the general sense is probably a bad idea (though there is a case to be made for components with collections, but that’s a separate topic).

多次添加组件似乎违反了这一核心原则。 如果要两次将“位置”添加到实体,系统应该如何处理? 两个职位是否相等? 它很快就会造成混乱,这就是为什么在一般意义上多次添加组件可能不是一个好主意(尽管对于带有collections的组件来说是必要的,但这是一个单独的主题)。

If this makes no sense, then why are we talking about it? Well, in some cases adding a component multiple times does make sense, as long as it is clear what the meaning of those components is. This is as abstract as it gets, so let’s use a concrete example.

如果这没有意义,那我们为什么要谈论它呢? 好吧,在某些情况下,多次添加一个组件确实是有道理的,只要清楚这些组件的含义是什么即可。 这是非常抽象的,所以让我们使用一个具体的例子。

Imagine a “HealthBuff” component that must be automatically removed from an entity after a certain amount of time has passed. An elegant solution to this would be to create a “HealthBuffExpiry” component, in which I store the expiry time, and the time passed since I added the buff. I then increase time passed each frame and when it exceeds expiry_time, I remove HealthBuff.

想象一下“ HealthBuff”组件,在经过一定时间后必须将其自动从实体中删除。 对此的一个很好的解决方案是创建一个“ HealthBuffExpiry”组件,在其中存储到期时间以及自添加buff以来经过的时间。 然后,我增加每个帧经过的时间,当它超过expiry_time时,我删除HealthBuff。

Now, I have to preface what comes next by saying that there are many ways to tackle this problem, and I am just using it as a way to illustrate traits.

现在,我必须先说下接下来的内容,说有很多方法可以解决此问题,而我只是在用它来说明特质。

The obvious next question for this approach is, “what if I want to add a “StaminaBuff” component? What if I want to add a “ManaBuff” component? Etcetera. The easiest solution would be to create as many “Expiry” components and “Expiry” systems, but I can guarantee you that you will feel dirty when coding this out. All Expiry components and systems will be exactly the same but for their name. Surely there must be a better way to do this…

这种方法下一个明显的问题是:“如果我想添加一个“ StaminaBuff”组件该怎么办? 如果要添加“ ManaBuff”组件怎么办? Etcetera。 最简单的解决方案是创建尽可能多的“ Expiry”组件和“ Expiry”系统,但是我可以保证您在编写代码时会感到不舒服。 所有Expiry组件和系统都将完全相同 ,只是名称相同。 当然,必须有更好的方法来做到这一点……

What we really want to express here is that we want to remove a component, any component, after a certain amount of time has expired. It really doesn’t matter (or rather: shouldn’t matter) for the systems which component is to be removed. If only we could write a single Expiry component and system, that we could simply apply to any Buff component. That’s where traits come in.

我们真正要表达的是,我们希望在一定时间后删除一个组件, 任何组件 。 对于要删除哪个组件的系统来说,这确实无关紧要(或更确切地说:无关紧要)。 如果只编写一个Expiry组件和系统,就可以简单地应用于任何Buff组件。 这就是特质的体现。

The problem is obvious: if we attempt to add the Expiry component for both HealthBuff and StaminaBuff, the second add would overwrite the first. After all, we can only add a component once per entity, and thus this doesn’t work.

问题很明显:如果我们尝试同时为HealthBuff和StaminaBuff添加Expiry组件,则第二个添加将覆盖第一个添加。 毕竟,我们每个实体只能添加一个组件,因此这是行不通的。

Instead, we need to be able to add Expiry for a component. That is what a trait is. Here, we would define Expiry as our trait that we can apply to any component we added to our entity. Because it is a trait (and because we make up the rules for it) we can add a trait as many times as we want, as long as it is applied to a different component each time.

相反,我们需要能够为组件添加Expiry 那就是特质。 在这里,我们将Expiry定义为可用于我们添加到实体中的任何组件的特征 。 因为它是一个特征(并且因为我们为它制定了规则),所以我们可以根据需要添加任意多次特征,只要每次将其应用于不同的组件即可。

Hold on, adding components to components? That sounds like it is going to complicate the ECS implementation by a lot, and that added complexity is probably not worth it, considering that this is a pretty niche use case.

等等,向组件添加组件吗? 听起来这会使ECS实施复杂化很多 ,并且考虑到这是一个相当小众的用例,因此增加的复杂性可能不值得。

I wouldn’t have gone through all this trouble if it were actually that complex.

如果实际上如此复杂,我将不会经历所有的麻烦。

The key thing to realize is that for an ECS, it really doesn’t matter if you add a type more than once. Most ECS frameworks apply type erasure which means that the framework only has very basic knowledge about your component types. Typically it won’t need to know more than just the size of a type.

要实现的关键是,对于ECS,添加一次以上的类型实际上并不重要。 大多数ECS框架都应用类型擦除 ,这意味着该框架仅具有有关组件类型的非常基础的知识。 通常,只需要知道一个类型的大小就可以了。

The limitation of only being able to add a component, or type multiple times is therefore self-imposed. We enforce that an EntityType vector can only contain exactly one instance of each identifier. Otherwise, operations such as add, get and remove would always need a qualifier: which one? But, as with all self-imposed rules, we can relax them if we have good reason to do so.

因此只能施加一个限制,即只能添加一个组件或多次键入 。 我们强制EntityType向量只能仅包含每个标识符的一个实例。 否则,诸如add,get和remove之类的操作将始终需要使用限定符: 哪个 ? 但是,与所有自我施加的规则一样,如果有充分的理由,我们可以放宽它们。

“an EntityType vector can only contain exactly one instance of each identifier”

“一个EntityType向量只能只包含每个标识符的一个实例”

This is what prevents us from adding the Expiry component multiple times. But what if we could use a different identifier for the Expiry component? That would solve one problem, in that we can now add it multiple times. But it only creates a new problem, which is: how do we generate those new identifiers?

这就是阻止我们多次添加Expiry组件的原因。 但是,如果我们可以对Expiry组件使用不同的标识符怎么办? 这样可以解决一个问题,因为我们现在可以多次添加它。 但这只会带来一个新问题,即:我们如何生成这些新标识符?

It turns out that the answer to this problem is more straightforward. If we want to be able to add a trait to a component, why don’t we combine the component identifier with the trait identifier to generate a new id? After all, we have a 64 bit addressing space. What if we reserve the first 32 bit for the component id, and the second 24 bit for the trait id (the remaining 8 bit is for type roles)? That still leaves us with 4 billion component ids and 16 million traits, which is more than the number of components in any project to date.

事实证明,此问题的答案更为简单。 如果我们希望能够向组件添加特征,为什么不组件标识符与特征标识符结合起来以生成新的ID? 毕竟,我们有64位寻址空间。 如果我们为组件ID保留前32位,为特征ID保留后24位(其余8位用于类型角色)怎么办? 这仍然给我们留下了40亿个组件ID和1600万个特征,这比迄今为止任何项目中的组件数量都要多。

In code, we can now do something like this, which combines the component ids of HealthBuff and Expiry in one id:

在代码中,我们现在可以执行类似的操作,将HealthBuff和Expiry的组件ID组合为一个ID:

EntityId trait = health_buf_id;
trait |= expiry_id << 32;

We now need to be able to tell the framework that this is in fact a trait, and not just a regular entity id. To do this, we introduce an additional TRAIT type role, so that we can do this:

现在,我们需要能够告诉框架这实际上是一个特征,而不仅仅是常规的实体ID。 为此,我们引入了一个额外的TRAIT类型角色,因此我们可以这样做:

EntityId e = ecs::new();
e.add(TRAIT | trait);

When the framework encounters the TRAIT type role, it will know to split up the identifier into the trait id and component id. When matching systems with traits, we can do the same trick, where a system that matches with Expiry is invoked for each instance of Expiry on the entity (the exact implementation of this is interesting, but out of scope for this blog).

当框架遇到TRAIT类型角色时,它将知道将标识符分为特征ID和组件ID。 当将具有特征的系统进行匹配时,我们可以做同样的技巧,即为实体上的每个Expiry实例调用一个与Expiry匹配的系统(这的确切实现很有趣,但是超出了本博客的范围)。

What’s best about this, is that we barely changed anything in our ECS framework. We still store entities with components, where a component is a unique identifier. It just so happens now that some of those unique identifiers point to the same type. If implemented well, this is only a very minor change, but comes with a great deal of benefits.

最好的是,我们几乎没有更改ECS框架中的任何内容。 我们仍然存储带有组件的实体,其中组件是唯一的标识符。 现在恰好发生了,其中一些唯一标识符指向同一类型。 如果实施得当,这只是很小的变化,但是会带来很多好处。

实体生成 (Entity Generation)

A feature that I haven’t implemented yet, but am planning to implement is entity generations. When an entity is deleted, it doesn’t mean that there are no more references to it. Especially since entities are regular integers, there is no weak-reference mechanism that magically nulls an identifier once deleted.

我尚未实现但计划实现的功能是实体世代。 删除实体后,并不意味着不再有对其的引用。 特别是由于实体是规则整数,因此不存在弱引用机制,该机制一旦删除就神奇地使标识符无效。

An answer to this problem is entity generations. A generation is a number that gets automatically increased every time an entity is deleted. With generations, an application can check if an entity is already deleted by simply comparing generations: if the active generation is different from the generation stored in the reference, the entity was deleted and the id should no longer be used.

这个问题的答案是实体世代。 世代是每次删除实体时都会自动增加的数字。 使用世代,应用程序可以通过简单地比较世代来检查是否已删除实体:如果活动世代与引用中存储的世代不同,则该实体已删除,并且不应再使用id。

A generation is yet another thing that can be encoded in the entity identifier. At this point you may be wondering if between type roles and traits we haven’t used up all of the available addressing space, and whether this isn’t going to put serious constraints on the total number of entities we can have.

世代是可以编码在实体标识符中的另一件事。 在这一点上,您可能想知道在类型角色和特征之间我们是否没有用完所有可用的寻址空间,以及这是否不会对我们可以拥有的实体总数施加严格的限制。

The answer is no, for the simple reason that we can reuse the addressing space for type roles. Type roles are only used in the entity type, which means that they don’t eat into our regular addressing space. For this reason, we can use the same byte in our regular identifiers to store the entity generation. Then, when we do an operation on the entity we compare the generation in the handle with the last known generation, and if it doesn’t match, we throw an error.

答案是否定的,原因很简单,我们可以为类型角色重新使用寻址空间。 类型角色仅用于实体类型 ,这意味着它们不会占用我们常规的寻址空间。 因此,我们可以在常规标识符中使用相同的字节来存储实体生成。 然后,当我们对实体进行操作时,我们将句柄中的生成与最后一个已知生成进行比较,如果不匹配,则会抛出错误。

But, the attentive reader may wonder, won’t this prevent us from keeping track of generations of entities that have been added to other entities? The answer is yes, but it doesn’t matter. Consider the things we can add to an entity (components, tags and parents):

但是,细心的读者可能会怀疑,这是否会阻止我们跟踪已添加到其他实体的实体世代? 答案是肯定的,但这并不重要。 考虑一下我们可以添加到实体中的内容(组件,标签和父对象):

  1. Components/tags will never increase in generation as they cannot be deleted (or should not, as this could cause undefined behavior)

    组件/标签将永远不会增加,因为它们不能被删除(或者不应该被删除,因为这可能导致不确定的行为)
  2. If a parent is deleted, its children should also be deleted, therefore it should never be possible to refer to a deleted parent in a type.

    如果删除了父级,则还应该删除其子级,因此永远不可能在类型中引用已删除的父级。

Other kinds of type roles, like instancing, have similar reasons for why this keeping track of generation in an entity type is not necessary. As a general rule of thumb, it seems reasonable to demand from an application that whatever is added to an entity, should not be deleted.

其他类型的类型角色(例如实例化)具有类似的原因,说明了为什么不必在实体类型中保持这种生成跟踪。 一般而言,从应用程序中要求不应删除添加到实体的任何内容,这似乎是合理的。

One remark about only using a single byte to store generation: it is not unthinkable that an entity is removed more than 256 times in the lifespan of an application. This could cause the generation to overflow and start again from 0. We can live with this however, since a generation still greatly reduces the chance of using a deleted entity as the generation of the identifier must match exactly with that of the actual entity generation. The worst case chance of accidentally using a deleted entity is therefore reduced to 1 / 256.

关于仅使用一个字节存储世代的一句话:在应用程序的生命周期中,删除一个实体超过256次并非不可想象。 这可能导致生成溢出并从0重新开始。但是,我们可以忍受,因为生成仍然大大减少了使用已删除实体的机会,因为标识符的生成必须与实际实体的生成完全匹配。 因此,意外使用删除的实体的最坏情况几率降低为1/256。

Lets finish with a topic that is a bit lighter:

让我们结束一个比较轻松的主题:

保留的组件标识符 (Reserved component identifiers)

If component identifiers are entity identifiers, that means that they can theoretically span the entire 64 bit addressing range (48 bit when using type roles, 32 bit when using traits). That is fine, but can be annoying as you can’t use a 64 bit address to index an array: anything above 32 bit, and probably much lower than that, will cause you to run out of memory. This is bad, as using component ids as array indices is one way to implement fast lookups.

如果组件标识符是实体标识符,则意味着它们在理论上可以覆盖整个64位寻址范围(使用类型角色时为48位,使用特征时为32位)。 很好,但是会很烦人,因为您不能使用64位地址为数组建立索引:任何高于32位(可能远低于此位)的内容都会导致内存不足。 这很不好,因为使用组件ID作为数组索引是实现快速查找的一种方法。

Having our component identifiers potentially scattered across the entire addressing range is therefore not desirable. There is one simple solution to this, which is to reserve the first N identifiers for components. Reasonable values for N can vary from project to project, but some multiple of 256 is probably a good place to start.

因此,使我们的组件标识符可能分散在整个寻址范围内是不希望的。 有一个简单的解决方案,即为组件保留前N个标识符。 N的合理值可能因项目而异,但是256的某个倍数可能是一个不错的起点。

Small component identifiers are one of the reasons that archetype traversal in Flecs is fast, as component identifiers are used to find the edge to traverse in an array of edges per archetype.

较小的组件标识符是Flecs中原型遍历速度很快的原因之一,因为组件标识符用于查找要在每个原型的一组边沿中遍历的边。

总结 (Summarizing)

64 bit is not a lot of data to store, but can contain a lot of information about an entity. The greatest thing about it is that all of this information is at our fingertips the moment an id is passed into a function, and only requires a few cheap bitwise operations to retrieve. Furthermore, we have seen that features that previously seemed impossible to implement efficiently in ECS become almost trivial when encoded in an entity identifier.

64位存储的数据不是很多,但是可以包含有关实体的很多信息。 最棒的是,所有这些信息都在将id传递给函数的那一刻就触手可及,并且只需要几个便宜的按位操作即可检索。 此外,我们已经看到,以前似乎无法在ECS中有效实现的功能在以实体标识符进行编码时变得微不足道。

This schematic shows the composition of an entity identifier with all of the techniques combined:

该示意图显示了结合了所有技术的实体标识符的组成:

Image for post

For an implementation of these concepts, check out the Flecs repository: https://github.com/SanderMertens/flecs

有关这些概念的实现,请查看Flecs存储库: https : //github.com/SanderMertens/flecs

If you’re building an ECS, are using Flecs or would like to discuss more about ECS design in general, feel free to join the Flecs discord!

如果您要构建ECS,正在使用Flecs或想在总体上讨论有关ECS设计的更多信息,请随时加入Flecs纷争!

翻译自: https://medium.com/@ajmmertens/doing-a-lot-with-a-little-ecs-identifiers-25a72bd2647

ecs服务器第一次登录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值