API Reference

All the public classes and functions exposed by snecs are documented here. Here’s a handy list of the individual sections:

snecs.world

Defines the World - the central object in snecs, which manages all the data.

class snecs.world.World(name: Optional[str] = None)[source]

A container for all your entities, components and systems.

Takes an optional name parameter that, if passed, will be used in the repr instead of the id.

Parameters

name (str) – A human-readable name for this world, for debugging purposes. The default world is called “default”.

Return type

None

classmethod get_by_name(name: str) → snecs.world.World[source]

Get a World instance by name.

This is obviously slower than just passing the World around, but it might sometimes be useful for avoiding circular imports. The preferred workflow is still to have one module that only imports from snecs and instantiates the world:

from snecs import World

my_world = World()

And import my_world from there.

Parameters

name (str) – Name of the World instance to get.

Returns

The World with the given name, if it exists.

Return type

World

Raises

KeyError – If there is no World with the given name.

snecs.world.default_world = <DEFAULT World>

The default World, used if you don’t explicitly pass one.

snecs.component

Base Component classes and the functions necessary to make them work.

class snecs.component.Component[source]

Base Component class.

snecs components must subclass Component, and be registered with register_component if they are going to be instantiated:

@register_component
class MyComponent(Component):
    ...

Base and abstract Component classes don’t have to be registered, as long as you’re not going to instantiate them.

Other than that, there are no restrictions on what Components should be. They can be anything as simple as an integer:

@register_component
class IntComponent(int, Component):
    pass

Or as complex as you want:

@register_component
class BigComponent(Component):
    __slots__ = ('a', 'b', 'c', 'd', ...)

    def __init__(a, b, c, d, ...):
        ...

    def __str__(self):
        ...

    ...

If you want to use the snecs full-world serialization, you should override serialize and deserialize in all your registered Components:

@register_component
class IntPairComponent(Component):
    ...
    def serialize(self):
        return (self.first, self.second)

    @classmethod
    def deserialize(cls, serialized):
        return cls(*serialized)

You must define either both, or neither of those two methods. Registering a class that only defines one of those is an error.

If you register a class that defines serialize or deserialize, all of your registered classes must define them. Likewise, if you register a class that doesn’t define serialize and deserialize, none of your registered classes can define them.

classmethod deserialize(serialized: Any) → CType[source]

Deserialize a serialized instance of this component.

Will get the output of serialize as an argument.

Override this in all your Component classes to make use of snecs’ full-World serialization feature.

Parameters

serialized (Any) –

Return type

CType

serialize() → Any[source]

Serialize an instance of this component into a simpler type.

Override this in all your Component classes to make use of snecs’ full-World serialization feature.

Return type

Any

class snecs.component.RegisteredComponent[source]

A convenience Component subclass for auto-registering components.

The following two ways of defining a component are equivalent:

@register_component
class MyComponent1(snecs.Component):
    ...

class MyComponent2(snecs.RegisteredComponent):
    ...

Bear in mind, however, that all subclasses of a RegisteredComponent subclass will also get registered. Registering unnecessary components does have a slight performance and memory impact, so it’s better to explicitly use register_component, or use RegisteredComponent as a mixin on leaf classes only:

class AbstractComponent(snecs.Component, ABC):
    ...

class ConcreteComponent(AbstractComponent, RegisteredComponent):
    ...

This last approach isn’t much different than just decorating your components with register_component, though.

snecs.component.register_component(component)[source]

A decorator to register a class for use as a snecs component.

Every class intended to be instantiated and used as a snecs component must inherit from Component and be registered by decorating it with @register_component.

Parameters

component (Type[CType]) –

Return type

Type[CType]

snecs.ecs

Functions for interacting with the ECS.

All the functions in this module are reexported under the snecs root for convenience, so that you can:

from snecs import new_entity

new_entity(...)

# or

import snecs

snecs.new_entity(...)
snecs.add_component(...)

