From 2427e1f2aa376e504a57334b4cd9eb2189214791 Mon Sep 17 00:00:00 2001 From: Ramine Agoune Date: Mon, 17 Mar 2025 10:17:40 +0100 Subject: [PATCH 1/6] feat: replace array of bytes with hash name --- world.go | 87 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/world.go b/world.go index 207cabb..768a344 100644 --- a/world.go +++ b/world.go @@ -2,6 +2,7 @@ package volt import ( + "hash/fnv" "math/rand" "slices" "strings" @@ -44,8 +45,9 @@ type entityRecord struct { // // It avoids the garbage collector to analyze this data constantly, // at the price of a fixed data size. -type entityName [64]byte -type entitiesNames map[entityName]EntityId +type entityName = string +type entityHashName = uint64 +type entitiesNames map[entityHashName]EntityId type entities map[EntityId]entityRecord // World representation, container of all the data related to entities and their Components. @@ -109,14 +111,16 @@ func newEntityId() EntityId { // CreateEntity creates a new Entity in World; // It is linked to no Component. func (world *World) CreateEntity(name string) EntityId { - entityName := stringToEntityName(name) + hash := fnv.New64() + hash.Write([]byte(name)) + entityId := newEntityId() archetype := world.getArchetypeForComponentsIds() - world.entitiesNames[entityName] = entityId + world.entitiesNames[hash.Sum64()] = entityId entityRecord := entityRecord{ Id: entityId, - name: entityName, + name: name, } world.entities[entityId] = entityRecord world.setArchetype(entityRecord, archetype) @@ -127,11 +131,12 @@ func (world *World) CreateEntity(name string) EntityId { // CreateEntityWithComponents2 creates an entity in World; // It sets the components A, B to the entity, for faster performances than the atomic version. func CreateEntityWithComponents2[A, B ComponentInterface](world *World, name string, a A, b B) (EntityId, error) { - entityName := stringToEntityName(name) entityId := newEntityId() + hash := fnv.New64() + hash.Write([]byte(name)) - world.entitiesNames[entityName] = entityId - entityRecord := entityRecord{Id: entityId, name: entityName} + world.entitiesNames[hash.Sum64()] = entityId + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents2(world, entityRecord, a, b) @@ -146,11 +151,12 @@ func CreateEntityWithComponents2[A, B ComponentInterface](world *World, name str // // It sets the components A, B, C to the entity, for faster performances than the atomic version. func CreateEntityWithComponents3[A, B, C ComponentInterface](world *World, name string, a A, b B, c C) (EntityId, error) { - entityName := stringToEntityName(name) entityId := newEntityId() + hash := fnv.New64() + hash.Write([]byte(name)) - world.entitiesNames[entityName] = entityId - entityRecord := entityRecord{Id: entityId, name: entityName} + world.entitiesNames[hash.Sum64()] = entityId + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents3(world, entityRecord, a, b, c) @@ -165,11 +171,12 @@ func CreateEntityWithComponents3[A, B, C ComponentInterface](world *World, name // // It sets the components A, B, C, D to the entity, for faster performances than the atomic version. func CreateEntityWithComponents4[A, B, C, D ComponentInterface](world *World, name string, a A, b B, c C, d D) (EntityId, error) { - entityName := stringToEntityName(name) entityId := newEntityId() + hash := fnv.New64() + hash.Write([]byte(name)) - world.entitiesNames[entityName] = entityId - entityRecord := entityRecord{Id: entityId, name: entityName} + world.entitiesNames[hash.Sum64()] = entityId + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents4(world, entityRecord, a, b, c, d) @@ -184,11 +191,12 @@ func CreateEntityWithComponents4[A, B, C, D ComponentInterface](world *World, na // // It sets the components A, B, C, D, E to the entity, for faster performances than the atomic version. func CreateEntityWithComponents5[A, B, C, D, E ComponentInterface](world *World, name string, a A, b B, c C, d D, e E) (EntityId, error) { - entityName := stringToEntityName(name) entityId := newEntityId() + hash := fnv.New64() + hash.Write([]byte(name)) - world.entitiesNames[entityName] = entityId - entityRecord := entityRecord{Id: entityId, name: entityName} + world.entitiesNames[hash.Sum64()] = entityId + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents5(world, entityRecord, a, b, c, d, e) @@ -203,11 +211,12 @@ func CreateEntityWithComponents5[A, B, C, D, E ComponentInterface](world *World, // // It sets the components A, B, C, D, E, F to the entity, for faster performances than the atomic version. func CreateEntityWithComponents6[A, B, C, D, E, F ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F) (EntityId, error) { - entityName := stringToEntityName(name) entityId := newEntityId() + hash := fnv.New64() + hash.Write([]byte(name)) - world.entitiesNames[entityName] = entityId - entityRecord := entityRecord{Id: entityId, name: entityName} + world.entitiesNames[hash.Sum64()] = entityId + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents6(world, entityRecord, a, b, c, d, e, f) @@ -222,11 +231,12 @@ func CreateEntityWithComponents6[A, B, C, D, E, F ComponentInterface](world *Wor // // It sets the components A, B, C, D, E, F, G to the entity, for faster performances than the atomic version. func CreateEntityWithComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F, g G) (EntityId, error) { - entityName := stringToEntityName(name) entityId := newEntityId() + hash := fnv.New64() + hash.Write([]byte(name)) - world.entitiesNames[entityName] = entityId - entityRecord := entityRecord{Id: entityId, name: entityName} + world.entitiesNames[hash.Sum64()] = entityId + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents7(world, entityRecord, a, b, c, d, e, f, g) @@ -241,11 +251,12 @@ func CreateEntityWithComponents7[A, B, C, D, E, F, G ComponentInterface](world * // // It sets the components A, B, C, D, E, F, G, H to the entity, for faster performances than the atomic version. func CreateEntityWithComponents8[A, B, C, D, E, F, G, H ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F, g G, h H) (EntityId, error) { - entityName := stringToEntityName(name) entityId := newEntityId() + hash := fnv.New64() + hash.Write([]byte(name)) - world.entitiesNames[entityName] = entityId - entityRecord := entityRecord{Id: entityId, name: entityName} + world.entitiesNames[hash.Sum64()] = entityId + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents8(world, entityRecord, a, b, c, d, e, f, g, h) @@ -290,15 +301,19 @@ func (world *World) RemoveEntity(entityId EntityId) { world.archetypes[archetype.Id] = archetype } - delete(world.entitiesNames, world.entities[entityId].name) + hash := fnv.New64() + hash.Write([]byte(world.entities[entityId].name)) + delete(world.entitiesNames, hash.Sum64()) delete(world.entities, entityId) } // SearchEntity returns the EntityId named by name. // If not found, returns 0. func (world *World) SearchEntity(name string) EntityId { - entityName := stringToEntityName(name) - if entityId, ok := world.entitiesNames[entityName]; ok { + hash := fnv.New64() + hash.Write([]byte(name)) + + if entityId, ok := world.entitiesNames[hash.Sum64()]; ok { return entityId } @@ -317,12 +332,13 @@ func (world *World) GetEntityName(entityId EntityId) string { // SetEntityName sets the name for an EntityId. func (world *World) SetEntityName(entityId EntityId, name string) { - entityName := stringToEntityName(name) + hash := fnv.New64() + hash.Write([]byte(name)) entityRecord := world.entities[entityId] - entityRecord.name = entityName + entityRecord.name = entityName(name) world.entities[entityId] = entityRecord - world.entitiesNames[entityName] = entityId + world.entitiesNames[hash.Sum64()] = entityId } // Count returns the number of entities in World. @@ -330,13 +346,6 @@ func (world *World) Count() int { return len(world.entities) } -func stringToEntityName(name string) entityName { - var nameByte entityName - copy(nameByte[:], name) - - return nameByte -} - func entityNameToString(entityName entityName) string { return strings.TrimRight(string(entityName[:]), "\x00") } From fb650bfd560437f480622387bfa62a7bf04461e1 Mon Sep 17 00:00:00 2001 From: Ramine Agoune Date: Mon, 17 Mar 2025 10:45:38 +0100 Subject: [PATCH 2/6] feat: change entityName to byte slice --- world.go | 90 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/world.go b/world.go index 768a344..9dc5d16 100644 --- a/world.go +++ b/world.go @@ -45,7 +45,7 @@ type entityRecord struct { // // It avoids the garbage collector to analyze this data constantly, // at the price of a fixed data size. -type entityName = string +type entityName []byte type entityHashName = uint64 type entitiesNames map[entityHashName]EntityId type entities map[EntityId]entityRecord @@ -111,16 +111,14 @@ func newEntityId() EntityId { // CreateEntity creates a new Entity in World; // It is linked to no Component. func (world *World) CreateEntity(name string) EntityId { - hash := fnv.New64() - hash.Write([]byte(name)) - + entityName := stringToEntityName(name) entityId := newEntityId() archetype := world.getArchetypeForComponentsIds() - world.entitiesNames[hash.Sum64()] = entityId + world.entitiesNames[hashEntityName(entityName)] = entityId entityRecord := entityRecord{ Id: entityId, - name: name, + name: entityName, } world.entities[entityId] = entityRecord world.setArchetype(entityRecord, archetype) @@ -131,12 +129,11 @@ func (world *World) CreateEntity(name string) EntityId { // CreateEntityWithComponents2 creates an entity in World; // It sets the components A, B to the entity, for faster performances than the atomic version. func CreateEntityWithComponents2[A, B ComponentInterface](world *World, name string, a A, b B) (EntityId, error) { + entityName := stringToEntityName(name) entityId := newEntityId() - hash := fnv.New64() - hash.Write([]byte(name)) - world.entitiesNames[hash.Sum64()] = entityId - entityRecord := entityRecord{Id: entityId, name: name} + world.entitiesNames[hashEntityName(entityName)] = entityId + entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord err := addComponents2(world, entityRecord, a, b) @@ -151,12 +148,11 @@ func CreateEntityWithComponents2[A, B ComponentInterface](world *World, name str // // It sets the components A, B, C to the entity, for faster performances than the atomic version. func CreateEntityWithComponents3[A, B, C ComponentInterface](world *World, name string, a A, b B, c C) (EntityId, error) { + entityName := stringToEntityName(name) entityId := newEntityId() - hash := fnv.New64() - hash.Write([]byte(name)) - world.entitiesNames[hash.Sum64()] = entityId - entityRecord := entityRecord{Id: entityId, name: name} + world.entitiesNames[hashEntityName(entityName)] = entityId + entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord err := addComponents3(world, entityRecord, a, b, c) @@ -171,12 +167,11 @@ func CreateEntityWithComponents3[A, B, C ComponentInterface](world *World, name // // It sets the components A, B, C, D to the entity, for faster performances than the atomic version. func CreateEntityWithComponents4[A, B, C, D ComponentInterface](world *World, name string, a A, b B, c C, d D) (EntityId, error) { + entityName := stringToEntityName(name) entityId := newEntityId() - hash := fnv.New64() - hash.Write([]byte(name)) - world.entitiesNames[hash.Sum64()] = entityId - entityRecord := entityRecord{Id: entityId, name: name} + world.entitiesNames[hashEntityName(entityName)] = entityId + entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord err := addComponents4(world, entityRecord, a, b, c, d) @@ -191,12 +186,11 @@ func CreateEntityWithComponents4[A, B, C, D ComponentInterface](world *World, na // // It sets the components A, B, C, D, E to the entity, for faster performances than the atomic version. func CreateEntityWithComponents5[A, B, C, D, E ComponentInterface](world *World, name string, a A, b B, c C, d D, e E) (EntityId, error) { + entityName := stringToEntityName(name) entityId := newEntityId() - hash := fnv.New64() - hash.Write([]byte(name)) - world.entitiesNames[hash.Sum64()] = entityId - entityRecord := entityRecord{Id: entityId, name: name} + world.entitiesNames[hashEntityName(entityName)] = entityId + entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord err := addComponents5(world, entityRecord, a, b, c, d, e) @@ -211,12 +205,11 @@ func CreateEntityWithComponents5[A, B, C, D, E ComponentInterface](world *World, // // It sets the components A, B, C, D, E, F to the entity, for faster performances than the atomic version. func CreateEntityWithComponents6[A, B, C, D, E, F ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F) (EntityId, error) { + entityName := stringToEntityName(name) entityId := newEntityId() - hash := fnv.New64() - hash.Write([]byte(name)) - world.entitiesNames[hash.Sum64()] = entityId - entityRecord := entityRecord{Id: entityId, name: name} + world.entitiesNames[hashEntityName(entityName)] = entityId + entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord err := addComponents6(world, entityRecord, a, b, c, d, e, f) @@ -231,12 +224,11 @@ func CreateEntityWithComponents6[A, B, C, D, E, F ComponentInterface](world *Wor // // It sets the components A, B, C, D, E, F, G to the entity, for faster performances than the atomic version. func CreateEntityWithComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F, g G) (EntityId, error) { + entityName := stringToEntityName(name) entityId := newEntityId() - hash := fnv.New64() - hash.Write([]byte(name)) - world.entitiesNames[hash.Sum64()] = entityId - entityRecord := entityRecord{Id: entityId, name: name} + world.entitiesNames[hashEntityName(entityName)] = entityId + entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord err := addComponents7(world, entityRecord, a, b, c, d, e, f, g) @@ -251,12 +243,11 @@ func CreateEntityWithComponents7[A, B, C, D, E, F, G ComponentInterface](world * // // It sets the components A, B, C, D, E, F, G, H to the entity, for faster performances than the atomic version. func CreateEntityWithComponents8[A, B, C, D, E, F, G, H ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F, g G, h H) (EntityId, error) { + entityName := stringToEntityName(name) entityId := newEntityId() - hash := fnv.New64() - hash.Write([]byte(name)) - world.entitiesNames[hash.Sum64()] = entityId - entityRecord := entityRecord{Id: entityId, name: name} + world.entitiesNames[hashEntityName(entityName)] = entityId + entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord err := addComponents8(world, entityRecord, a, b, c, d, e, f, g, h) @@ -301,19 +292,15 @@ func (world *World) RemoveEntity(entityId EntityId) { world.archetypes[archetype.Id] = archetype } - hash := fnv.New64() - hash.Write([]byte(world.entities[entityId].name)) - delete(world.entitiesNames, hash.Sum64()) + delete(world.entitiesNames, hashEntityName(world.entities[entityId].name)) delete(world.entities, entityId) } // SearchEntity returns the EntityId named by name. // If not found, returns 0. func (world *World) SearchEntity(name string) EntityId { - hash := fnv.New64() - hash.Write([]byte(name)) - - if entityId, ok := world.entitiesNames[hash.Sum64()]; ok { + entityName := stringToEntityName(name) + if entityId, ok := world.entitiesNames[hashEntityName(entityName)]; ok { return entityId } @@ -332,13 +319,12 @@ func (world *World) GetEntityName(entityId EntityId) string { // SetEntityName sets the name for an EntityId. func (world *World) SetEntityName(entityId EntityId, name string) { - hash := fnv.New64() - hash.Write([]byte(name)) + entityName := stringToEntityName(name) entityRecord := world.entities[entityId] - entityRecord.name = entityName(name) + entityRecord.name = entityName world.entities[entityId] = entityRecord - world.entitiesNames[hash.Sum64()] = entityId + world.entitiesNames[hashEntityName(entityName)] = entityId } // Count returns the number of entities in World. @@ -346,6 +332,20 @@ func (world *World) Count() int { return len(world.entities) } +func hashEntityName(name entityName) entityHashName { + h := fnv.New64() + h.Write(name) + + return h.Sum64() +} + +func stringToEntityName(name string) entityName { + var nameByte entityName + copy(nameByte[:], name) + + return nameByte +} + func entityNameToString(entityName entityName) string { return strings.TrimRight(string(entityName[:]), "\x00") } From 20235bb2c65e51ea17415c083f464ea5020d5fc6 Mon Sep 17 00:00:00 2001 From: Ramine Agoune Date: Mon, 17 Mar 2025 11:28:38 +0100 Subject: [PATCH 3/6] feat: generate entityId from name string Hashing the name, we can remove the random generation of EntityId. This requires to have unique names for entities --- world.go | 46 +++++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/world.go b/world.go index 9dc5d16..a91fbf7 100644 --- a/world.go +++ b/world.go @@ -46,14 +46,11 @@ type entityRecord struct { // It avoids the garbage collector to analyze this data constantly, // at the price of a fixed data size. type entityName []byte -type entityHashName = uint64 -type entitiesNames map[entityHashName]EntityId type entities map[EntityId]entityRecord // World representation, container of all the data related to entities and their Components. type World struct { componentsRegistry ComponentsRegister - entitiesNames entitiesNames entities entities archetypes []archetype storage []storage @@ -69,7 +66,6 @@ type World struct { // It preallocates initialCapacity in memory. func CreateWorld(initialCapacity int) *World { world := &World{ - entitiesNames: make(entitiesNames, initialCapacity), entities: make(entities, initialCapacity), archetypes: make([]archetype, 0, 1024), storage: make([]storage, TAGS_INDICES), @@ -111,11 +107,14 @@ func newEntityId() EntityId { // CreateEntity creates a new Entity in World; // It is linked to no Component. func (world *World) CreateEntity(name string) EntityId { + if existingId := world.SearchEntity(name); existingId != 0 { + return existingId + } + entityName := stringToEntityName(name) - entityId := newEntityId() + entityId := hashEntityName(entityName) archetype := world.getArchetypeForComponentsIds() - world.entitiesNames[hashEntityName(entityName)] = entityId entityRecord := entityRecord{ Id: entityId, name: entityName, @@ -130,9 +129,8 @@ func (world *World) CreateEntity(name string) EntityId { // It sets the components A, B to the entity, for faster performances than the atomic version. func CreateEntityWithComponents2[A, B ComponentInterface](world *World, name string, a A, b B) (EntityId, error) { entityName := stringToEntityName(name) - entityId := newEntityId() + entityId := hashEntityName(entityName) - world.entitiesNames[hashEntityName(entityName)] = entityId entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord @@ -149,9 +147,8 @@ func CreateEntityWithComponents2[A, B ComponentInterface](world *World, name str // It sets the components A, B, C to the entity, for faster performances than the atomic version. func CreateEntityWithComponents3[A, B, C ComponentInterface](world *World, name string, a A, b B, c C) (EntityId, error) { entityName := stringToEntityName(name) - entityId := newEntityId() + entityId := hashEntityName(entityName) - world.entitiesNames[hashEntityName(entityName)] = entityId entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord @@ -168,9 +165,8 @@ func CreateEntityWithComponents3[A, B, C ComponentInterface](world *World, name // It sets the components A, B, C, D to the entity, for faster performances than the atomic version. func CreateEntityWithComponents4[A, B, C, D ComponentInterface](world *World, name string, a A, b B, c C, d D) (EntityId, error) { entityName := stringToEntityName(name) - entityId := newEntityId() + entityId := hashEntityName(entityName) - world.entitiesNames[hashEntityName(entityName)] = entityId entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord @@ -187,9 +183,8 @@ func CreateEntityWithComponents4[A, B, C, D ComponentInterface](world *World, na // It sets the components A, B, C, D, E to the entity, for faster performances than the atomic version. func CreateEntityWithComponents5[A, B, C, D, E ComponentInterface](world *World, name string, a A, b B, c C, d D, e E) (EntityId, error) { entityName := stringToEntityName(name) - entityId := newEntityId() + entityId := hashEntityName(entityName) - world.entitiesNames[hashEntityName(entityName)] = entityId entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord @@ -206,9 +201,8 @@ func CreateEntityWithComponents5[A, B, C, D, E ComponentInterface](world *World, // It sets the components A, B, C, D, E, F to the entity, for faster performances than the atomic version. func CreateEntityWithComponents6[A, B, C, D, E, F ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F) (EntityId, error) { entityName := stringToEntityName(name) - entityId := newEntityId() - world.entitiesNames[hashEntityName(entityName)] = entityId + entityId := hashEntityName(entityName) entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord @@ -225,9 +219,8 @@ func CreateEntityWithComponents6[A, B, C, D, E, F ComponentInterface](world *Wor // It sets the components A, B, C, D, E, F, G to the entity, for faster performances than the atomic version. func CreateEntityWithComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F, g G) (EntityId, error) { entityName := stringToEntityName(name) - entityId := newEntityId() - world.entitiesNames[hashEntityName(entityName)] = entityId + entityId := hashEntityName(entityName) entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord @@ -244,9 +237,8 @@ func CreateEntityWithComponents7[A, B, C, D, E, F, G ComponentInterface](world * // It sets the components A, B, C, D, E, F, G, H to the entity, for faster performances than the atomic version. func CreateEntityWithComponents8[A, B, C, D, E, F, G, H ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F, g G, h H) (EntityId, error) { entityName := stringToEntityName(name) - entityId := newEntityId() - world.entitiesNames[hashEntityName(entityName)] = entityId + entityId := hashEntityName(entityName) entityRecord := entityRecord{Id: entityId, name: entityName} world.entities[entityId] = entityRecord @@ -292,7 +284,6 @@ func (world *World) RemoveEntity(entityId EntityId) { world.archetypes[archetype.Id] = archetype } - delete(world.entitiesNames, hashEntityName(world.entities[entityId].name)) delete(world.entities, entityId) } @@ -300,7 +291,8 @@ func (world *World) RemoveEntity(entityId EntityId) { // If not found, returns 0. func (world *World) SearchEntity(name string) EntityId { entityName := stringToEntityName(name) - if entityId, ok := world.entitiesNames[hashEntityName(entityName)]; ok { + entityId := hashEntityName(entityName) + if _, ok := world.entities[entityId]; ok { return entityId } @@ -324,7 +316,6 @@ func (world *World) SetEntityName(entityId EntityId, name string) { entityRecord := world.entities[entityId] entityRecord.name = entityName world.entities[entityId] = entityRecord - world.entitiesNames[hashEntityName(entityName)] = entityId } // Count returns the number of entities in World. @@ -332,18 +323,15 @@ func (world *World) Count() int { return len(world.entities) } -func hashEntityName(name entityName) entityHashName { +func hashEntityName(name entityName) EntityId { h := fnv.New64() h.Write(name) - return h.Sum64() + return EntityId(h.Sum64()) } func stringToEntityName(name string) entityName { - var nameByte entityName - copy(nameByte[:], name) - - return nameByte + return []byte(name) } func entityNameToString(entityName entityName) string { From 18c0e5827f7bbec069662a6d580a79be2e009870 Mon Sep 17 00:00:00 2001 From: Ramine Agoune Date: Mon, 17 Mar 2025 11:55:52 +0100 Subject: [PATCH 4/6] feat: simplify API for entity names --- world.go | 73 +++++++++++++++++++------------------------------------- 1 file changed, 24 insertions(+), 49 deletions(-) diff --git a/world.go b/world.go index a91fbf7..60cf0c4 100644 --- a/world.go +++ b/world.go @@ -3,9 +3,7 @@ package volt import ( "hash/fnv" - "math/rand" "slices" - "strings" ) // uint16 identifier, for small scoped data. @@ -45,7 +43,7 @@ type entityRecord struct { // // It avoids the garbage collector to analyze this data constantly, // at the price of a fixed data size. -type entityName []byte +type entityName = string type entities map[EntityId]entityRecord // World representation, container of all the data related to entities and their Components. @@ -100,10 +98,6 @@ func (world *World) SetComponentRemovedFn(componentRemovedFn func(entityId Entit world.componentRemovedFn = componentRemovedFn } -func newEntityId() EntityId { - return EntityId(rand.Uint64()) -} - // CreateEntity creates a new Entity in World; // It is linked to no Component. func (world *World) CreateEntity(name string) EntityId { @@ -111,13 +105,12 @@ func (world *World) CreateEntity(name string) EntityId { return existingId } - entityName := stringToEntityName(name) - entityId := hashEntityName(entityName) + entityId := hashEntityName(name) archetype := world.getArchetypeForComponentsIds() entityRecord := entityRecord{ Id: entityId, - name: entityName, + name: name, } world.entities[entityId] = entityRecord world.setArchetype(entityRecord, archetype) @@ -128,10 +121,9 @@ func (world *World) CreateEntity(name string) EntityId { // CreateEntityWithComponents2 creates an entity in World; // It sets the components A, B to the entity, for faster performances than the atomic version. func CreateEntityWithComponents2[A, B ComponentInterface](world *World, name string, a A, b B) (EntityId, error) { - entityName := stringToEntityName(name) - entityId := hashEntityName(entityName) + entityId := hashEntityName(name) - entityRecord := entityRecord{Id: entityId, name: entityName} + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents2(world, entityRecord, a, b) @@ -146,10 +138,9 @@ func CreateEntityWithComponents2[A, B ComponentInterface](world *World, name str // // It sets the components A, B, C to the entity, for faster performances than the atomic version. func CreateEntityWithComponents3[A, B, C ComponentInterface](world *World, name string, a A, b B, c C) (EntityId, error) { - entityName := stringToEntityName(name) - entityId := hashEntityName(entityName) + entityId := hashEntityName(name) - entityRecord := entityRecord{Id: entityId, name: entityName} + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents3(world, entityRecord, a, b, c) @@ -164,10 +155,9 @@ func CreateEntityWithComponents3[A, B, C ComponentInterface](world *World, name // // It sets the components A, B, C, D to the entity, for faster performances than the atomic version. func CreateEntityWithComponents4[A, B, C, D ComponentInterface](world *World, name string, a A, b B, c C, d D) (EntityId, error) { - entityName := stringToEntityName(name) - entityId := hashEntityName(entityName) + entityId := hashEntityName(name) - entityRecord := entityRecord{Id: entityId, name: entityName} + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents4(world, entityRecord, a, b, c, d) @@ -182,10 +172,9 @@ func CreateEntityWithComponents4[A, B, C, D ComponentInterface](world *World, na // // It sets the components A, B, C, D, E to the entity, for faster performances than the atomic version. func CreateEntityWithComponents5[A, B, C, D, E ComponentInterface](world *World, name string, a A, b B, c C, d D, e E) (EntityId, error) { - entityName := stringToEntityName(name) - entityId := hashEntityName(entityName) + entityId := hashEntityName(name) - entityRecord := entityRecord{Id: entityId, name: entityName} + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents5(world, entityRecord, a, b, c, d, e) @@ -200,10 +189,8 @@ func CreateEntityWithComponents5[A, B, C, D, E ComponentInterface](world *World, // // It sets the components A, B, C, D, E, F to the entity, for faster performances than the atomic version. func CreateEntityWithComponents6[A, B, C, D, E, F ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F) (EntityId, error) { - entityName := stringToEntityName(name) - - entityId := hashEntityName(entityName) - entityRecord := entityRecord{Id: entityId, name: entityName} + entityId := hashEntityName(name) + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents6(world, entityRecord, a, b, c, d, e, f) @@ -218,10 +205,8 @@ func CreateEntityWithComponents6[A, B, C, D, E, F ComponentInterface](world *Wor // // It sets the components A, B, C, D, E, F, G to the entity, for faster performances than the atomic version. func CreateEntityWithComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F, g G) (EntityId, error) { - entityName := stringToEntityName(name) - - entityId := hashEntityName(entityName) - entityRecord := entityRecord{Id: entityId, name: entityName} + entityId := hashEntityName(name) + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents7(world, entityRecord, a, b, c, d, e, f, g) @@ -236,10 +221,8 @@ func CreateEntityWithComponents7[A, B, C, D, E, F, G ComponentInterface](world * // // It sets the components A, B, C, D, E, F, G, H to the entity, for faster performances than the atomic version. func CreateEntityWithComponents8[A, B, C, D, E, F, G, H ComponentInterface](world *World, name string, a A, b B, c C, d D, e E, f F, g G, h H) (EntityId, error) { - entityName := stringToEntityName(name) - - entityId := hashEntityName(entityName) - entityRecord := entityRecord{Id: entityId, name: entityName} + entityId := hashEntityName(name) + entityRecord := entityRecord{Id: entityId, name: name} world.entities[entityId] = entityRecord err := addComponents8(world, entityRecord, a, b, c, d, e, f, g, h) @@ -290,8 +273,7 @@ func (world *World) RemoveEntity(entityId EntityId) { // SearchEntity returns the EntityId named by name. // If not found, returns 0. func (world *World) SearchEntity(name string) EntityId { - entityName := stringToEntityName(name) - entityId := hashEntityName(entityName) + entityId := hashEntityName(name) if _, ok := world.entities[entityId]; ok { return entityId } @@ -303,7 +285,7 @@ func (world *World) SearchEntity(name string) EntityId { // If not found, returns an empty string. func (world *World) GetEntityName(entityId EntityId) string { if entity, ok := world.entities[entityId]; ok { - return entityNameToString(entity.name) + return entity.name } return "" @@ -311,10 +293,8 @@ func (world *World) GetEntityName(entityId EntityId) string { // SetEntityName sets the name for an EntityId. func (world *World) SetEntityName(entityId EntityId, name string) { - entityName := stringToEntityName(name) - entityRecord := world.entities[entityId] - entityRecord.name = entityName + entityRecord.name = name world.entities[entityId] = entityRecord } @@ -325,15 +305,10 @@ func (world *World) Count() int { func hashEntityName(name entityName) EntityId { h := fnv.New64() - h.Write(name) + _, err := h.Write([]byte(name)) + if err != nil { + return EntityId(0) + } return EntityId(h.Sum64()) } - -func stringToEntityName(name string) entityName { - return []byte(name) -} - -func entityNameToString(entityName entityName) string { - return strings.TrimRight(string(entityName[:]), "\x00") -} From 430c5391c964d05207508706c9b8e016a18b38fc Mon Sep 17 00:00:00 2001 From: Ramine Agoune Date: Mon, 17 Mar 2025 12:08:28 +0100 Subject: [PATCH 5/6] chore: add new benchmark --- README.md | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index b68329c..578d541 100644 --- a/README.md +++ b/README.md @@ -192,27 +192,32 @@ Few ECS tools exist for Go. Arche and unitoftime/ecs are probably the most looke In the benchmark folder, this module is compared to both of them. - Go - v1.24.0 -- Volt - v1.4.0 +- Volt - v1.5.0 - [Arche - v0.15.3](https://github.com/mlange-42/arche) - [UECS - v0.0.3](https://github.com/unitoftime/ecs) The given results were produced by a ryzen 7 5800x, with 100.000 entities: -| Benchmark feature (entities count) | Time/Operation | Bytes/Operation | Allocations/Operation | -|-------------------------------------------------------------------|----------------|-----------------|-----------------------| -| BenchmarkCreateEntityVolt (100000) | 41716162 ns/op | 57996246 B/op | 200517 allocs/op | -| BenchmarkCreateEntityArche (100000) | 6922002 ns/op | 11096962 B/op | 61 allocs/op | -| BenchmarkCreateEntityUECS (100000) | 34596263 ns/op | 49119538 B/op | 200146 allocs/op | -| BenchmarkIterateVolt (100000) | 317646 ns/op | 264 B/op | 9 allocs/op | -| BenchmarkIterateConcurrentlyVolt (100000) - 16 concurrent workers | 95976 ns/op | 3327 B/op | 93 allocs/op | -| BenchmarkIterateArche (100000) | 429663 ns/op | 354 B/op | 4 allocs/op | -| BenchmarkIterateUECS (100000) | 234043 ns/op | 128 B/op | 3 allocs/op | -| BenchmarkAddVolt (100000) | 29081055 ns/op | 4806438 B/op | 300002 allocs/op | -| BenchmarkAddArche (100000) | 4262538 ns/op | 119805 B/op | 100000 allocs/op | -| BenchmarkAddUECS (100000) | 37041871 ns/op | 4574654 B/op | 100004 allocs/op | -| BenchmarkRemoveVolt (100000) | 21988113 ns/op | 400000 B/op | 100000 allocs/op | -| BenchmarkRemoveArche (100000) | 4749902 ns/op | 100000 B/op | 100000 allocs/op | -| BenchmarkRemoveUECS (100000) | 31742113 ns/op | 3328168 B/op | 100000 allocs/op | +goos: linux +goarch: amd64 +pkg: benchmark +cpu: AMD Ryzen 7 5800X 8-Core Processor + +| Benchmark | Iterations | ns/op | B/op | Allocs/op | +|---------------------------------|------------|-----------|------------|-----------| +| BenchmarkCreateEntityArche-16 | 171 | 6948273 | 11096966 | 61 | +| BenchmarkIterateArche-16 | 2704 | 426795 | 354 | 4 | +| BenchmarkAddArche-16 | 279 | 4250519 | 120089 | 100000 | +| BenchmarkRemoveArche-16 | 249 | 4821120 | 100000 | 100000 | +| BenchmarkCreateEntityUECS-16 | 34 | 37943381 | 49119549 | 200146 | +| BenchmarkIterateUECS-16 | 3885 | 287027 | 128 | 3 | +| BenchmarkAddUECS-16 | 30 | 38097927 | 4620476 | 100004 | +| BenchmarkRemoveUECS-16 | 40 | 31008811 | 3302536 | 100000 | +| BenchmarkCreateEntityVolt-16 | 49 | 27246822 | 41214216 | 200259 | +| BenchmarkIterateVolt-16 | 3651 | 329858 | 264 | 9 | +| BenchmarkIterateConcurrentlyVolt-16 | 10000 | 102732 | 3330 | 93 | +| BenchmarkAddVolt-16 | 54 | 22508281 | 4597363 | 300001 | +| BenchmarkRemoveVolt-16 | 72 | 17219355 | 400001 | 100000 | These results show a few things: - Arche is the fastest tool for writes operations. In our game development though we would rather lean towards fastest read operations, because the games loops will read way more often than write. From 05e488c0f6fce68c19b787cd001f540b8a13e45a Mon Sep 17 00:00:00 2001 From: Ramine Agoune Date: Mon, 17 Mar 2025 13:20:29 +0100 Subject: [PATCH 6/6] chore: warning unique entity name in documentation --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 578d541..ce44ff6 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ volt.RegisterComponent[transformComponent](world, &ComponentConfig[transformComp ```go entityId := world.CreateEntity("entityName") ``` +**Important**: the entity name MUST be unique. + - Add the component to the entity ```go component := volt.ConfigureComponent[transformComponent](&scene.World, transformConfiguration{x: 1.0, y: 2.0, z: 3.0})