Code Sample – Game Engine Core

public class Engine
{
  private var systems_          :Vector.<ISystem> = null;
  private var injector_         :Injector         = null;
  private var entitiesToDestroy_:Vector.<Entity>  = null;
  
  public function Engine(stage:Stage)
  {
    systems_           = new Vector.<ISystem>();
    injector_          = new Injector();
    entitiesToDestroy_ = new Vector.<Entity>();
    
    injector_.map(Stage).toValue(stage);
    injector_.map(Engine).toValue(this);
  }
  
  public function getInjector():Injector 
  { return injector_; }
  
  public function update(dt:Number):void
  {
    var i:int, len:int;
    for (i = 0, len = systems_.length; i < len; ++i)
    {
      if (systems_[i].active) systems_[i].update(dt);
    }
    
    //remove entities marked for destruction from engine
    for 
    (
      i = 0, len = entitiesToDestroy_.length; 
      i < len; ++i
    )
    {
      var entity:Entity = entitiesToDestroy_[i];
      
      //destroy components
      entity.dispose();
      
      //unroll dependency
      injector_.unmap(Entity, entity.name());
      entity.setInjector(null);
    }
    entitiesToDestroy_.length = 0;
  }
  
  public function addSystem(SystemClass:Class, ...params):*
  {
    //check duplicate system
    if (injector_.satisfies(SystemClass))
      throw new Error
        ("System " + SystemClass + " already added.");
    
    //add system to engine
    var system:ISystem = construct(SystemClass, params);
    
    //map injector
    injector_.map(SystemClass).toValue(system);
    
    //initialize system
    systems_.push(system);
    system.setInjector(injector_);
    
    injector_.injectInto(system);
    system.init();
    
    return system;
  }
  
  public function removeSystem(SystemClass:Class):void
  {
    //check system existence
    if (!injector_.satisfies(SystemClass)) 
      throw new Error
        ("System " + SystemClass + " not found.");
    
    var system:ISystem = getInstance(SystemClass);
    
    //dispose system
    system.dispose();
    
    //remove system from engine
    systems_.splice(systems_.indexOf(system), 1);
    system.setInjector(null);
    
    //unmap injector
    injector_.unmap(SystemClass);
  }
  
  public function getInstance(SystemClass:Class):*
  {
    return injector_.getInstance(SystemClass);
  }
  
  private var entityCounter_:uint = 0;
  public function createEntity(name:String = null):Entity
  {
    //check duplicate entity name
    if (injector_.satisfies(Entity, name)) 
      throw new Error
        ("Entity named\"" + name + "\" already exists.");
    
    if (!name) name = "__entity" + entityCounter_++;
    
    //add entity to engine
    var entity:Entity = new Entity(name);
    injector_.map(Entity, name).toValue(entity);
    
    //inject child injector
    var childInjector:Injector = 
      injector_.createChildInjector();
    
    childInjector.map(Entity).toValue(entity);
    entity.setInjector(childInjector);
    
    return entity;
  }
  
  public function destroyEntity(name:String):void
  {
    //check entity name existence
    if (!injector_.satisfies(Entity, name)) 
      throw new Error
        ("Entity named\"" + name + "\" does not exist.");
    
    entitiesToDestroy_.push(getEntity(name));
  }
  
  public function getEntity(name:String):Entity
  {
    return injector_.getInstance(Entity, name);
  }
}

2 Responses to Code Sample – Game Engine Core

  1. Hi Allen,

    I’m looking up a few different component-based game engines to get some inspiration on creating my own.

    I’m curious though, in yours (and some other ones I stumbled upon)… you keep track of “entitiesToDestroy”. Some engines also have something like “entitiesToAdd” to delay the list / array / vector manipulation until later at the end of the game-cycle.

    Can you share some info why this is necessary? Also, can you think of any situations where that may be a problem? Personally, I’m thinking if you try to access the component / entity immediately after adding it (expecting it to be part of the list), then that could crash, right?

    • Allen Chou says:

      You’d always want to defer the actual entity deletion until the end of each frame, so that you won’t crash the game when some system or component tries to access an entity that has already been destroyed in the same frame.

      As for the “entitiesToAdd” scenario, I’d imagine that you would do this if you want to allow one entity to access another entity, where both are added within the same frame, independent of the order they are added in. So when two entities are added, they are both added to the name-entity map in the engine, and added to the “entitiesToAdd” list. At the end of the frame, the engine loops through the “entitiesToAdd” list, and then invoke, say, Entity::OnAdded methods on both of the entities, which in turn invoke the Component::OnOwnerAdded methods on the components. If the Component::OnOwnerAdded method of a component owned by one entity contains game logic that accesses the other entity, it will be able to find what it is looking for, even if the desired entity was added after the owner entity of the component.

      There is no “correct” design or “the right” game engine architecture. It all depends what you want out of your engine.

Leave a Reply