2
\$\begingroup\$

I'm updating an old ECS I wrote a couple years back, and I've written the following piece of code in order to generate a GUID:

const auto& es::EntityStore::RequestEntity()
{
    if(m_freeEntities.size() != 0)
    {
        return ec::ENTITY_ID_INVALID;
    }
    //Get the lastmost EntityID
    auto eid = m_freeEntities.back(); //an std::vector<uint64_t>
    m_freeEntities.pop_back();
    //Get the index value of the EntityID
    uint32_t idx = 0;
    idx = eid & 0XFFFFFFFF;
    //Get the version value of the EntityID
    uint32_t version = 0;
    version = ((eid >> 32) & 0xFFFFFFFF) + 1;

    //Update the metadata at idx
    m_entityMetadata.at(idx).version = version;

    //Create & return the new EntityID
    eid = version;
    eid = eid << 32 | idx;
    //an unordered_set<uint64_t>
    return *m_livingEntities.insert(eid).first;
}

However, I believe it can be written out nicer, and perhaps with less machine code generated.

HOW THE GUID WORKS

I am using a uint64_t to represent my GUID. Reading left-to-right, the first 32 bits will represent the version of the GUID, while the last 32 bits represent the index of the GUID.

The index will be used to lookup items in other arrays and vectors, so it never changes.

The version, however, is used to differentiate between an object who might have the same index as another.

When a GUID is invalidated, it is stuck onto the end of a vector. When it is reused, it's version goes up by a counter of 1.

So, if there are any game objects out there referencing ID 0 or belonging to ID 0, even though it occupies the same spot in the array because of it's index, it's GUID will be different because it's version is 1 higher than whatever GUID came before it.

\$\endgroup\$
3
  • \$\begingroup\$ GUIDs aren't 128-bit things? What is an "ECS"? \$\endgroup\$ Commented Jun 16, 2016 at 14:34
  • \$\begingroup\$ I suppose I could make them 128-bit, but I don't see a reason to. No two GUIDs can exist at the same time that reference the same index. Because the index bits (first 32 bits right-to-left) determine the maximum number of allowed active GUIDs, I can have up to 4,294,967,295 active GUIDs at once if I wanted, and each GUID would have 4,294,967,295 different versions. With so many open spots for GUIDs, unless I'm creating a ridiculous amount of GUIDs per second, I should never run out of spots. \$\endgroup\$ Commented Jun 16, 2016 at 16:07
  • \$\begingroup\$ An "ECS" stands for Entity-Component-System. Entities are used to identify a collection of Components, Components are POD structs that describe a property but have no methods or functions, and Systems operate on a specific set of Components, by iterating over the Entities that have subscribed to that system. Entity-Components are like a relation database, while Systems are operations performed on the set of data returned from a query to the relational database. \$\endgroup\$ Commented Jun 16, 2016 at 16:09

2 Answers 2

1
\$\begingroup\$

What I see here:

1.

You use autos, whose bit length is unknown in compile time. Despite that, the code uses them as 64-bit values. Afaik you should have some structural guarantee that they are really 64-bit (f.e. instead of "auto" use simply uint64_t).

2.

This code:

uint32_t version = 0;
version = ((eid >> 32) & 0xFFFFFFFF) + 1;

could be simplified to

uint32_t version = ((eid >> 32) & 0xFFFFFFFF) + 1;
\$\endgroup\$
2
  • \$\begingroup\$ Maybe XCode was acting funky. Originally, I had uint32_t version = ((eid >> 32) & 0xFFFFFFFF) + 1;, but it was spitting out a warning that version was read before it was accessed, so I initialized it before assigning it a value. \$\endgroup\$ Commented Jun 16, 2016 at 16:11
  • \$\begingroup\$ You use autos, whose bit length is unknown in compile time. Nope. The size of all objects is known at compile time. C++ is a statically typed language all types and their sizes are known at compile time. \$\endgroup\$ Commented Jun 16, 2016 at 17:30
1
\$\begingroup\$

initialization & const

Same advice for idx and eid as peterh gave for version. Just initialize it with proper value. I would add const on top of that to let compiler prevent some silly typo bugs.

uint32_t idx = 0;
idx = eid & 0XFFFFFFFF;

Could be simplified to

const uint32_t idx = eid & 0XFFFFFFFF;

And

eid = version;
eid = eid << 32 | idx;

could be simplified to

const eid = version << 32 | idx;

I am not saying it is going to be faster as optimizer might solve it for you but the code might be more clear.

error handling

In general my preferred error handling strategy in C++ are exceptions so this kind of surprised me. It might be perfectly fine though. It depends on your context.

if(m_freeEntities.size() != 0)
{
    return ec::ENTITY_ID_INVALID;
}
\$\endgroup\$
1
  • \$\begingroup\$ I had taken some time to think about that, do I want to throw an exception or return an invalid ID and check it? I've decided that, because I don't want to quit or halt when I run out of room for available GUIDs, returning an invalid ID and checking it would be faster and shouldn't matter too much because of CPU branch prediction magix. I haven't profiled it yet, though, and if performance does become an issue it might be better to just throw & suppress the exception. \$\endgroup\$ Commented Jun 16, 2016 at 21:52

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.