Source code for Elements.pyECSS.Entity

""" Entity classes, part of the Elements.pyECSS package
    
Elements.pyECSS (Entity Component Systems in a Scenegraph) package
@Copyright 2021-2022 Dr. George Papagiannakis
    
The Entity realted classes are the based aggregation of Components in Elements.pyECSS, 
based on the Composite design pattern

"""

from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Iterable, Iterator
from typing import Any, List
import uuid

from Elements.pyECSS.Component import Component, ComponentIterator
from Elements.pyECSS.System import System



[docs]class EntityDfsIterator(Iterator, ComponentIterator): """ This is a depth-first-iterator for Hierarchical Entities (Iterables) and their Components, based on the Iterator design pattern :param Iterator: [description] :type Iterator: [type] Here is the explanation for the code above: 1. This is a depth-first-iterator for Hierarchical Entities (Iterables) and their Components, based on the Iterator design pattern 2. We have a stack to keep track of the iterators that we have to go through. 3. We push the Entity's iterator on the stack, that is the iterator that iterates through the Entity's children. 4. We get the iterator from the top of the stack, and try to get the next child. 5. If we get a StopIteration exception, then we have reached the end of the iterator, and we pop it off the stack. 6. If we get a node, then we check if it is an Entity, and if it is, we push the Entity's iterator on the stack, and return the node. 7. If we get a node and it is not an Entity, then we return the node. """ # List stack to push/pop iterators
[docs] def __init__(self, entity: Entity) ->None: self._entity = entity #entity this iterator can access the children of # access underlying Entity List iterator self._entityIterator = iter(self._entity._children) self._stack: List[Iterator] = [] # store top level Entity List iterator in stack self._stack.append(self._entityIterator)
def __next__(self): """ The __next__() iterator method should return the next Entity in the graph, using a DFS algorithm. This is a "pythonic" iterator, based on standard python List iterators """ if (len(self._stack) == 0): raise StopIteration else: stackIter = self._stack[-1] # peak last stack element try: node = next(stackIter) # advance stack iterator to retrieve first child node in it except StopIteration: self._stack.pop() # remove top iterator as it has been exhausted return None else: if isinstance(node, Entity): self._stack.append(iter(node._children)) # push the new Entity's iterator on top of the stack to be parsed next() iteration return node #node is an Entity else: return node #node is Component
[docs]class Entity(Component): """ The main EntityI concrete class of glGA ECS This is the typical equivalent of a Group node in traditional scenegraphs or GameObject in Unity Engine It can contain several other Entity objects as children. It is an actual data aggregator container of Components. All the actuall operations and logic is performed by Systems and not the Components or Entity itself. """
[docs] def __init__(self, name=None, type=None, id=None) -> None: """ PEP 256 note this is how we declare the type of variables in Phython 3.6 and later. e.g. x: int=1 or x: List[int] = [1] """ super().__init__(name, type, id) self._children: List[Component]=[] self._parent = None
def print(self): """ Print out contents of Entity for Debug purposes only """ #print out name, type, id of this Entity and its components print(f" _______________________________________________________________ ") #create a local iterator of Entity's children debugIterator = iter(self._children) #call print() on all children (Concrete Components or Entities) while there are more children to traverse done_traversing = False while not done_traversing: try: comp = next(debugIterator) except StopIteration: done_traversing = True else: print(comp) #calls the component's __str__() comp.print() # recursive call of this method to traverse hierarchy def add(self, object: Component) ->None: self._children.append(object) object._parent = self def remove(self, object: Component) ->None: self._children.remove(object) object._parent = None def getChild(self, index) ->Component: if index < len(self._children): return self._children[index] else: return None def getChildByType(self, type) ->Component: for node in self._children: if node.type == type: return node return None def getParent(self) ->Component: return self._parent def getNumberOfChildren(self) -> int: return len(self._children) def isEntity(self) -> bool: return True def update(self, **kwargs) ->bool: return True def transform(self)->bool: """ Sample transform() only for subclassing here and debug purposes """ return False "" def accept(self, system: System): """ Accepts a class object to operate on the Component, based on the Visitor pattern. :param system: [a System object] :type system: [System] """ system.update() def init(self): """ abstract method to be subclassed for extra initialisation """ pass def __iter__(self) -> EntityDfsIterator: """ The __iter__() method normaly returns the iterator object itself, by default we return the depth-first-search iterator """ return EntityDfsIterator(self) def __str__(self): if (self._parent is not None): #in case this is not the root node return f"\n{self.getClassName()} \nname: {self._name}, \ntype: {self._type}, \nid: {self._id}, \nparent: {self._parent._name}" else: return f"\n{self.getClassName()} \nname: {self._name}, \ntype: {self._type}, \nid: {self._id}, \nparent: None (root node)"