Instead of having to type snecs.ecs every time.

snecs.ecs.add_component(entity_id, component, world=<DEFAULT World>)[source]

Add a single component instance to an entity.

Warning

Adding multiple components of the same type to a single entity is invalid.

Parameters
  • entity_id (EntityID) – ID of the entity to add the Component to.

  • component (Component) – A single Component instances to add to the Entity.

  • world (Optional[World]) – The World holding the entity.

Raises
  • KeyError – If the Component type was not registered, or if the entity doesn’t exist in the given World.

  • ValueError – If the entity already has a Component of the same type as the given one.

Return type

None

snecs.ecs.add_components(entity_id, components, world=<DEFAULT World>)[source]

Add components to an entity.

See notes in add_component.

Parameters
  • entity_id (EntityID) – ID of the entity to add the Components to.

  • components (Collection[Component]) – An iterable of Component instances to add to the Entity.

  • world (Optional[World]) – The World holding the entity.

Raises
  • KeyError – If any of the Component types was not registered, or if the entity doesn’t exist in the given World.

  • ValueError – If the entity already has a Component of the same type as one of the given ones.

Return type

None

snecs.ecs.all_components(entity_id, world=<DEFAULT World>)[source]

Get a mapping of all Components for a specific entity in a given World.

Parameters
  • entity_id (EntityID) – ID of the entity to get the Component from.

  • world (Optional[World]) – The World to look up the entity in.

Returns

An immutable mapping between each Component type of which the entity has an instance, and the instance.

Return type

Mapping[Type[Component], Component]

Raises

KeyError – If the entity doesn’t exist in this World.

snecs.ecs.delete_entity_immediately(entity_id, world=<DEFAULT World>)[source]

Delete an entity from a given World immediately. Not thread-safe.

Idempotent. Will not throw a KeyError if the entity doesn’t exist in this World.

Parameters
  • entity_id (EntityID) – ID of the entity to delete

  • world (Optional[World]) – The World to delete the entity from

Return type

None

snecs.ecs.deserialize_world(serialized, name=None)[source]

Deserialize the output of serialize_world into a new World.

If any of the component types that were registered in the serialized World are not registered when you call deserialize_world, or any of them have been renamed, this will fail and raise a ValueError.

If you want to deserialize into the default World, you’ll want to call move_world afterwards:

move_world(deserialize_world(serialized_data))
Parameters
Returns

A new World with the data from the serialized one.

Return type

World

snecs.ecs.entity_component(entity_id, component_type, world=<DEFAULT World>)[source]

Get the Component instance of a given type for a specific entity.

Parameters
  • entity_id (EntityID) – ID of the entity to get the Component from.

  • component_type (Type[Component]) – Type of the component to fetch.

  • world (Optional[World]) – The World to look up the entity in.

Returns

The instance of component_type that’s assigned to the entity.

Return type

Instance of component_type

Raises

KeyError – If the entity doesn’t exist in this World or if it doesn’t have the requested Component.

snecs.ecs.entity_components(entity_id, components, world=<DEFAULT World>)[source]

Get the given instances of given Component types for a specific entity.

Parameters
  • entity_id (EntityID) – ID of the entity to get the Components from.

  • components (Iterable[Type[Component]]) – An iterable of Component types to look up.

  • world (Optional[World]) – The World to look up the entity in.

Returns

A dictionary mapping each given Component type to the instance of that type attached to the given entity. Note that mutations of this dictionary do not affect the world.

Return type

Dict[Type[Component], Component]

Raises

KeyError – If the entity doesn’t exist in this World or if it doesn’t have any of the requested Components.

snecs.ecs.exists(entity_id, world)[source]

Check whether an entity exists in this World.

This is a convenience function that should not be necessary except in concurrent code or with uncertain usage of schedule_for_deletion (see the documentation of that function).

