From ae6ef2a17a33a62e6b794526d71b80eb93049b28 Mon Sep 17 00:00:00 2001 From: Ramine Agoune Date: Mon, 17 Mar 2025 17:05:02 +0100 Subject: [PATCH 1/2] feat: improve add performances Re use the entityRecord instead of fetching it in all sub functions --- archetype.go | 4 ++-- component.go | 32 ++++++++++++++++++++------------ register.go | 5 +++-- tag.go | 5 +++-- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/archetype.go b/archetype.go index ab5b8e7..1c6f273 100644 --- a/archetype.go +++ b/archetype.go @@ -75,9 +75,9 @@ func (world *World) getArchetypesForComponentsIds(componentsIds ...ComponentId) return archetypes } -func (world *World) getNextArchetype(entityId EntityId, componentsIds ...ComponentId) *archetype { +func (world *World) getNextArchetype(entityRecord entityRecord, componentsIds ...ComponentId) *archetype { var archetype *archetype - if entityRecord, ok := world.entities[entityId]; !ok { + if entityRecord.archetypeId == 0 { archetype = world.getArchetypeForComponentsIds(componentsIds...) } else { oldArchetype := world.getArchetype(entityRecord) diff --git a/component.go b/component.go index 1e7b796..0ec848e 100644 --- a/component.go +++ b/component.go @@ -36,15 +36,23 @@ func ConfigureComponent[T ComponentInterface](world *World, conf any) T { // AddComponent adds the component T to the existing EntityId. // -// It returns an error if the entity already has the component, or if an internal error occurs. +// It returns an error if: +// - the entity does not exist +// - the entity has the component +// - an internal error occurs func AddComponent[T ComponentInterface](world *World, entityId EntityId, component T) error { componentId := component.GetComponentId() if world.HasComponents(entityId, componentId) { return fmt.Errorf("the entity %d already owns the component %d", entityId, componentId) } - archetype := world.getNextArchetype(entityId, world.getComponentsIds(component)...) - err := addComponentsToArchetype1(world, entityId, archetype, component) + entityRecord, ok := world.entities[entityId] + if !ok { + return fmt.Errorf("entity %v does not exist", entityId) + } + + archetype := world.getNextArchetype(entityRecord, world.getComponentsIds(component)...) + err := addComponentsToArchetype1(world, entityRecord, archetype, component) if err != nil { return fmt.Errorf("the component %d cannot be added to entity %d: %w", componentId, entityId, err) } @@ -56,7 +64,7 @@ func AddComponent[T ComponentInterface](world *World, entityId EntityId, compone // AddComponents2 adds the components A, B to the existing EntityId. // -// It returns an error if the: +// It returns an error if: // - the entity does not exist // - the entity has one of the component // - an internal error occurs @@ -93,7 +101,7 @@ func addComponents2[A, B ComponentInterface](world *World, entityRecord entityRe // AddComponents3 adds the components A, B, C to the existing EntityId. // -// It returns an error if the: +// It returns an error if: // - the entity does not exist // - the entity has one of the component // - an internal error occurs @@ -131,7 +139,7 @@ func addComponents3[A, B, C ComponentInterface](world *World, entityRecord entit // AddComponents4 adds the components A, B, C, D to the existing EntityId. // -// It returns an error if the: +// It returns an error if: // - the entity does not exist // - the entity has one of the component // - an internal error occurs @@ -170,7 +178,7 @@ func addComponents4[A, B, C, D ComponentInterface](world *World, entityRecord en // AddComponents5 adds the components A, B, C, D, E to the existing EntityId. // -// It returns an error if the: +// It returns an error if: // - the entity does not exist // - the entity has one of the component // - an internal error occurs @@ -210,7 +218,7 @@ func addComponents5[A, B, C, D, E ComponentInterface](world *World, entityRecord // AddComponents6 adds the components A, B, C, D, E, F to the existing EntityId. // -// It returns an error if the: +// It returns an error if: // - the entity does not exist // - the entity has one of the component // - an internal error occurs @@ -251,7 +259,7 @@ func addComponents6[A, B, C, D, E, F ComponentInterface](world *World, entityRec // AddComponents7 adds the components A, B, C, D, E, F, G to the existing EntityId. // -// It returns an error if the: +// It returns an error if: // - the entity does not exist // - the entity has one of the component // - an internal error occurs @@ -293,7 +301,7 @@ func addComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, entity // AddComponents8 adds the components A, B, C, D, E, F, G, H to the existing EntityId. // -// It returns an error if the: +// It returns an error if: // - the entity does not exist // - the entity has one of the component // - an internal error occurs @@ -480,7 +488,7 @@ func (world *World) GetComponent(entityId EntityId, componentId ComponentId) (an return s.get(entityRecord.archetypeId, entityRecord.key), nil } -func addComponentsToArchetype1[A ComponentInterface](world *World, entityId EntityId, archetype *archetype, component A) error { +func addComponentsToArchetype1[A ComponentInterface](world *World, entityRecord entityRecord, archetype *archetype, component A) error { storageA := getStorage[A](world) if storageA == nil { @@ -489,7 +497,7 @@ func addComponentsToArchetype1[A ComponentInterface](world *World, entityId Enti } // If the entity has no component, simply add it the archetype - if entityRecord, ok := world.entities[entityId]; !ok { + if entityRecord.archetypeId == 0 { world.setArchetype(entityRecord, archetype) storageA.add(archetype.Id, component) } else { diff --git a/register.go b/register.go index f6b8044..dda9011 100644 --- a/register.go +++ b/register.go @@ -35,8 +35,9 @@ func (componentConfig *ComponentConfig[T]) addComponent(world *World, entityId E var t T componentConfig.builderFn(&t, configuration) - archetype := world.getNextArchetype(entityId, componentConfig.id) - err := addComponentsToArchetype1[T](world, entityId, archetype, t) + entityRecord := world.entities[entityId] + archetype := world.getNextArchetype(entityRecord, componentConfig.id) + err := addComponentsToArchetype1[T](world, entityRecord, archetype, t) return err } diff --git a/tag.go b/tag.go index d095f75..2a3aaba 100644 --- a/tag.go +++ b/tag.go @@ -23,9 +23,10 @@ func (world *World) AddTag(tagId TagId, entityId EntityId) error { return fmt.Errorf("the entity %d already owns the tag %d", entityId, tagId) } - archetype := world.getNextArchetype(entityId, tagId) + entityRecord := world.entities[entityId] + archetype := world.getNextArchetype(entityRecord, tagId) - if entityRecord, ok := world.entities[entityId]; !ok { + if entityRecord.Id == 0 { world.setArchetype(entityRecord, archetype) } else { oldArchetype := world.getArchetype(entityRecord) From 44d0917a91039200b8ca7e40654557587fd1733d Mon Sep 17 00:00:00 2001 From: Ramine Agoune Date: Mon, 17 Mar 2025 17:26:17 +0100 Subject: [PATCH 2/2] fix: calling AddComponentsN moves the archetype AddComponentsN were not meant to be used multiple time on the same entity. But in the Arche benchmark it is required to add 10 components (AddComponents8 and AddComponents3). This resulted to the entity having only 3 components and being attache to the archetype of those 3 components. The 8 other components were lost in memory, in their storage. --- component.go | 113 ++++++++++++++++++++++++++++++++++++++++---------- query_test.go | 16 +++++++ 2 files changed, 107 insertions(+), 22 deletions(-) diff --git a/component.go b/component.go index 0ec848e..0858c09 100644 --- a/component.go +++ b/component.go @@ -80,11 +80,10 @@ func AddComponents2[A, B ComponentInterface](world *World, entityId EntityId, a } func addComponents2[A, B ComponentInterface](world *World, entityRecord entityRecord, a A, b B) error { - archetype := world.getArchetypeForComponentsIds(a.GetComponentId(), b.GetComponentId()) + archetype := world.getNextArchetype(entityRecord, world.getComponentsIds(a, b)...) entityId := entityRecord.Id - - if world.hasComponents(entityRecord, a.GetComponentId(), b.GetComponentId()) { + if world.hasComponents(entityRecord, world.getComponentsIds(a, b)...) { return fmt.Errorf("the entity %d already owns the components %v", entityId, []ComponentId{a.GetComponentId(), b.GetComponentId()}) } @@ -117,11 +116,11 @@ func AddComponents3[A, B, C ComponentInterface](world *World, entityId EntityId, } func addComponents3[A, B, C ComponentInterface](world *World, entityRecord entityRecord, a A, b B, c C) error { - archetype := world.getArchetypeForComponentsIds(a.GetComponentId(), b.GetComponentId(), c.GetComponentId()) + archetype := world.getNextArchetype(entityRecord, world.getComponentsIds(a, b, c)...) entityId := entityRecord.Id - if world.hasComponents(entityRecord, a.GetComponentId(), b.GetComponentId(), c.GetComponentId()) { + if world.hasComponents(entityRecord, world.getComponentsIds(a, b, c)...) { return fmt.Errorf("the entity %d already owns the components %v", entityId, []ComponentId{a.GetComponentId(), b.GetComponentId(), c.GetComponentId()}) } @@ -155,11 +154,11 @@ func AddComponents4[A, B, C, D ComponentInterface](world *World, entityId Entity } func addComponents4[A, B, C, D ComponentInterface](world *World, entityRecord entityRecord, a A, b B, c C, d D) error { - archetype := world.getArchetypeForComponentsIds(a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId()) + archetype := world.getNextArchetype(entityRecord, world.getComponentsIds(a, b, c, d)...) entityId := entityRecord.Id - if world.hasComponents(entityRecord, a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId()) { + if world.hasComponents(entityRecord, world.getComponentsIds(a, b, c, d)...) { return fmt.Errorf("the entity %d already owns the components %v", entityId, []ComponentId{a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId()}) } @@ -194,11 +193,11 @@ func AddComponents5[A, B, C, D, E ComponentInterface](world *World, entityId Ent } func addComponents5[A, B, C, D, E ComponentInterface](world *World, entityRecord entityRecord, a A, b B, c C, d D, e E) error { - archetype := world.getArchetypeForComponentsIds(a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId()) + archetype := world.getNextArchetype(entityRecord, world.getComponentsIds(a, b, c, d, e)...) entityId := entityRecord.Id - if world.hasComponents(entityRecord, a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId()) { + if world.hasComponents(entityRecord, world.getComponentsIds(a, b, c, d, e)...) { return fmt.Errorf("the entity %d already owns the components %v", entityId, []ComponentId{a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId()}) } @@ -234,11 +233,11 @@ func AddComponents6[A, B, C, D, E, F ComponentInterface](world *World, entityId } func addComponents6[A, B, C, D, E, F ComponentInterface](world *World, entityRecord entityRecord, a A, b B, c C, d D, e E, f F) error { - archetype := world.getArchetypeForComponentsIds(a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId(), f.GetComponentId()) + archetype := world.getNextArchetype(entityRecord, world.getComponentsIds(a, b, c, d, e, f)...) entityId := entityRecord.Id - if world.hasComponents(entityRecord, a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId(), f.GetComponentId()) { + if world.hasComponents(entityRecord, world.getComponentsIds(a, b, c, d, e, f)...) { return fmt.Errorf("the entity %d already owns the components %v", entityId, []ComponentId{a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId(), f.GetComponentId()}) } @@ -275,11 +274,11 @@ func AddComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, entity } func addComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, entityRecord entityRecord, a A, b B, c C, d D, e E, f F, g G) error { - archetype := world.getArchetypeForComponentsIds(a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId(), f.GetComponentId(), g.GetComponentId()) + archetype := world.getNextArchetype(entityRecord, world.getComponentsIds(a, b, c, d, e, f, g)...) entityId := entityRecord.Id - if world.hasComponents(entityRecord, a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId(), f.GetComponentId(), g.GetComponentId()) { + if world.hasComponents(entityRecord, world.getComponentsIds(a, b, c, d, e, f, g)...) { return fmt.Errorf("the entity %d already owns the components %v", entityId, []ComponentId{a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId(), f.GetComponentId(), g.GetComponentId()}) } @@ -317,11 +316,11 @@ func AddComponents8[A, B, C, D, E, F, G, H ComponentInterface](world *World, ent } func addComponents8[A, B, C, D, E, F, G, H ComponentInterface](world *World, entityRecord entityRecord, a A, b B, c C, d D, e E, f F, g G, h H) error { - archetype := world.getArchetypeForComponentsIds(a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId(), f.GetComponentId(), g.GetComponentId(), h.GetComponentId()) + archetype := world.getNextArchetype(entityRecord, world.getComponentsIds(a, b, c, d, e, f, g, h)...) entityId := entityRecord.Id - if world.hasComponents(entityRecord, a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId(), f.GetComponentId(), g.GetComponentId(), h.GetComponentId()) { + if world.hasComponents(entityRecord, world.getComponentsIds(a, b, c, d, e, f, g, h)...) { return fmt.Errorf("the entity %d already owns the components %v", entityId, []ComponentId{a.GetComponentId(), b.GetComponentId(), c.GetComponentId(), d.GetComponentId(), e.GetComponentId(), f.GetComponentId(), g.GetComponentId(), h.GetComponentId()}) } @@ -522,9 +521,19 @@ func addComponentsToArchetype2[A, B ComponentInterface](world *World, entityReco return fmt.Errorf("no storage found for component %v", componentsIds) } + // If the entity has no component, simply add it the archetype + if entityRecord.archetypeId == 0 { + world.setArchetype(entityRecord, archetype) + } else { + oldArchetype := world.getArchetype(entityRecord) + if archetype.Id != oldArchetype.Id { + moveComponentsToArchetype(world, entityRecord, oldArchetype, archetype) + world.setArchetype(entityRecord, archetype) + } + } + storageA.add(archetype.Id, componentA) storageB.add(archetype.Id, componentB) - world.setArchetype(entityRecord, archetype) return nil } @@ -539,10 +548,20 @@ func addComponentsToArchetype3[A, B, C ComponentInterface](world *World, entityR return fmt.Errorf("no storage found for components %v", componentsIds) } + // If the entity has no component, simply add it the archetype + if entityRecord.archetypeId == 0 { + world.setArchetype(entityRecord, archetype) + } else { + oldArchetype := world.getArchetype(entityRecord) + if archetype.Id != oldArchetype.Id { + moveComponentsToArchetype(world, entityRecord, oldArchetype, archetype) + world.setArchetype(entityRecord, archetype) + } + } + storageA.add(archetype.Id, componentA) storageB.add(archetype.Id, componentB) storageC.add(archetype.Id, componentC) - world.setArchetype(entityRecord, archetype) return nil } @@ -558,11 +577,21 @@ func addComponentsToArchetype4[A, B, C, D ComponentInterface](world *World, enti return fmt.Errorf("no storage found for components %v", componentsIds) } + // If the entity has no component, simply add it the archetype + if entityRecord.archetypeId == 0 { + world.setArchetype(entityRecord, archetype) + } else { + oldArchetype := world.getArchetype(entityRecord) + if archetype.Id != oldArchetype.Id { + moveComponentsToArchetype(world, entityRecord, oldArchetype, archetype) + world.setArchetype(entityRecord, archetype) + } + } + storageA.add(archetype.Id, componentA) storageB.add(archetype.Id, componentB) storageC.add(archetype.Id, componentC) storageD.add(archetype.Id, componentD) - world.setArchetype(entityRecord, archetype) return nil } @@ -579,12 +608,22 @@ func addComponentsToArchetype5[A, B, C, D, E ComponentInterface](world *World, e return fmt.Errorf("no storage found for components %v", componentsIds) } + // If the entity has no component, simply add it the archetype + if entityRecord.archetypeId == 0 { + world.setArchetype(entityRecord, archetype) + } else { + oldArchetype := world.getArchetype(entityRecord) + if archetype.Id != oldArchetype.Id { + moveComponentsToArchetype(world, entityRecord, oldArchetype, archetype) + world.setArchetype(entityRecord, archetype) + } + } + storageA.add(archetype.Id, componentA) storageB.add(archetype.Id, componentB) storageC.add(archetype.Id, componentC) storageD.add(archetype.Id, componentD) storageE.add(archetype.Id, componentE) - world.setArchetype(entityRecord, archetype) return nil } @@ -602,13 +641,23 @@ func addComponentsToArchetype6[A, B, C, D, E, F ComponentInterface](world *World return fmt.Errorf("no storage found for components %v", componentsIds) } + // If the entity has no component, simply add it the archetype + if entityRecord.archetypeId == 0 { + world.setArchetype(entityRecord, archetype) + } else { + oldArchetype := world.getArchetype(entityRecord) + if archetype.Id != oldArchetype.Id { + moveComponentsToArchetype(world, entityRecord, oldArchetype, archetype) + world.setArchetype(entityRecord, archetype) + } + } + storageA.add(archetype.Id, componentA) storageB.add(archetype.Id, componentB) storageC.add(archetype.Id, componentC) storageD.add(archetype.Id, componentD) storageE.add(archetype.Id, componentE) storageF.add(archetype.Id, componentF) - world.setArchetype(entityRecord, archetype) return nil } @@ -627,6 +676,17 @@ func addComponentsToArchetype7[A, B, C, D, E, F, G ComponentInterface](world *Wo return fmt.Errorf("no storage found for components %v", componentsIds) } + // If the entity has no component, simply add it the archetype + if entityRecord.archetypeId == 0 { + world.setArchetype(entityRecord, archetype) + } else { + oldArchetype := world.getArchetype(entityRecord) + if archetype.Id != oldArchetype.Id { + moveComponentsToArchetype(world, entityRecord, oldArchetype, archetype) + world.setArchetype(entityRecord, archetype) + } + } + storageA.add(archetype.Id, componentA) storageB.add(archetype.Id, componentB) storageC.add(archetype.Id, componentC) @@ -634,7 +694,6 @@ func addComponentsToArchetype7[A, B, C, D, E, F, G ComponentInterface](world *Wo storageE.add(archetype.Id, componentE) storageF.add(archetype.Id, componentF) storageG.add(archetype.Id, componentG) - world.setArchetype(entityRecord, archetype) return nil } @@ -654,6 +713,17 @@ func addComponentsToArchetype8[A, B, C, D, E, F, G, H ComponentInterface](world return fmt.Errorf("no storage found for components %v", componentsIds) } + // If the entity has no component, simply add it the archetype + if entityRecord.archetypeId == 0 { + world.setArchetype(entityRecord, archetype) + } else { + oldArchetype := world.getArchetype(entityRecord) + if archetype.Id != oldArchetype.Id { + moveComponentsToArchetype(world, entityRecord, oldArchetype, archetype) + world.setArchetype(entityRecord, archetype) + } + } + storageA.add(archetype.Id, componentA) storageB.add(archetype.Id, componentB) storageC.add(archetype.Id, componentC) @@ -662,7 +732,6 @@ func addComponentsToArchetype8[A, B, C, D, E, F, G, H ComponentInterface](world storageF.add(archetype.Id, componentF) storageG.add(archetype.Id, componentG) storageH.add(archetype.Id, componentH) - world.setArchetype(entityRecord, archetype) return nil } diff --git a/query_test.go b/query_test.go index f2f3d20..53fd66e 100644 --- a/query_test.go +++ b/query_test.go @@ -95,6 +95,7 @@ func TestQuery1_Foreach(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -132,6 +133,7 @@ func TestQuery1_ForeachChannel(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -228,6 +230,7 @@ func TestQuery2_Foreach(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -266,6 +269,7 @@ func TestQuery2_ForeachChannel(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -367,6 +371,7 @@ func TestQuery3_Foreach(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -406,6 +411,7 @@ func TestQuery3_ForeachChannel(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -510,6 +516,7 @@ func TestQuery4_Foreach(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -550,6 +557,7 @@ func TestQuery4_ForeachChannel(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -658,6 +666,7 @@ func TestQuery5_Foreach(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -699,6 +708,7 @@ func TestQuery5_ForeachChannel(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -811,6 +821,7 @@ func TestQuery6_Foreach(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -853,6 +864,7 @@ func TestQuery6_ForeachChannel(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -970,6 +982,7 @@ func TestQuery7_Foreach(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -1014,6 +1027,7 @@ func TestQuery7_ForeachChannel(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -1127,6 +1141,7 @@ func TestQuery8_Foreach(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } } @@ -1171,6 +1186,7 @@ func TestQuery8_ForeachChannel(t *testing.T) { } if !found { t.Errorf("query should return EntityId %d in Foreach iterator", entityId) + break } } }