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
9 changes: 3 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI/CD Pipeline
name: Automated Tests

on:
push:
Expand All @@ -17,18 +17,15 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
python-version: '3.10.8'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install black pytest
pip install pytest numpy line_profiler

- name: Set up PYTHONPATH
run: echo "PYTHONPATH=$PYTHONPATH:$(pwd)" >> $GITHUB_ENV

- name: FORMATTING - Run black
run: black .

- name: TESTS - Run tests
run: pytest --disable-warnings
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).

### Changed
- Refactor `HemeraTermFx` to further optimize terminal printing performance.
- Update `NyxEngine` as the main enforcer of game state consistency across the project. It now holds all instances of the major subclasses.

---

Expand All @@ -22,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Add an alient planet sprite to the current game demo in `main.py`.

### Changed
- Optimize terminal printing string generation for a 95% reduction in frame printing time.
- Optimize `HemeraTermFx` terminal printing string generation for a 95% reduction in frame printing time.

---

Expand Down
4 changes: 2 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def generate_planet(engine: NyxEngine):


if __name__ == "__main__":
engine = NyxEngine()
# Configs
#Line profile string buffer printing:
line_profiling = False
Expand All @@ -109,7 +110,6 @@ def generate_planet(engine: NyxEngine):
window_width = 480

# Start the engine
engine = NyxEngine()
engine.hemera_term_fx.run_line_profile = line_profiling
# Add required systems to loop
engine.add_system(MovementSystem())
Expand Down Expand Up @@ -211,7 +211,7 @@ def generate_planet(engine: NyxEngine):
engine.render_frame()
# Cull off-screen entities
engine.kill_entities()
sleep_time = NyxEngine.sec_per_game_loop - (datetime.now() - start_time).seconds
sleep_time = engine.sec_per_game_loop - (datetime.now() - start_time).seconds
time.sleep(sleep_time)


5 changes: 2 additions & 3 deletions nyx/aether_renderer/aether_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,10 @@ def _process_tilemap_component(self):
component (TilemapComponent): The component holding a tilemap array.
"""
from nyx.nyx_engine.nyx_engine import NyxEngine

engine = NyxEngine()
frame_w = self.dimensions.effective_window_w
frame_h = self.dimensions.effective_window_h

# NyxEngine.tilemap_manager.render_tilemap()
self.layered_frames[0] = NyxEngine.tilemap_manager.rendered_tilemap[
self.layered_frames[0] = engine.tilemap_manager.rendered_tilemap[
:frame_h, :frame_w
]
14 changes: 14 additions & 0 deletions nyx/base_classes/base_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from abc import ABC

from nyx.aether_renderer.aether_dimensions import AetherDimensions
from nyx.nyx_engine.nyx_engine import NyxEngine


class BaseManager:
@property
def engine(self) -> NyxEngine:
return NyxEngine()

@property
def dimensions(self) -> AetherDimensions:
return self.engine.aether_dimensions
10 changes: 8 additions & 2 deletions nyx/hemera_term_fx/term_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,11 @@ def cursor_abs_move(pixel_x: int, pixel_y: int) -> str:
@staticmethod
def get_terminal_dimensions() -> Tuple[int, int]:
"""Return the terminal size as a tuple of integers in (h, w) format."""
terminal_size = os.get_terminal_size()
return (terminal_size.lines, terminal_size.columns)
try:
terminal_size = os.get_terminal_size()
h, w = terminal_size.lines, terminal_size.columns
except OSError:
# Triggers when running in a non-terminal env., such as during testing
# 10, 10 was used simply because it is not 0, 0; which may cause bound issues
h, w = 10, 10
return h, w
46 changes: 23 additions & 23 deletions nyx/moirai_ecs/component/component_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,18 @@ class ComponentManager:
remove_entity(): Remove all components belonging to an entity.
"""

# Component Registry
component_registry: Dict[str, Dict[int, NyxComponent]] = {
"background-color": {},
"dimensions": {},
"position": {},
"scene": {},
"texture": {},
"tilemap": {},
"velocity": {},
"z-index": {},
}
def __init__(self):
# Component Registry
self.component_registry: Dict[str, Dict[int, NyxComponent]] = {
"background-color": {},
"dimensions": {},
"position": {},
"scene": {},
"texture": {},
"tilemap": {},
"velocity": {},
"z-index": {},
}