Parameters
  • entity_id (EntityID) – ID of the entity to check for

  • world (World) – The World to check for the existence of the entity

Returns

True if the entity exists in the World, False otherwise

Return type

bool

snecs.ecs.has_component(entity_id, component_type, world=<DEFAULT World>)[source]

Check if a given entity has a specific Component.

if not has_component(entity_id, FreezeStatus):
    do_stuff(entity_id)
Parameters
  • entity_id (EntityID) – ID of the entity to check

  • component_type (Type[Component]) – Component type to check for

  • world (Optional[World]) – World to check for the entity

Returns

Whether the entity has a component of the given type.

Return type

bool

Raises

KeyError – If the entity doesn’t exist in this World.

snecs.ecs.has_components(entity_id, component_types, world=<DEFAULT World>)[source]

Check if a given entity has all of the specified Components.

Parameters
  • entity_id (EntityID) – ID of the entity to check

  • component_types (Iterable[Type[Component]]) – Iterable of component types to check for

  • world (Optional[World]) – World to check for the entity

Returns

Whether the entity has components of all of the given types.

Return type

bool

Raises

KeyError – If the entity doesn’t exist in this World.

snecs.ecs.move_world(original, target=<DEFAULT World>)[source]

Move the data from a World into a different one, then clear the original.

This is mainly useful for replacing the default snecs World with the output of deserialize_world.

Returns the target world, to make this pattern easier:

world = move_world(original, World())

By passing an empty World as the first argument, this method can be used to clear the target World.

Parameters
  • original (World) – The World to move the data from.

  • target (Optional[World]) – The World to move the data into.

Returns

The target World.

Return type

snecs.world.World

snecs.ecs.new_entity(components=(), world=<DEFAULT World>)[source]

Create an entity in a World with the given Components, returning its ID.

Parameters
  • world (Optional[World]) – World to create the entity in.

  • components (Collection[Component]) – An iterable of Component instances to attach to the entity.

Returns

ID of the newly created entity.

Return type

EntityID

snecs.ecs.process_pending_deletions(world=<DEFAULT World>)[source]

Process pending entity deletions.

Equivalent to calling delete_entity_immediately on all entities for which schedule_for_deletion had been called for since the last call to process_pending_deletions.

Idempotent. Not thread-safe.

Parameters

world (Optional[World]) – The World to delete the entities from

Return type

None

snecs.ecs.remove_component(entity_id, component_type, world=<DEFAULT World>)[source]

Remove the component of a given type from an entity.

Parameters
  • entity_id (EntityID) – ID of the entity to remove the component from.

  • component_type (Type[Component]) – Type of the component to remove.

  • world (Optional[World]) – The World to look up the entity in.

Raises

KeyError – If the entity doesn’t exist in this World or it doesn’t have the specified component.

Return type

None

snecs.ecs.schedule_for_deletion(entity_id, world=<DEFAULT World>)[source]

Schedule an entity for deletion from the World. Thread-safe.

The entity will be deleted on the next call to process_pending_deletions.

This method is idempotent - you can schedule an entity for deletion many times, and it will only be deleted once.

Warning

This will not raise an error if the entity doesn’t exist. However, calling this with an entity ID that isn’t in the World will put the World into an unrecoverable state - all later calls to process_pending_deletions will fail.

To prevent this from happening, make sure you’re calling process_pending_deletions only after all your Systems have finished running for this frame, so that they won’t have a reference to the dead entity on the next loop.

Calling delete_entity_immediately on an entity that is scheduled for deletion but hasn’t been deleted yet is safe, and will remove the entity from the deletion queue.

If there is a risk of an entity you want to delete being already gone, do this instead:

from snecs.ecs import exists, schedule_for_deletion

if exists(entity_id, world):
    schedule_for_deletion(entity_id, world)
Parameters
  • entity_id (EntityID) – ID of the entity to schedule for deletion

  • world (Optional[World]) – The World to delete the entity from

Return type

None

