diff --git a/README.md b/README.md index ce44ff6..2d8d218 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ There is many ways to write an ECS, and Volt is based on the Archetype paradigm. ## Knowledge ### Entity An entity is the end object in a game (e.g. a character). It is only defined by -its identifier called EntityId. This identifier is randomly generated, its type uint64 avoiding to generate twice the same id. -It is also required to set a name for each entity, only used to easily retrieve them when required. +its identifier called EntityId. This identifier is generated, its type uint64 avoiding to generate twice the same id. +When an entity is removed, this identifier can be used again for a new one. Looking at the benchmark, a scene can handle between 100.000 to 1.000.000 depending on your machine and the complexity of the project. But of course, the lower the better, as it will allow the project to run on slower computers. @@ -82,14 +82,14 @@ volt.RegisterComponent[transformComponent](world, &ComponentConfig[transformComp ``` - Create the entity ```go -entityId := world.CreateEntity("entityName") +entityId := world.CreateEntity() ``` -**Important**: the entity name MUST be unique. +**Important**: the entity will receive a unique identifier. When the entity is removed, this id can be used again and assigned to a new entity. - Add the component to the entity ```go -component := volt.ConfigureComponent[transformComponent](&scene.World, transformConfiguration{x: 1.0, y: 2.0, z: 3.0}) -volt.AddComponent(&scene.World, entity, component) +component := volt.ConfigureComponent[transformComponent](&world, transformConfiguration{x: 1.0, y: 2.0, z: 3.0}) +volt.AddComponent(&world, entity, component) ``` - Remove the component to the entity ```go @@ -102,15 +102,6 @@ if err != nil { ```go world.RemoveEntity(entityId) ``` -## Searching for an entity -- Knowing an entity by its name, you can get its identifier: -```go -entityId := world.SearchEntity("entityName") -``` -- The reversed search is also possible, fetching its name by its idenfier: -```go -entityName := world.GetEntityName(entityId) -``` ## Queries The most powerful feature is the possibility to query entities with a given set of Components. @@ -189,12 +180,71 @@ world.HasTag(TAG_STATIC_ID, entityId) world.RemoveTag(TAG_STATIC_ID, entityId) ``` +## Events +The lifecycle (creation/deletion) of entities and components can trigger events. +You can configure a callback function for each of these events, to execute your custom code: +```go +world := volt.CreateWorld(100) +world.SetEntityAddedFn(func(entityId volt.EntityId) { + fmt.Println("A new entity has been created", entityId) +}) +world.SetEntityRemovedFn(func(entityId volt.EntityId) { + fmt.Println("An entity has been deleted", entityId) +}) +world.SetComponentAddedFn(func(entityId volt.EntityId, componentId volt.ComponentId) { + fmt.Println("The component", componentId, "is attached to the entity", entityId) +}) +world.SetComponentRemovedFn(func(entityId volt.EntityId, componentId volt.ComponentId) { +fmt.Println("The component", componentId, "is removed from the entity", entityId) +}) +``` + +## Naming entities +Volt managed the naming of entities up to the version 1.6.0. For performances reasons, this feature is removed from the v1.7.0+. +You now have to keep track of the names by yourself in your application: +- Having a simple map[name string]volt.EntityId, you can react to the events and register these. Keep in mind that if your scene has a lot +of entities, it will probably have a huge impact on the garbage collector. +- Add a MetadataComponent. To fetch an entity by its name can be very slow, so you probably do not want to name all your entities. For example: +```go +const MetadataComponentId = 0 + +type MetadataComponent struct { + Name string +} + +func (MetadataComponent MetadataComponent) GetComponentId() volt.ComponentId { + return MetadataComponentId +} +volt.RegisterComponent[MetadataComponent](&world, &volt.ComponentConfig[MetadataComponent]{BuilderFn: func(component any, configuration any) {}}) + +func GetEntityName(world *volt.World, entityId volt.EntityId) string { + if world.HasComponents(entityId, MetadataComponentId) { + metadata := volt.GetComponent[MetadataComponent](world, entityId) + + return metadata.Name + } + + return "" +} + +func (scene *Scene) SearchEntity(name string) volt.EntityId { + q := volt.CreateQuery1[MetadataComponent](&world, volt.QueryConfiguration{}) + for result := range q.Foreach(nil) { + if result.A.Name == name { + return result.EntityId + } + } + + return 0 +} +``` + ## Benchmark Few ECS tools exist for Go. Arche and unitoftime/ecs are probably the most looked at, and the most optimized. In the benchmark folder, this module is compared to both of them. -- Go - v1.24.0 -- Volt - v1.5.0 +- Go - v1.25.3 +- Volt - v1.7.0 - [Arche - v0.15.3](https://github.com/mlange-42/arche) - [UECS - v0.0.3](https://github.com/unitoftime/ecs) @@ -207,19 +257,19 @@ 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 | +| BenchmarkCreateEntityArche-16 | 171 | 7138387 | 11096954 | 61 | +| BenchmarkIterateArche-16 | 2798 | 429744 | 354 | 4 | +| BenchmarkAddArche-16 | 253 | 4673362 | 122153 | 100000 | +| BenchmarkRemoveArche-16 | 247 | 4840772 | 100000 | 100000 | +| BenchmarkCreateEntityUECS-16 | 27 | 38852089 | 49119503 | 200146 | +| BenchmarkIterateUECS-16 | 4892 | 235333 | 128 | 3 | +| BenchmarkAddUECS-16 | 28 | 38982533 | 4721942 | 100005 | +| BenchmarkRemoveUECS-16 | 30 | 40290316 | 3336712 | 100000 | +| BenchmarkCreateEntityVolt-16 | 63 | 18836136 | 35181458 | 100101 | +| BenchmarkIterateVolt-16 | 3619 | 337764 | 256 | 8 | +| BenchmarkIterateConcurrentlyVolt-16 | 9164 | 121653 | 3324 | 91 | +| BenchmarkAddVolt-16 | 103 | 11379690 | 4313182 | 300000 | +| BenchmarkRemoveVolt-16 | 146 | 7647252 | 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. @@ -236,8 +286,6 @@ The creator and maintainer of Arche has published more complex benchmarks availa https://github.com/mlange-42/go-ecs-benchmarks ## What is to come next ? -- Tags (zero sized types) are useful to query entities with specific features: for example, in a renderer, to get only the entities with the boolean isCulled == false. -This would hugely reduce the loops operations in some scenarios. Currently we can use the filters on the iterators, but it does not avoid the fact that every entity (with the given components) is looped by the renderer. - For now the system is not designed to manage writes on a concurrent way: it means it is not safe to add/remove components in queries using multiples threads/goroutines. I need to figure out how to implement this, though I never met the need for this feature myself. diff --git a/benchmark/volt_test.go b/benchmark/volt_test.go index 0c2f707..4effaf6 100644 --- a/benchmark/volt_test.go +++ b/benchmark/volt_test.go @@ -1,10 +1,10 @@ package benchmark import ( - "github.com/akmonengine/volt" "math/rand/v2" - "strconv" "testing" + + "github.com/akmonengine/volt" ) func BenchmarkCreateEntityVolt(b *testing.B) { @@ -13,8 +13,8 @@ func BenchmarkCreateEntityVolt(b *testing.B) { volt.RegisterComponent[testTransform](world, &volt.ComponentConfig[testTransform]{}) volt.RegisterComponent[testTag](world, &volt.ComponentConfig[testTag]{}) - for j := range ENTITIES_COUNT { - volt.CreateEntityWithComponents2(world, strconv.Itoa(j), + for range ENTITIES_COUNT { + volt.CreateEntityWithComponents2(world, testTransform{ x: rand.Float64() * 100, y: rand.Float64() * 100, @@ -34,7 +34,7 @@ func BenchmarkIterateVolt(b *testing.B) { volt.RegisterComponent[testTag](world, &volt.ComponentConfig[testTag]{}) for i := 0; i < ENTITIES_COUNT; i++ { - id := world.CreateEntity(strconv.Itoa(i)) + id := world.CreateEntity() volt.AddComponent[testTransform](world, id, testTransform{}) volt.AddComponent[testTag](world, id, testTag{}) } @@ -55,7 +55,7 @@ func BenchmarkIterateConcurrentlyVolt(b *testing.B) { volt.RegisterComponent[testTag](world, &volt.ComponentConfig[testTag]{}) for i := 0; i < ENTITIES_COUNT; i++ { - id := world.CreateEntity(strconv.Itoa(i)) + id := world.CreateEntity() volt.AddComponent[testTransform](world, id, testTransform{}) volt.AddComponent[testTag](world, id, testTag{}) } @@ -84,8 +84,8 @@ func BenchmarkAddVolt(b *testing.B) { volt.RegisterComponent[testTag](world, &volt.ComponentConfig[testTag]{}) entities := make([]volt.EntityId, 0, ENTITIES_COUNT) - for j := range ENTITIES_COUNT { - entityId := world.CreateEntity(strconv.Itoa(j)) + for range ENTITIES_COUNT { + entityId := world.CreateEntity() volt.AddComponent(world, entityId, testTag{}) entities = append(entities, entityId) } @@ -113,8 +113,8 @@ func BenchmarkRemoveVolt(b *testing.B) { volt.RegisterComponent[testTag](world, &volt.ComponentConfig[testTag]{}) entities := make([]volt.EntityId, 0, ENTITIES_COUNT) - for j := range ENTITIES_COUNT { - entityId := world.CreateEntity(strconv.Itoa(j)) + for range ENTITIES_COUNT { + entityId := world.CreateEntity() volt.AddComponent(world, entityId, testTag{}) entities = append(entities, entityId) } diff --git a/component.go b/component.go index 5775ea8..af0866b 100644 --- a/component.go +++ b/component.go @@ -46,10 +46,10 @@ func ConfigureComponent[T ComponentInterface](world *World, conf any) T { // - the entity has the component // - an internal error occurs func AddComponent[T ComponentInterface](world *World, entityId EntityId, component T) error { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("entity %v does not exist", entityId) } + entityRecord := world.entities[entityId] componentId := component.GetComponentId() if world.hasComponents(entityRecord, componentId) { @@ -76,10 +76,10 @@ func AddComponent[T ComponentInterface](world *World, entityId EntityId, compone // // This solution is faster than an atomic solution. func AddComponents2[A, B ComponentInterface](world *World, entityId EntityId, a A, b B) error { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("entity %v does not exist", entityId) } + entityRecord := world.entities[entityId] return addComponents2(world, entityRecord, a, b) } @@ -112,10 +112,10 @@ func addComponents2[A, B ComponentInterface](world *World, entityRecord entityRe // // This solution is faster than an atomic solution. func AddComponents3[A, B, C ComponentInterface](world *World, entityId EntityId, a A, b B, c C) error { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("entity %v does not exist", entityId) } + entityRecord := world.entities[entityId] return addComponents3(world, entityRecord, a, b, c) } @@ -150,10 +150,10 @@ func addComponents3[A, B, C ComponentInterface](world *World, entityRecord entit // // This solution is faster than an atomic solution. func AddComponents4[A, B, C, D ComponentInterface](world *World, entityId EntityId, a A, b B, c C, d D) error { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("entity %v does not exist", entityId) } + entityRecord := world.entities[entityId] return addComponents4(world, entityRecord, a, b, c, d) } @@ -189,10 +189,10 @@ func addComponents4[A, B, C, D ComponentInterface](world *World, entityRecord en // // This solution is faster than an atomic solution. func AddComponents5[A, B, C, D, E ComponentInterface](world *World, entityId EntityId, a A, b B, c C, d D, e E) error { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("entity %v does not exist", entityId) } + entityRecord := world.entities[entityId] return addComponents5(world, entityRecord, a, b, c, d, e) } @@ -229,10 +229,10 @@ func addComponents5[A, B, C, D, E ComponentInterface](world *World, entityRecord // // This solution is faster than an atomic solution. func AddComponents6[A, B, C, D, E, F ComponentInterface](world *World, entityId EntityId, a A, b B, c C, d D, e E, f F) error { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("entity %v does not exist", entityId) } + entityRecord := world.entities[entityId] return addComponents6(world, entityRecord, a, b, c, d, e, f) } @@ -270,10 +270,10 @@ func addComponents6[A, B, C, D, E, F ComponentInterface](world *World, entityRec // // This solution is faster than an atomic solution. func AddComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, entityId EntityId, a A, b B, c C, d D, e E, f F, g G) error { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("entity %v does not exist", entityId) } + entityRecord := world.entities[entityId] return addComponents7(world, entityRecord, a, b, c, d, e, f, g) } @@ -312,10 +312,10 @@ func addComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, entity // // This solution is faster than an atomic solution. func AddComponents8[A, B, C, D, E, F, G, H ComponentInterface](world *World, entityId EntityId, a A, b B, c C, d D, e E, f F, g G, h H) error { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("entity %v does not exist", entityId) } + entityRecord := world.entities[entityId] return addComponents8(world, entityRecord, a, b, c, d, e, f, g, h) } @@ -354,10 +354,10 @@ func addComponents8[A, B, C, D, E, F, G, H ComponentInterface](world *World, ent // - the componentId is not registered in the World // - an internal error occurs func (world *World) AddComponent(entityId EntityId, componentId ComponentId, conf any) error { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("entity %v does not exist", entityId) } + entityRecord := world.entities[entityId] if world.hasComponents(entityRecord, componentId) { return fmt.Errorf("the entity %d already owns the component %d", entityId, componentId) @@ -385,10 +385,10 @@ func (world *World) AddComponent(entityId EntityId, componentId ComponentId, con // - the componentsIds are not registered in the World // - an internal error occurs func (world *World) AddComponents(entityId EntityId, componentsIdsConfs ...ComponentIdConf) error { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("entity %v does not exist", entityId) } + entityRecord := world.entities[entityId] var componentsIds []ComponentId for _, componentIdConf := range componentsIdsConfs { @@ -423,10 +423,10 @@ func RemoveComponent[T ComponentInterface](world *World, entityId EntityId) erro var t T componentId := t.GetComponentId() - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("entity %v does not exist", entityId) } + entityRecord := world.entities[entityId] if !world.hasComponents(entityRecord, componentId) { return fmt.Errorf("the entity %d doesn't own the component %d", entityId, componentId) @@ -486,10 +486,10 @@ func removeComponent(world *World, s storage, entityRecord entityRecord, compone // // It returns false if at least one ComponentId is not owned. func (world *World) HasComponents(entityId EntityId, componentsIds ...ComponentId) bool { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return false } + entityRecord := world.entities[entityId] return world.hasComponents(entityRecord, componentsIds...) } diff --git a/component_test.go b/component_test.go index 134b2b2..6eb493b 100644 --- a/component_test.go +++ b/component_test.go @@ -1,7 +1,6 @@ package volt import ( - "fmt" "testing" ) @@ -14,7 +13,6 @@ const ( testComponent6Id testComponent7Id testComponent8Id - testComponent9Id ) type testComponent struct { @@ -91,7 +89,7 @@ func TestAddComponent(t *testing.T) { RegisterComponent[testComponent2](world, &ComponentConfig[testComponent2]{BuilderFn: func(component any, configuration any) {}}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent(world, entities[i], testComponent1{}) if err != nil { @@ -122,7 +120,7 @@ func TestAddComponents(t *testing.T) { RegisterComponent[testComponent5](world, &ComponentConfig[testComponent5]{BuilderFn: func(component any, configuration any) {}}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := world.AddComponents(entities[i], ComponentIdConf{ComponentId: testComponent1Id}, ComponentIdConf{ComponentId: testComponent2Id}, ComponentIdConf{ComponentId: testComponent3Id}, ComponentIdConf{ComponentId: testComponent4Id}, ComponentIdConf{ComponentId: testComponent5Id}) if err != nil { @@ -160,7 +158,7 @@ func TestGetComponent(t *testing.T) { RegisterComponent[testComponent2](world, &ComponentConfig[testComponent2]{BuilderFn: func(component any, configuration any) {}}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent(world, entities[i], testComponent1{}) if err != nil { t.Errorf("%s", err.Error()) @@ -189,7 +187,7 @@ func TestRemoveComponent(t *testing.T) { RegisterComponent[testComponent2](world, &ComponentConfig[testComponent2]{BuilderFn: func(component any, configuration any) {}}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent(world, entities[i], testComponent1{testComponent{x: i, y: i, z: i}}) if err != nil { t.Errorf("%s", err.Error()) diff --git a/pool.go b/pool.go new file mode 100644 index 0000000..b418877 --- /dev/null +++ b/pool.go @@ -0,0 +1,27 @@ +package volt + +type pool struct { + ids []EntityId + next EntityId +} + +func (pool *pool) Get() EntityId { + var entityId EntityId + if len(pool.ids) > 0 { + entityId = pool.ids[len(pool.ids)-1] + pool.ids = pool.ids[:len(pool.ids)-1] + } else { + entityId = pool.next + pool.next++ + } + + return entityId +} + +func (pool *pool) Recycle(id EntityId) { + pool.ids = append(pool.ids, id) +} + +func (pool *pool) Count() int { + return len(pool.ids) +} diff --git a/query_test.go b/query_test.go index 53fd66e..361973f 100644 --- a/query_test.go +++ b/query_test.go @@ -1,7 +1,6 @@ package volt import ( - "fmt" "slices" "testing" ) @@ -26,7 +25,7 @@ func TestQuery1_filter(t *testing.T) { RegisterComponent[testComponent2](world, &ComponentConfig[testComponent2]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() if i%2 == 0 { err := AddComponent[testComponent1](world, entityId, testComponent1{}) @@ -53,7 +52,7 @@ func TestQuery1_Count(t *testing.T) { RegisterComponent[testComponent1](world, &ComponentConfig[testComponent1]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() err := AddComponent[testComponent1](world, entityId, testComponent1{}) if err != nil { @@ -73,7 +72,7 @@ func TestQuery1_Foreach(t *testing.T) { RegisterComponent[testComponent1](world, &ComponentConfig[testComponent1]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponent[testComponent1](world, entityId, testComponent1{}) @@ -106,7 +105,7 @@ func TestQuery1_ForeachChannel(t *testing.T) { RegisterComponent[testComponent1](world, &ComponentConfig[testComponent1]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponent[testComponent1](world, entityId, testComponent1{}) @@ -160,7 +159,7 @@ func TestQuery2_filter(t *testing.T) { RegisterComponent[testComponent3](world, &ComponentConfig[testComponent3]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() if i%2 == 0 { err := AddComponents3[testComponent1, testComponent2, testComponent3](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}) @@ -188,7 +187,7 @@ func TestQuery2_Count(t *testing.T) { RegisterComponent[testComponent2](world, &ComponentConfig[testComponent2]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() err := AddComponents2[testComponent1, testComponent2](world, entityId, testComponent1{}, testComponent2{}) if err != nil { t.Errorf("%s", err.Error()) @@ -208,7 +207,7 @@ func TestQuery2_Foreach(t *testing.T) { RegisterComponent[testComponent2](world, &ComponentConfig[testComponent2]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents2[testComponent1, testComponent2](world, entityId, testComponent1{}, testComponent2{}) @@ -242,7 +241,7 @@ func TestQuery2_ForeachChannel(t *testing.T) { RegisterComponent[testComponent2](world, &ComponentConfig[testComponent2]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents2[testComponent1, testComponent2](world, entityId, testComponent1{}, testComponent2{}) @@ -298,7 +297,7 @@ func TestQuery3_filter(t *testing.T) { RegisterComponent[testComponent4](world, &ComponentConfig[testComponent4]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() if i%2 == 0 { err := AddComponents4[testComponent1, testComponent2, testComponent3, testComponent4](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}) @@ -327,7 +326,7 @@ func TestQuery3_Count(t *testing.T) { RegisterComponent[testComponent3](world, &ComponentConfig[testComponent3]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() err := AddComponents3[testComponent1, testComponent2, testComponent3](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}) if err != nil { t.Errorf("%s", err.Error()) @@ -349,7 +348,7 @@ func TestQuery3_Foreach(t *testing.T) { RegisterComponent[testComponent3](world, &ComponentConfig[testComponent3]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents3[testComponent1, testComponent2, testComponent3](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}) @@ -384,7 +383,7 @@ func TestQuery3_ForeachChannel(t *testing.T) { RegisterComponent[testComponent3](world, &ComponentConfig[testComponent3]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents3[testComponent1, testComponent2, testComponent3](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}) @@ -442,7 +441,7 @@ func TestQuery4_filter(t *testing.T) { RegisterComponent[testComponent5](world, &ComponentConfig[testComponent5]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() if i%2 == 0 { err := AddComponents5[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}) @@ -472,7 +471,7 @@ func TestQuery4_Count(t *testing.T) { RegisterComponent[testComponent4](world, &ComponentConfig[testComponent4]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() err := AddComponents4[testComponent1, testComponent2, testComponent3, testComponent4](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}) if err != nil { t.Errorf("%s", err.Error()) @@ -494,7 +493,7 @@ func TestQuery4_Foreach(t *testing.T) { RegisterComponent[testComponent4](world, &ComponentConfig[testComponent4]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents4[testComponent1, testComponent2, testComponent3, testComponent4](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}) @@ -530,7 +529,7 @@ func TestQuery4_ForeachChannel(t *testing.T) { RegisterComponent[testComponent4](world, &ComponentConfig[testComponent4]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents4[testComponent1, testComponent2, testComponent3, testComponent4](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}) @@ -590,7 +589,7 @@ func TestQuery5_filter(t *testing.T) { RegisterComponent[testComponent6](world, &ComponentConfig[testComponent6]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() if i%2 == 0 { err := AddComponents6[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}) @@ -621,7 +620,7 @@ func TestQuery5_Count(t *testing.T) { RegisterComponent[testComponent5](world, &ComponentConfig[testComponent5]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() err := AddComponents5[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}) if err != nil { t.Errorf("%s", err.Error()) @@ -644,7 +643,7 @@ func TestQuery5_Foreach(t *testing.T) { RegisterComponent[testComponent5](world, &ComponentConfig[testComponent5]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents5[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}) @@ -681,7 +680,7 @@ func TestQuery5_ForeachChannel(t *testing.T) { RegisterComponent[testComponent5](world, &ComponentConfig[testComponent5]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents5[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}) @@ -743,7 +742,7 @@ func TestQuery6_filter(t *testing.T) { RegisterComponent[testComponent7](world, &ComponentConfig[testComponent7]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() if i%2 == 0 { err := AddComponents7[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6, testComponent7](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}) @@ -775,7 +774,7 @@ func TestQuery6_Count(t *testing.T) { RegisterComponent[testComponent6](world, &ComponentConfig[testComponent6]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() err := AddComponents6[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}) if err != nil { t.Errorf("%s", err.Error()) @@ -799,7 +798,7 @@ func TestQuery6_Foreach(t *testing.T) { RegisterComponent[testComponent6](world, &ComponentConfig[testComponent6]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents6[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}) @@ -837,7 +836,7 @@ func TestQuery6_ForeachChannel(t *testing.T) { RegisterComponent[testComponent6](world, &ComponentConfig[testComponent6]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents6[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}) @@ -901,7 +900,7 @@ func TestQuery7_filter(t *testing.T) { RegisterComponent[testComponent8](world, &ComponentConfig[testComponent8]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() if i%2 == 0 { err := AddComponents8[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6, testComponent7, testComponent8](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}, testComponent8{}) @@ -934,7 +933,7 @@ func TestQuery7_Count(t *testing.T) { RegisterComponent[testComponent7](world, &ComponentConfig[testComponent7]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() err := AddComponents7[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6, testComponent7](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}) if err != nil { t.Errorf("%s", err.Error()) @@ -960,7 +959,7 @@ func TestQuery7_Foreach(t *testing.T) { RegisterComponent[testComponent7](world, &ComponentConfig[testComponent7]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents7[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6, testComponent7](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}) @@ -1000,7 +999,7 @@ func TestQuery7_ForeachChannel(t *testing.T) { RegisterComponent[testComponent7](world, &ComponentConfig[testComponent7]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents7[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6, testComponent7](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}) @@ -1065,7 +1064,7 @@ func TestQuery8_filter(t *testing.T) { RegisterComponent[testComponent8](world, &ComponentConfig[testComponent8]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() err := AddComponents8[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6, testComponent7, testComponent8](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}, testComponent8{}) if err != nil { @@ -1092,7 +1091,7 @@ func TestQuery8_Count(t *testing.T) { RegisterComponent[testComponent8](world, &ComponentConfig[testComponent8]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() err := AddComponents8[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6, testComponent7, testComponent8](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}, testComponent8{}) if err != nil { t.Errorf("%s", err.Error()) @@ -1119,7 +1118,7 @@ func TestQuery8_Foreach(t *testing.T) { RegisterComponent[testComponent8](world, &ComponentConfig[testComponent8]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents8[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6, testComponent7, testComponent8](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}, testComponent8{}) @@ -1159,7 +1158,7 @@ func TestQuery8_ForeachChannel(t *testing.T) { RegisterComponent[testComponent8](world, &ComponentConfig[testComponent8]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entityId := world.CreateEntity(fmt.Sprint(i)) + entityId := world.CreateEntity() entities = append(entities, entityId) err := AddComponents8[testComponent1, testComponent2, testComponent3, testComponent4, testComponent5, testComponent6, testComponent7, testComponent8](world, entityId, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}, testComponent8{}) diff --git a/register.go b/register.go index dda9011..dca8abf 100644 --- a/register.go +++ b/register.go @@ -48,7 +48,7 @@ func (componentConfig *ComponentConfig[T]) builderFn(component any, configuratio } } -type ComponentsRegister map[ComponentId]ComponentConfigInterface +type ComponentsRegister []ComponentConfigInterface // ComponentBuilder is the function called to set the properties of a given component. // @@ -61,7 +61,10 @@ type ComponentBuilder func(component any, configuration any) func RegisterComponent[T ComponentInterface](world *World, config ComponentConfigInterface) { var t T if world.componentsRegistry == nil { - world.componentsRegistry = make(ComponentsRegister) + world.componentsRegistry = make(ComponentsRegister, TAGS_INDICES) + for i := range TAGS_INDICES { + world.componentsRegistry[i] = nil + } } config.setComponent(t) @@ -70,11 +73,9 @@ func RegisterComponent[T ComponentInterface](world *World, config ComponentConfi } func (world *World) getConfigByComponentId(componentId ComponentId) (ComponentConfigInterface, error) { - for _, config := range world.componentsRegistry { - if config.getComponentId() == componentId { - return config, nil - } + if world.componentsRegistry[componentId] == nil { + return nil, fmt.Errorf("componentConfiguration not found for %d", componentId) } - return nil, fmt.Errorf("componentConfiguration not found for %d", componentId) + return world.componentsRegistry[componentId], nil } diff --git a/register_test.go b/register_test.go index d9943a1..e1b8738 100644 --- a/register_test.go +++ b/register_test.go @@ -8,7 +8,7 @@ func TestComponentConfig_addComponent(t *testing.T) { world := CreateWorld(16) RegisterComponent[testComponent1](world, &ComponentConfig[testComponent1]{BuilderFn: func(component any, configuration any) {}}) - entityId := world.CreateEntity("entity") + entityId := world.CreateEntity() componentRegistry, _ := world.getConfigByComponentId(testComponent1Id) err := componentRegistry.addComponent(world, entityId, testComponent1Configuration{}) @@ -33,7 +33,7 @@ func TestComponentConfig_builderFn(t *testing.T) { testTransformComponent.z = conf.z }}) - entityId := world.CreateEntity("entity") + entityId := world.CreateEntity() componentRegistry, _ := world.getConfigByComponentId(testComponent1Id) err := componentRegistry.addComponent(world, entityId, testComponent1Configuration{ testComponent{x: 1.0, y: 2.0, z: 3.0}, diff --git a/storage.go b/storage.go index a641c79..c324427 100644 --- a/storage.go +++ b/storage.go @@ -10,7 +10,7 @@ func getStorage[T ComponentInterface](world *World) *ComponentsStorage[T] { var t T componentId := t.GetComponentId() - if _, ok := world.componentsRegistry[componentId]; !ok { + if world.componentsRegistry[componentId] == nil { return nil } diff --git a/tag.go b/tag.go index 2a3aaba..ca351a5 100644 --- a/tag.go +++ b/tag.go @@ -26,14 +26,10 @@ func (world *World) AddTag(tagId TagId, entityId EntityId) error { entityRecord := world.entities[entityId] archetype := world.getNextArchetype(entityRecord, tagId) - if entityRecord.Id == 0 { + oldArchetype := world.getArchetype(entityRecord) + if archetype.Id != oldArchetype.Id { + moveComponentsToArchetype(world, entityRecord, oldArchetype, archetype) world.setArchetype(entityRecord, archetype) - } else { - oldArchetype := world.getArchetype(entityRecord) - if archetype.Id != oldArchetype.Id { - moveComponentsToArchetype(world, entityRecord, oldArchetype, archetype) - world.setArchetype(entityRecord, archetype) - } } return nil @@ -41,10 +37,10 @@ func (world *World) AddTag(tagId TagId, entityId EntityId) error { // HasTag returns a boolean, to check if an EntityId owns a Tag. func (world *World) HasTag(tagId TagId, entityId EntityId) bool { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return false } + entityRecord := world.entities[entityId] return world.hasComponents(entityRecord, tagId) } @@ -54,10 +50,10 @@ func (world *World) HasTag(tagId TagId, entityId EntityId) bool { // - The entity does not exists. // - The entity already owns the Tag. func (world *World) RemoveTag(tagId TagId, entityId EntityId) error { - entityRecord, ok := world.entities[entityId] - if !ok { + if int(entityId) >= len(world.entities) { return fmt.Errorf("the entity %d does not exist", entityId) } + entityRecord := world.entities[entityId] if !world.HasTag(tagId, entityId) { return fmt.Errorf("the entity %d doesn't own the tag %d", entityId, tagId) diff --git a/tag_test.go b/tag_test.go index b00bcad..023f35a 100644 --- a/tag_test.go +++ b/tag_test.go @@ -1,7 +1,6 @@ package volt import ( - "fmt" "slices" "testing" ) @@ -17,7 +16,7 @@ func TestAddTag(t *testing.T) { RegisterComponent[testComponent1](world, &ComponentConfig[testComponent1]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent[testComponent1](world, entities[i], testComponent1{}) if err != nil { @@ -70,7 +69,7 @@ func TestHasTag(t *testing.T) { RegisterComponent[testComponent1](world, &ComponentConfig[testComponent1]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent[testComponent1](world, entities[i], testComponent1{}) if err != nil { @@ -102,7 +101,7 @@ func TestHasTag(t *testing.T) { } } - if world.HasTag(TAG_2, 0) { + if world.HasTag(TAG_2, TEST_ENTITY_NUMBER) { t.Errorf("entity %d does not exist, it should not have the tag %d", 0, TAG_2) } } @@ -113,7 +112,7 @@ func TestRemoveTag(t *testing.T) { RegisterComponent[testComponent1](world, &ComponentConfig[testComponent1]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent[testComponent1](world, entities[i], testComponent1{}) if err != nil { @@ -161,7 +160,7 @@ func TestTag2(t *testing.T) { RegisterComponent[testComponent2](world, &ComponentConfig[testComponent2]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent[testComponent1](world, entities[i], testComponent1{}) if err != nil { @@ -210,7 +209,7 @@ func TestTag3(t *testing.T) { RegisterComponent[testComponent3](world, &ComponentConfig[testComponent3]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent[testComponent1](world, entities[i], testComponent1{}) if err != nil { @@ -265,7 +264,7 @@ func TestTag4(t *testing.T) { RegisterComponent[testComponent4](world, &ComponentConfig[testComponent4]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent[testComponent1](world, entities[i], testComponent1{}) if err != nil { @@ -325,7 +324,7 @@ func TestTag5(t *testing.T) { RegisterComponent[testComponent5](world, &ComponentConfig[testComponent5]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent[testComponent1](world, entities[i], testComponent1{}) if err != nil { @@ -390,7 +389,7 @@ func TestTag6(t *testing.T) { RegisterComponent[testComponent6](world, &ComponentConfig[testComponent6]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent[testComponent1](world, entities[i], testComponent1{}) if err != nil { @@ -460,7 +459,7 @@ func TestTag7(t *testing.T) { RegisterComponent[testComponent7](world, &ComponentConfig[testComponent7]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent[testComponent1](world, entities[i], testComponent1{}) if err != nil { @@ -535,7 +534,7 @@ func TestTag8(t *testing.T) { RegisterComponent[testComponent8](world, &ComponentConfig[testComponent8]{}) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() err := AddComponent[testComponent1](world, entities[i], testComponent1{}) if err != nil { diff --git a/world.go b/world.go index ace9694..0769606 100644 --- a/world.go +++ b/world.go @@ -2,7 +2,6 @@ package volt import ( - "hash/fnv" "slices" ) @@ -36,19 +35,14 @@ type entityRecord struct { Id EntityId archetypeId archetypeId key int - name entityName } -// entityName is a string transformed to byte array. -// -// It avoids the garbage collector to analyze this data constantly, -// at the price of a fixed data size. -type entityName = string -type entities map[EntityId]entityRecord +type entities []entityRecord // World representation, container of all the data related to entities and their Components. type World struct { componentsRegistry ComponentsRegister + pool pool entities entities archetypes []archetype storage []storage @@ -64,6 +58,7 @@ type World struct { // It preallocates initialCapacity in memory. func CreateWorld(initialCapacity int) *World { world := &World{ + pool: pool{}, entities: make(entities, initialCapacity), archetypes: make([]archetype, 0, 1024), storage: make([]storage, TAGS_INDICES), @@ -100,31 +95,32 @@ func (world *World) SetComponentRemovedFn(componentRemovedFn func(entityId Entit // 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 - } - - entityId := hashEntityName(name) +func (world *World) CreateEntity() EntityId { + entityId := world.pool.Get() archetype := world.getArchetypeForComponentsIds() - entityRecord := entityRecord{ - Id: entityId, - name: name, - } - world.entities[entityId] = entityRecord + entityRecord := entityRecord{Id: entityId} + world.addEntity(entityRecord) world.setArchetype(entityRecord, archetype) return entityId } +func (world *World) addEntity(entityRecord entityRecord) { + if int(entityRecord.Id) < len(world.entities) { + world.entities[entityRecord.Id] = entityRecord + } else { + world.entities = append(world.entities, entityRecord) + } +} + // 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) { - entityId := hashEntityName(name) +func CreateEntityWithComponents2[A, B ComponentInterface](world *World, a A, b B) (EntityId, error) { + entityId := world.pool.Get() - entityRecord := entityRecord{Id: entityId, name: name} - world.entities[entityId] = entityRecord + entityRecord := entityRecord{Id: entityId} + world.addEntity(entityRecord) err := addComponents2(world, entityRecord, a, b) if err != nil { @@ -137,11 +133,11 @@ func CreateEntityWithComponents2[A, B ComponentInterface](world *World, name str // CreateEntityWithComponents3 creates an entity in World; // // 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) { - entityId := hashEntityName(name) +func CreateEntityWithComponents3[A, B, C ComponentInterface](world *World, a A, b B, c C) (EntityId, error) { + entityId := world.pool.Get() - entityRecord := entityRecord{Id: entityId, name: name} - world.entities[entityId] = entityRecord + entityRecord := entityRecord{Id: entityId} + world.addEntity(entityRecord) err := addComponents3(world, entityRecord, a, b, c) if err != nil { @@ -154,11 +150,11 @@ func CreateEntityWithComponents3[A, B, C ComponentInterface](world *World, name // CreateEntityWithComponents4 creates an entity in World; // // 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) { - entityId := hashEntityName(name) +func CreateEntityWithComponents4[A, B, C, D ComponentInterface](world *World, a A, b B, c C, d D) (EntityId, error) { + entityId := world.pool.Get() - entityRecord := entityRecord{Id: entityId, name: name} - world.entities[entityId] = entityRecord + entityRecord := entityRecord{Id: entityId} + world.addEntity(entityRecord) err := addComponents4(world, entityRecord, a, b, c, d) if err != nil { @@ -171,11 +167,11 @@ func CreateEntityWithComponents4[A, B, C, D ComponentInterface](world *World, na // CreateEntityWithComponents5 creates an entity in World; // // 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) { - entityId := hashEntityName(name) +func CreateEntityWithComponents5[A, B, C, D, E ComponentInterface](world *World, a A, b B, c C, d D, e E) (EntityId, error) { + entityId := world.pool.Get() - entityRecord := entityRecord{Id: entityId, name: name} - world.entities[entityId] = entityRecord + entityRecord := entityRecord{Id: entityId} + world.addEntity(entityRecord) err := addComponents5(world, entityRecord, a, b, c, d, e) if err != nil { @@ -188,10 +184,11 @@ func CreateEntityWithComponents5[A, B, C, D, E ComponentInterface](world *World, // CreateEntityWithComponents6 creates an entity in 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) { - entityId := hashEntityName(name) - entityRecord := entityRecord{Id: entityId, name: name} - world.entities[entityId] = entityRecord +func CreateEntityWithComponents6[A, B, C, D, E, F ComponentInterface](world *World, a A, b B, c C, d D, e E, f F) (EntityId, error) { + entityId := world.pool.Get() + + entityRecord := entityRecord{Id: entityId} + world.addEntity(entityRecord) err := addComponents6(world, entityRecord, a, b, c, d, e, f) if err != nil { @@ -204,10 +201,11 @@ func CreateEntityWithComponents6[A, B, C, D, E, F ComponentInterface](world *Wor // CreateEntityWithComponents7 creates an entity in World; // // 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) { - entityId := hashEntityName(name) - entityRecord := entityRecord{Id: entityId, name: name} - world.entities[entityId] = entityRecord +func CreateEntityWithComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, a A, b B, c C, d D, e E, f F, g G) (EntityId, error) { + entityId := world.pool.Get() + + entityRecord := entityRecord{Id: entityId} + world.addEntity(entityRecord) err := addComponents7(world, entityRecord, a, b, c, d, e, f, g) if err != nil { @@ -220,10 +218,11 @@ func CreateEntityWithComponents7[A, B, C, D, E, F, G ComponentInterface](world * // CreateEntityWithComponents8 creates an entity in 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) { - entityId := hashEntityName(name) - entityRecord := entityRecord{Id: entityId, name: name} - world.entities[entityId] = entityRecord +func CreateEntityWithComponents8[A, B, C, D, E, F, G, H ComponentInterface](world *World, a A, b B, c C, d D, e E, f F, g G, h H) (EntityId, error) { + entityId := world.pool.Get() + + entityRecord := entityRecord{Id: entityId} + world.addEntity(entityRecord) err := addComponents8(world, entityRecord, a, b, c, d, e, f, g, h) if err != nil { @@ -268,48 +267,10 @@ func (world *World) RemoveEntity(entityId EntityId) { world.archetypes[archetype.Id] = archetype } - delete(world.entities, entityId) -} - -// SearchEntity returns the EntityId named by name. -// If not found, returns 0. -func (world *World) SearchEntity(name string) EntityId { - entityId := hashEntityName(name) - if _, ok := world.entities[entityId]; ok { - return entityId - } - - return 0 -} - -// GetEntityName returns the name of an EntityId. -// If not found, returns an empty string. -func (world *World) GetEntityName(entityId EntityId) string { - if entity, ok := world.entities[entityId]; ok { - return entity.name - } - - return "" -} - -// SetEntityName sets the name for an EntityId. -func (world *World) SetEntityName(entityId EntityId, name string) { - entityRecord := world.entities[entityId] - entityRecord.name = name - world.entities[entityId] = entityRecord + world.pool.Recycle(entityId) } // Count returns the number of entities in World. func (world *World) Count() int { - return len(world.entities) -} - -func hashEntityName(name entityName) EntityId { - h := fnv.New64() - _, err := h.Write([]byte(name)) - if err != nil { - return EntityId(0) - } - - return EntityId(h.Sum64()) + return len(world.entities) - world.pool.Count() } diff --git a/world_test.go b/world_test.go index 97f1809..2e726f1 100644 --- a/world_test.go +++ b/world_test.go @@ -1,7 +1,7 @@ package volt import ( - "fmt" + "slices" "testing" ) @@ -25,20 +25,13 @@ func TestWorld_CreateEntity(t *testing.T) { world := CreateWorld(1024) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() } // Check if the entities all exist in the world if len(world.entities) != TEST_ENTITY_NUMBER { t.Errorf("Number of entities created invalid") } - for i := 0; i < TEST_ENTITY_NUMBER; i++ { - _, ok := world.entities[entities[i]] - - if !ok { - t.Errorf("Entity %d was not created properly", entities[i]) - } - } } func TestCreateEntityWithComponents2(t *testing.T) { @@ -46,17 +39,11 @@ func TestCreateEntityWithComponents2(t *testing.T) { RegisterComponent[testComponent1](world, &ComponentConfig[testComponent1]{}) RegisterComponent[testComponent2](world, &ComponentConfig[testComponent2]{}) - entityId, err := CreateEntityWithComponents2(world, "entity1", testComponent1{}, testComponent2{}) + entityId, err := CreateEntityWithComponents2(world, testComponent1{}, testComponent2{}) if err != nil { t.Errorf("%s", err.Error()) } - if id := world.SearchEntity("entity1"); id == 0 { - t.Errorf("Could not find entityName %s", "entity1") - } - if _, ok := world.entities[entityId]; !ok { - t.Errorf("Could not find entityId %d", entityId) - } if component := GetComponent[testComponent1](world, entityId); component == nil { t.Errorf("Could not find component testComponent1 for entityId %d", entityId) } @@ -71,17 +58,11 @@ func TestCreateEntityWithComponents3(t *testing.T) { RegisterComponent[testComponent2](world, &ComponentConfig[testComponent2]{}) RegisterComponent[testComponent3](world, &ComponentConfig[testComponent3]{}) - entityId, err := CreateEntityWithComponents3(world, "entity1", testComponent1{}, testComponent2{}, testComponent3{}) + entityId, err := CreateEntityWithComponents3(world, testComponent1{}, testComponent2{}, testComponent3{}) if err != nil { t.Errorf("%s", err.Error()) } - if id := world.SearchEntity("entity1"); id == 0 { - t.Errorf("Could not find entityName %s", "entity1") - } - if _, ok := world.entities[entityId]; !ok { - t.Errorf("Could not find entityId %d", entityId) - } if component := GetComponent[testComponent1](world, entityId); component == nil { t.Errorf("Could not find component testComponent1 for entityId %d", entityId) } @@ -100,17 +81,11 @@ func TestCreateEntityWithComponents4(t *testing.T) { RegisterComponent[testComponent3](world, &ComponentConfig[testComponent3]{}) RegisterComponent[testComponent4](world, &ComponentConfig[testComponent4]{}) - entityId, err := CreateEntityWithComponents4(world, "entity1", testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}) + entityId, err := CreateEntityWithComponents4(world, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}) if err != nil { t.Errorf("%s", err.Error()) } - if id := world.SearchEntity("entity1"); id == 0 { - t.Errorf("Could not find entityName %s", "entity1") - } - if _, ok := world.entities[entityId]; !ok { - t.Errorf("Could not find entityId %d", entityId) - } if component := GetComponent[testComponent1](world, entityId); component == nil { t.Errorf("Could not find component testComponent1 for entityId %d", entityId) } @@ -133,17 +108,11 @@ func TestCreateEntityWithComponents5(t *testing.T) { RegisterComponent[testComponent4](world, &ComponentConfig[testComponent4]{}) RegisterComponent[testComponent5](world, &ComponentConfig[testComponent5]{}) - entityId, err := CreateEntityWithComponents5(world, "entity1", testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}) + entityId, err := CreateEntityWithComponents5(world, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}) if err != nil { t.Errorf("%s", err.Error()) } - if id := world.SearchEntity("entity1"); id == 0 { - t.Errorf("Could not find entityName %s", "entity1") - } - if _, ok := world.entities[entityId]; !ok { - t.Errorf("Could not find entityId %d", entityId) - } if component := GetComponent[testComponent1](world, entityId); component == nil { t.Errorf("Could not find component testComponent1 for entityId %d", entityId) } @@ -170,17 +139,11 @@ func TestCreateEntityWithComponents6(t *testing.T) { RegisterComponent[testComponent5](world, &ComponentConfig[testComponent5]{}) RegisterComponent[testComponent6](world, &ComponentConfig[testComponent6]{}) - entityId, err := CreateEntityWithComponents6(world, "entity1", testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}) + entityId, err := CreateEntityWithComponents6(world, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}) if err != nil { t.Errorf("%s", err.Error()) } - if id := world.SearchEntity("entity1"); id == 0 { - t.Errorf("Could not find entityName %s", "entity1") - } - if _, ok := world.entities[entityId]; !ok { - t.Errorf("Could not find entityId %d", entityId) - } if component := GetComponent[testComponent1](world, entityId); component == nil { t.Errorf("Could not find component testComponent1 for entityId %d", entityId) } @@ -211,17 +174,11 @@ func TestCreateEntityWithComponents7(t *testing.T) { RegisterComponent[testComponent6](world, &ComponentConfig[testComponent6]{}) RegisterComponent[testComponent7](world, &ComponentConfig[testComponent7]{}) - entityId, err := CreateEntityWithComponents7(world, "entity1", testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}) + entityId, err := CreateEntityWithComponents7(world, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}) if err != nil { t.Errorf("%s", err.Error()) } - if id := world.SearchEntity("entity1"); id == 0 { - t.Errorf("Could not find entityName %s", "entity1") - } - if _, ok := world.entities[entityId]; !ok { - t.Errorf("Could not find entityId %d", entityId) - } if component := GetComponent[testComponent1](world, entityId); component == nil { t.Errorf("Could not find component testComponent1 for entityId %d", entityId) } @@ -256,17 +213,11 @@ func TestCreateEntityWithComponents8(t *testing.T) { RegisterComponent[testComponent7](world, &ComponentConfig[testComponent7]{}) RegisterComponent[testComponent8](world, &ComponentConfig[testComponent8]{}) - entityId, err := CreateEntityWithComponents8(world, "entity1", testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}, testComponent8{}) + entityId, err := CreateEntityWithComponents8(world, testComponent1{}, testComponent2{}, testComponent3{}, testComponent4{}, testComponent5{}, testComponent6{}, testComponent7{}, testComponent8{}) if err != nil { t.Errorf("%s", err.Error()) } - if id := world.SearchEntity("entity1"); id == 0 { - t.Errorf("Could not find entityName %s", "entity1") - } - if _, ok := world.entities[entityId]; !ok { - t.Errorf("Could not find entityId %d", entityId) - } if component := GetComponent[testComponent1](world, entityId); component == nil { t.Errorf("Could not find component testComponent1 for entityId %d", entityId) } @@ -298,79 +249,32 @@ func TestWorld_RemoveEntity(t *testing.T) { world := CreateWorld(1024) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() } // Remove first, last, and a middle one entity from the world world.RemoveEntity(entities[0]) world.RemoveEntity(entities[TEST_ENTITY_NUMBER/2]) world.RemoveEntity(entities[TEST_ENTITY_NUMBER-1]) - // Check the expected world size - if len(world.entities) != (TEST_ENTITY_NUMBER - 3) { - t.Errorf("World size not valid after removal of entities") - } - // Check if the entities are correctly removed of the world for _, id := range []EntityId{0, TEST_ENTITY_NUMBER / 2, TEST_ENTITY_NUMBER - 1} { - if world.SearchEntity(fmt.Sprint(0)) != 0 { + if !slices.Contains(world.pool.ids, id) { t.Errorf("Entity %d was not removed", entities[id]) } } } -func TestWorld_SearchEntity(t *testing.T) { - entities := make([]EntityId, TEST_ENTITY_NUMBER) - world := CreateWorld(1024) - - for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) - } - - // Test searching for existing entities - for entityName, entityId := range entities { - if entityId != world.SearchEntity(fmt.Sprint(entityName)) { - t.Errorf("SearchEntity does not return correct entityId for %s", fmt.Sprint(entityName)) - } - } - - // Test searching for a non-existing entity - if id := world.SearchEntity("nonexistent"); id != 0 { - t.Errorf("world.SearchEntity returned id %d for a non existent entity", id) - } -} - -func TestWorld_GetEntityName(t *testing.T) { - entities := make([]EntityId, TEST_ENTITY_NUMBER) - world := CreateWorld(1024) - - for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) - } - - // Test the names for each entity - for entityName, entityId := range entities { - if fmt.Sprint(entityName) != world.GetEntityName(entityId) { - t.Errorf("world.GetEntityName does not return correct value for id %d", entityId) - } - } - - // Test if none entity return an empty name - if world.GetEntityName(0) != "" { - t.Errorf("world.GetEntityName does not return empty string for entityId 0") - } -} - func TestWorld_Count(t *testing.T) { world := CreateWorld(1024) - if world.Count() != 0 { + if world.Count() != 1024 { t.Errorf("world.Count should return 0 if the world is empty") } entities := make([]EntityId, TEST_ENTITY_NUMBER) for i := 0; i < TEST_ENTITY_NUMBER; i++ { - entities[i] = world.CreateEntity(fmt.Sprint(i)) + entities[i] = world.CreateEntity() } if world.Count() != TEST_ENTITY_NUMBER {