Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docs-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12"]
python-version: ["3.10"]

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12"]
python-version: ["3.10"]

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pypi-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12"]
python-version: ["3.10"]

steps:
- uses: actions/checkout@v4
Expand Down
21 changes: 9 additions & 12 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,20 @@
flake-utils.url = "github:numtide/flake-utils";
};

outputs =
{
nixpkgs,
flake-utils,
...
}:
outputs = {
nixpkgs,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
system: let
pkgs = import nixpkgs {
inherit system;
};
in
{
in {
devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
python312
python310
];

buildInputs = with pkgs; [
Expand All @@ -31,7 +28,7 @@
uv
isort
mypy
python312Packages.pylint
pylint
];
};
}
Expand Down
10 changes: 7 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "snakia"
version = "0.4.1"
version = "0.4.2"
description = "Modern python framework"
readme = "README.md"
authors = [
Expand All @@ -10,15 +10,19 @@ keywords = ["python3", "event system", "ecs", "reactive programming"]
classifiers = [
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Free Threading",
]
requires-python = ">=3.12"
requires-python = ">=3.10"
dependencies = [
"exceptiongroup>=1.3.0",
"networkx>=3.4.2",
"pydantic>=2.12.3",
"typing-extensions>=4.15.0",
]
license = "CC0-1.0"
license-files = ["LICENSE"]
Expand All @@ -38,4 +42,4 @@ disable = ["C0114", "C0115", "C0116", "R0801"]
max-args = 8
max-positional-arguments = 7
min-public-methods = 1
fail-on = "error"
fail-on = "error"
6 changes: 5 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
# uv pip compile pyproject.toml -o requirements.txt
annotated-types==0.7.0
# via pydantic
networkx==3.5
exceptiongroup==1.3.0
# via snakia (pyproject.toml)
networkx==3.4.2
# via snakia (pyproject.toml)
pydantic==2.12.3
# via snakia (pyproject.toml)
pydantic-core==2.41.4
# via pydantic
typing-extensions==4.15.0
# via
# snakia (pyproject.toml)
# exceptiongroup
# pydantic
# pydantic-core
# typing-inspection
Expand Down
126 changes: 49 additions & 77 deletions src/snakia/core/ecs/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections import defaultdict
from collections.abc import Iterable
from itertools import count
from typing import Any, cast, overload
from typing import Any, TypeVar, cast, overload

import networkx as nx # type: ignore

Expand All @@ -12,6 +12,14 @@
from .component import Component
from .processor import Processor

A = TypeVar("A", bound=Component)
B = TypeVar("B", bound=Component)
C = TypeVar("C", bound=Component)
D = TypeVar("D", bound=Component)
E = TypeVar("E", bound=Component)

P = TypeVar("P", bound=Processor)


class System:
"""
Expand Down Expand Up @@ -46,9 +54,7 @@ def full_reset(self) -> None:
self.__entity_counter = count(start=1)
self.__dead_entities = set()

def get_processor[P: Processor](
self, processor_type: type[P], /
) -> P | None:
def get_processor(self, processor_type: type[P], /) -> P | None:
"""Returns the first processor of the given type."""
for processor in self.__processors:
if isinstance(processor, processor_type):
Expand All @@ -67,39 +73,31 @@ def remove_processor(self, processor_type: type[Processor]) -> None:
self.__processors.remove(processor)

@overload
def get_components[A: Component](
self, __c1: type[A], /
) -> Iterable[tuple[int, tuple[A]]]: ...
def get_components(self, c1: type[A], /) -> Iterable[tuple[int, tuple[A]]]: ...

@overload
def get_components[A: Component, B: Component](
self, __c1: type[A], __c2: type[B], /
def get_components(
self, c1: type[A], c2: type[B], /
) -> Iterable[tuple[int, tuple[A, B]]]: ...

@overload
def get_components[A: Component, B: Component, C: Component](
self, __c1: type[A], __c2: type[B], __c3: type[C], /
def get_components(
self, c1: type[A], c2: type[B], c3: type[C], /
) -> Iterable[tuple[int, tuple[A, B, C]]]: ...

@overload
def get_components[A: Component, B: Component, C: Component, D: Component](
self, __c1: type[A], __c2: type[B], __c3: type[C], __c4: type[D], /
def get_components(
self, c1: type[A], c2: type[B], c3: type[C], c4: type[D], /
) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ...

@overload
def get_components[
A: Component,
B: Component,
C: Component,
D: Component,
E: Component,
](
def get_components(
self,
__c1: type[A],
__c2: type[B],
__c3: type[C],
__c4: type[D],
__c5: type[E],
c1: type[A],
c2: type[B],
c3: type[C],
c4: type[D],
c5: type[E],
/,
) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ...

Expand All @@ -108,10 +106,7 @@ def get_components(
) -> Iterable[tuple[int, tuple[Component, ...]]]:
"""Returns all entities with the given components."""
entity_set = set.intersection(
*(
self.__components[component_type]
for component_type in component_types
)
*(self.__components[component_type] for component_type in component_types)
)
for entity in entity_set:
yield (
Expand All @@ -123,51 +118,40 @@ def get_components(
)

@overload
def get_components_of_entity[A: Component](
self, entity: int, __c1: type[A], /
def get_components_of_entity(
self, entity: int, c1: type[A], /
) -> tuple[A | None]: ...

@overload
def get_components_of_entity[A: Component, B: Component](
self, entity: int, __c1: type[A], __c2: type[B], /
def get_components_of_entity(
self, entity: int, c1: type[A], c2: type[B], /
) -> tuple[A | None, B | None]: ...

@overload
def get_components_of_entity[A: Component, B: Component, C: Component](
self, entity: int, __c1: type[A], __c2: type[B], __c3: type[C], /
def get_components_of_entity(
self, entity: int, c1: type[A], c2: type[B], c3: type[C], /
) -> tuple[A | None, B | None, C | None]: ...

@overload
def get_components_of_entity[
A: Component,
B: Component,
C: Component,
D: Component,
](
def get_components_of_entity(
self,
entity: int,
__c1: type[A],
__c2: type[B],
__c3: type[C],
__c4: type[D],
c1: type[A],
c2: type[B],
c3: type[C],
c4: type[D],
/,
) -> tuple[A | None, B | None, C | None, D | None]: ...

@overload
def get_components_of_entity[
A: Component,
B: Component,
C: Component,
D: Component,
E: Component,
](
def get_components_of_entity(
self,
entity: int,
__c1: type[A],
__c2: type[B],
__c3: type[C],
__c4: type[D],
__c5: type[E],
c1: type[A],
c2: type[B],
c3: type[C],
c4: type[D],
c5: type[E],
/,
) -> tuple[A | None, B | None, C | None, D | None, E | None]: ...

Expand All @@ -183,20 +167,18 @@ def get_components_of_entity(
),
)

def get_component[C: Component](
self, component_type: type[C], /
) -> Iterable[tuple[int, C]]:
def get_component(self, component_type: type[C], /) -> Iterable[tuple[int, C]]:
"""Returns all entities with the given component."""
for entity in self.__components[component_type].copy():
yield entity, cast(C, self.__entitites[entity][component_type])

@overload
def get_component_of_entity[C: Component](
def get_component_of_entity(
self, entity: int, component_type: type[C], /
) -> C | None: ...

@overload
def get_component_of_entity[C: Component, D: Any](
def get_component_of_entity(
self, entity: int, component_type: type[C], /, default: D
) -> C | D: ...

Expand All @@ -216,24 +198,16 @@ def add_component(self, entity: int, component: Component) -> None:
self.__components[component_type].add(entity)
self.__entitites[entity][component_type] = component

def has_component(
self, entity: int, component_type: type[Component]
) -> bool:
def has_component(self, entity: int, component_type: type[Component]) -> bool:
"""Returns True if the entity has the given component."""
return component_type in self.__entitites[entity]

def has_components(
self, entity: int, *component_types: type[Component]
) -> bool:
def has_components(self, entity: int, *component_types: type[Component]) -> bool:
"""Returns True if the entity has all the given components."""
components_dict = self.__entitites[entity]
return all(
comp_type in components_dict for comp_type in component_types
)
return all(comp_type in components_dict for comp_type in component_types)

def remove_component[C: Component](
self, entity: int, component_type: type[C]
) -> C | None:
def remove_component(self, entity: int, component_type: type[C]) -> C | None:
"""Removes a component from an entity."""
self.__components[component_type].discard(entity)
if not self.__components[component_type]:
Expand Down Expand Up @@ -265,9 +239,7 @@ def delete_entity(self, entity: int, immediate: bool = False) -> None:

def entity_exists(self, entity: int) -> bool:
"""Returns True if the entity exists."""
return (
entity in self.__entitites and entity not in self.__dead_entities
)
return entity in self.__entitites and entity not in self.__dead_entities

def start(self) -> None:
"""Starts the system."""
Expand Down
4 changes: 1 addition & 3 deletions src/snakia/core/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ def __init__(self) -> None:
self.__dispatcher_thread: threading.Thread | None = None

def start(self) -> None:
self.__system_thread = threading.Thread(
target=self.system.start, daemon=False
)
self.__system_thread = threading.Thread(target=self.system.start, daemon=False)
self.__dispatcher_thread = threading.Thread(
target=self.dispatcher.start, daemon=False
)
Expand Down
Loading
Loading