snecs.ecs.serialize_world(world=<DEFAULT World>)[source]

Serialize a World and all the Entities and Components inside it.

All Component classes in your World must be serializable for this to run.

Returns a Python dictionary that can be passed to deserialize_world to reconstruct the serialized world.

You can for example, dump the output of serialize_world to a file with json.dump, to use as an effortless savegame format:

import json

def save_to_file(world: World, filename: str):
    with open(filename, "w") as f:
        json.dump(serialize_world(world), f)

The resulting dict looks like this:

serialized = {
    SERIALIZED_COMPONENTS_KEY: ["ComponentType1",...],
    SERIALIZED_ENTITIES_KEY: {
        entity_id: {
            component_idx: serialized_component_data,
            ...
        },
        entity_id: ...,
        ...
    }
}

Where:

  • serialized[SERIALIZED_COMPONENTS_KEY] is a list of names of the Component classes present in the World.

  • serialized[SERIALIZED_ENTITIES_KEY] is a dictionary mapping entity IDs to dictionaries representing their components.

    • Each of the dictionaries in serialized[SERIALIZED_ENTITIES_KEY] maps indices of component types in serialized[SERIALIZED_COMPONENTS_KEY] to that component’s serialized data.

      For example, if serialized[SERIALIZED_COMPONENTS_KEY] is [A, B, C], the index 1 would indicate that the value is a serialized instance of a B component.

Parameters

world (Optional[World]) – The World to serialize.

Returns

A serialized dictionary representing this World, which can be deserialized with deserialize_world.

Return type

SerializedWorldType

Raises

AttributeError – If any component in the World is not serializable.

snecs.ecs.SERIALIZED_COMPONENTS_KEY = 'C'

See serialize_world

snecs.ecs.SERIALIZED_ENTITIES_KEY = 'E'

See serialize_world

snecs.types

snecs is fully type hinted and checked with mypy. The snecs.types module exports a couple types you might need if you want to type hint the parts of your code that use snecs:

class snecs.types.EntityID([int])

A subclass of int (effectively a typing.NewType("EntityID", int)) used for annotating entity identifiers. At runtime, exactly equivalent to int.

All the functions in snecs that take an entity ID as an argument or return one are annotated appropriately with EntityID.

class snecs.types.SerializedWorldType

This constant defines the exact return type of snecs.ecs.serialize_world. It exists as an alias so that your typed code doesn’t need to specify the type literally - to make migration easier when PyPy 3.8 releases with typing.TypedDict.

class snecs.types.QueryType

The abstract base class for all queries:

def execute_query(q: QueryType) -> ...:
    ...

my_query = query([MyComponent])
compiled = my_query.compile()

execute_query(my_query)  # ok!
execute_query(compiled)  # ok!
execute_query(MyComponent)  # error!

Note

You can also use query itself as a type annotation, but remember that compiled queries aren’t a subclass of query:

def compile_query(q: query) -> ...:
    ...

my_query = query([MyComponent])
compiled = my_query.compile()

compile_query(my_query)  # ok!
compile_query(compiled)  # error!
class snecs.types.CompiledQueryType

The abstract base class for all compiled queries:

def execute_compiled_query(q: CompiledQueryType) -> ...:
    ...

my_query = query([MyComponent])
compiled = my_query.compile()

execute_compiled_query(my_query)  # error!
execute_compiled_query(compiled)  # ok!
class snecs.types.Expr

The abstract base class for filter expressions:

def foo(e: Expr) -> ...:
    ...

# `ComponentA & ComponentB` and all other filter expressions are of
# type `Expr`.
foo(ComponentA & ComponentB)  # ok!
foo(~ComponentC | (ComponentA & Component))  # ok!

# `ComponentA` is of type `Type[Component]`
foo(ComponentA)  # error!

Note that filter expressions accept lone component types - the type of “any value accepted by query.filter” is Union[Expr, Type[Component]].