diff --git a/esper/__init__.py b/esper/__init__.py index 2a478ba..2b85689 100644 --- a/esper/__init__.py +++ b/esper/__init__.py @@ -493,6 +493,7 @@ def _get_components(*component_types: type[_C]) -> _Iterable[tuple[int, tuple[_C comp_db = _components min_set = None + min_type = None min_size = inf other_types = [] @@ -502,10 +503,11 @@ def _get_components(*component_types: type[_C]) -> _Iterable[tuple[int, tuple[_C return set_size = len(comp_set) if set_size < min_size: - if min_set is not None: - other_types.append(component_types[len(other_types)]) + if min_type is not None: + other_types.append(min_type) min_size = set_size min_set = comp_set + min_type = ct else: other_types.append(ct) diff --git a/tests/test_world.py b/tests/test_world.py index 9d8503e..52dfe8b 100644 --- a/tests/test_world.py +++ b/tests/test_world.py @@ -154,6 +154,32 @@ def test_get_three_components(): assert isinstance(e, ComponentE) +def test_get_components_smallest_set_missing_component(): + # Regression test: _get_components scans for the smallest component set + # first. When a type is set aside before a new smallest set is found, the + # previously-smallest type must be the one moved aside. Component sizes are + # arranged so ComponentB is set aside, then ComponentC becomes the new + # smallest -- ComponentA (the prior smallest) must not be dropped, otherwise + # an entity that matches on the remaining types but lacks ComponentA raises + # a KeyError while building the result tuple. + esper.create_entity(ComponentB(), ComponentC()) # matches B and C, not A + esper.create_entity(ComponentA()) + esper.create_entity(ComponentA()) + esper.create_entity(ComponentB()) + esper.create_entity(ComponentB()) + entity_all = esper.create_entity(ComponentA(), ComponentB(), ComponentC()) + + # sizes: A=3, B=4, C=2 -> C is discovered as the smallest set last + result = esper.get_components(ComponentA, ComponentB, ComponentC) + + assert len(result) == 1 + ent, (a, b, c) = result[0] + assert ent == entity_all + assert isinstance(a, ComponentA) + assert isinstance(b, ComponentB) + assert isinstance(c, ComponentC) + + def test_try_component(): entity1 = esper.create_entity(ComponentA(), ComponentB())