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 therepr
instead of theid
.- Parameters
name (str) – A human-readable name for this world, for debugging purposes. The default world is called “default”.
- Return type
-
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.
-
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 withregister_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
anddeserialize
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
ordeserialize
, all of your registered classes must define them. Likewise, if you register a class that doesn’t defineserialize
anddeserialize
, 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
-
classmethod
-
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 useRegisteredComponent
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
- 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
-
snecs.ecs.
add_components
(entity_id, components, world=<DEFAULT World>)[source]¶ Add components to an entity.
See notes in
add_component
.- Parameters
- 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
-
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
- Returns
An immutable mapping between each Component type of which the entity has an instance, and the instance.
- Return type
- Raises
-
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
.
-
snecs.ecs.
deserialize_world
(serialized, name=None)[source]¶ Deserialize the output of
serialize_world
into a newWorld
.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
serialized (
SerializedWorldType
) – A serialized world, as output byserialize_world
.name (Optional[str]) – An optional name to use for the new World. See
World
.
- Returns
A new World with the data from the serialized one.
- Return type
-
snecs.ecs.
entity_component
(entity_id, component_type, world=<DEFAULT World>)[source]¶ Get the Component instance of a given type for a specific entity.
-
snecs.ecs.
entity_components
(entity_id, components, world=<DEFAULT World>)[source]¶ Get the given instances of given Component types for a specific entity.
- Parameters
- 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
- 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).
-
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
- Returns
Whether the entity has a component of the given type.
- Return type
- Raises
-
snecs.ecs.
has_components
(entity_id, component_types, world=<DEFAULT World>)[source]¶ Check if a given entity has all of the specified Components.
- Parameters
- Returns
Whether the entity has components of all of the given types.
- Return type
- Raises
-
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.
-
snecs.ecs.
new_entity
(components=(), world=<DEFAULT World>)[source]¶ Create an entity in a World with the given Components, returning its ID.
-
snecs.ecs.
process_pending_deletions
(world=<DEFAULT World>)[source]¶ Process pending entity deletions.
Equivalent to calling
delete_entity_immediately
on all entities for whichschedule_for_deletion
had been called for since the last call toprocess_pending_deletions
.Idempotent. Not thread-safe.
-
snecs.ecs.
remove_component
(entity_id, component_type, world=<DEFAULT World>)[source]¶ Remove the component of a given type from an entity.
-
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)
-
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 withjson.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 inserialized[SERIALIZED_COMPONENTS_KEY]
to that component’s serialized data.For example, if
serialized[SERIALIZED_COMPONENTS_KEY]
is[A, B, C]
, the index1
would indicate that the value is a serialized instance of aB
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
- 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 atyping.NewType("EntityID", int)
) used for annotating entity identifiers. At runtime, exactly equivalent toint
.All the functions in
snecs
that take an entity ID as an argument or return one are annotated appropriately withEntityID
.
-
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 withtyping.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 ofquery
: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
” isUnion[Expr, Type[Component]]
.