def add_component(
self, entity_id: int, component_name: str, component: NyxComponent
Expand All @@ -55,12 +56,12 @@ def add_component(
Raises:
ValueError: If the component type is already registered for that entity ID.
"""
if entity_id in ComponentManager.component_registry[component_name]:
if entity_id in self.component_registry[component_name]:
raise ValueError(
f'Component="{component_name}" already exists for entity={entity_id}'
)

ComponentManager.component_registry[component_name][entity_id] = component
self.component_registry[component_name][entity_id] = component

def get_component(self, entity_id: int, component_name: str) -> NyxComponent:
"""Get a component for an entity.
Expand All @@ -76,8 +77,8 @@ def get_component(self, entity_id: int, component_name: str) -> NyxComponent:
NyxComponent: The component retrieved.
"""

if entity_id in ComponentManager.component_registry[component_name]:
return ComponentManager.component_registry[component_name][entity_id]
if entity_id in self.component_registry[component_name]:
return self.component_registry[component_name][entity_id]
raise KeyError(
f'Entity={entity_id} not found in "{component_name}" component registry.'
)
Expand All @@ -95,13 +96,13 @@ def update_component(
Raises:
KeyError: If the entity_id is not found for that component type.
"""
if entity_id not in ComponentManager.component_registry[component_name]:
if entity_id not in self.component_registry[component_name]:
raise KeyError(
f'Entity={entity_id} not found in "{component_name}" component registry.'
)
ComponentManager.component_registry[component_name][entity_id] = component
self.component_registry[component_name][entity_id] = component

def destroy_component(self, entity_id: int, component_name: str):
def remove_component(self, entity_id: int, component_name: str):
"""Remove the a component from from the registry.

Args:
Expand All @@ -111,19 +112,18 @@ def destroy_component(self, entity_id: int, component_name: str):
Raises:
KeyError: If the entity_id is not found for that component type.
"""
if entity_id not in ComponentManager.component_registry[component_name]:
if entity_id not in self.component_registry[component_name]:
raise KeyError(
f'Entity={entity_id} not found in "{component_name}" component registry.'
)
del ComponentManager.component_registry[component_name][entity_id]
del self.component_registry[component_name][entity_id]

@staticmethod
def remove_entity(entity_id: int):
def remove_entity(self, entity_id: int):
"""Remove all components belonging to an entity.

Args:
entity_id (int): The entity ID of the entity to clear from the registry.
"""
for sub_dict in ComponentManager.component_registry.values():
for sub_dict in self.component_registry.values():
if entity_id in sub_dict.keys():
del sub_dict[entity_id]
28 changes: 18 additions & 10 deletions nyx/moirai_ecs/entity/moirai_entity_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
the path that life will follow; and Atropos cuts the thread, ending that life's journey.
"""

from typing import Dict
from nyx.moirai_ecs.component.component_manager import ComponentManager
from typing import Dict, TYPE_CHECKING
from nyx.moirai_ecs.entity.nyx_entity import NyxEntity

if TYPE_CHECKING:
from nyx.nyx_engine.nyx_engine import NyxEngine


class MoiraiEntityManager:
"""Holds and performs CRUD operations on registries for entities and their entity ids.
Expand All @@ -40,6 +42,12 @@ def reset_entity_registry(cls):
"""Clear the entity registry of all entities."""
cls.entity_registry.clear()

def __init__(self, engine: "NyxEngine"):
self.engine = engine
self.component_manager = self.engine.component_manager
self.component_registry = self.engine.component_registry
self.entity_registry: Dict[int, NyxEntity] = {}

def create_entity(self, friendly_name: str = "") -> NyxEntity:
"""Create a NyxEntity and add it to the entity registry.

Expand All @@ -51,7 +59,7 @@ def create_entity(self, friendly_name: str = "") -> NyxEntity:
"""

new_entity = NyxEntity(friendly_name=friendly_name.strip())
MoiraiEntityManager.entity_registry[new_entity.entity_id] = new_entity
self.entity_registry[new_entity.entity_id] = new_entity
return new_entity

def destroy_entity(self, entity_id: int):
Expand All @@ -60,9 +68,9 @@ def destroy_entity(self, entity_id: int):
Args:
entity_id (int): The entity ID to remove.
"""
if entity_id in MoiraiEntityManager.entity_registry:
del MoiraiEntityManager.entity_registry[entity_id]
ComponentManager.remove_entity(entity_id=entity_id)
if entity_id in self.entity_registry:
del self.entity_registry[entity_id]
self.component_manager.remove_entity(entity_id=entity_id)
return self

def is_alive(self, entity_id: int) -> bool:
Expand All @@ -74,7 +82,7 @@ def is_alive(self, entity_id: int) -> bool:
Returns:
bool: If the entity is alive.
"""
return entity_id in MoiraiEntityManager.entity_registry
return entity_id in self.entity_registry

def get_entity(self, entity_id: int) -> NyxEntity:
"""Get an entity from the entity list
Expand All @@ -85,13 +93,13 @@ def get_entity(self, entity_id: int) -> NyxEntity:
Returns:
NyxEntity: The entity with the specified entity ID.
"""
if entity_id in MoiraiEntityManager.entity_registry:
return MoiraiEntityManager.entity_registry[entity_id]
if entity_id in self.entity_registry:
return self.entity_registry[entity_id]

def get_all_entities(self) -> Dict[int, NyxEntity]:
"""Return a registry of all entities in this manager.

Returns:
Dict[int, NyxEntity]: The registry of NyxEntity objects.
"""
return MoiraiEntityManager.entity_registry
return self.entity_registry
3 changes: 1 addition & 2 deletions nyx/moirai_ecs/system/aether_bridge_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from typing import Dict, List, Tuple

import numpy as np
from nyx.moirai_ecs.component.component_manager import ComponentManager
from nyx.moirai_ecs.system.base_systems import BaseSystem


Expand All @@ -32,7 +31,7 @@ def __init__(self):

def update(self):
"""Gather all renderable entities and components, then pass them to AetherRenderer."""
component_registry = ComponentManager.component_registry
component_registry = self.engine.component_registry
renderable_entities = {}
# scene_entities = {}

Expand Down
12 changes: 10 additions & 2 deletions nyx/moirai_ecs/system/base_systems.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@
"""

from abc import ABC
from typing import TYPE_CHECKING

from nyx.moirai_ecs.entity.moirai_entity_manager import MoiraiEntityManager
if TYPE_CHECKING:
from nyx.nyx_engine.nyx_engine import NyxEngine


class BaseSystem(ABC):
"""The base system class, which all systems in the ECS architechture inherit from."""
"""The base system class, which all systems in the ECS architecture inherit from."""

@property
def engine(self) -> "NyxEngine":
"""Return the engine instance."""
from nyx.nyx_engine.nyx_engine import NyxEngine
return NyxEngine()
13 changes: 7 additions & 6 deletions nyx/moirai_ecs/system/movement_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@
"""

from typing import Dict
from nyx.moirai_ecs.component.component_manager import ComponentManager
from nyx.moirai_ecs.component.transform_components import (
PositionComponent,
VelocityComponent,
)
from nyx.moirai_ecs.entity.moirai_entity_manager import MoiraiEntityManager
from nyx.moirai_ecs.system.base_systems import BaseSystem
from nyx.nyx_engine.nyx_engine import NyxEngine

Expand All @@ -28,14 +26,17 @@ def update(self):
Note:
Calculates both the actual position and the position to render on the screen.
"""
entity_reg = MoiraiEntityManager.entity_registry
engine = self.engine
component_registry = engine.component_registry
entity_reg = engine.entity_manager.entity_registry
position_reg: Dict[int, PositionComponent] = (
ComponentManager.component_registry["position"]
component_registry["position"]
)
velocity_reg: Dict[int, VelocityComponent] = (
ComponentManager.component_registry["velocity"]
component_registry["velocity"]
)
dt = NyxEngine.sec_per_game_loop
engine = NyxEngine()
dt = engine.sec_per_game_loop

# Update the position of each entity
for entity_id, velocity_component in velocity_reg.items():
Expand Down
Loading
Loading