Skip to content

Understanding Entities

Overview

An IdleKit game is made up of many different Entities of different concepts that make up the core foundation of the game. Examples of IdleKit Entities include Stages, Generators, Modifiers, and Currencies.

Note

For more on extending or overriding entities, see Customizing Entities.

Components

Entities within IdleKit follow the MVC pattern. In practice, this means that each Entity has a Controller (within the IEntity class), Static Data, Data Asset, and optional Saved Data. A full IEntity will implement the following classes:

  • [EntityName].cs — I[EntityName], IEntity
    • The controller containing the logic performed by the Entity
  • [EntityName]Data.cs — I[EntityName]Data, IEntityData
    • The static data for the IEntity which is loaded at runtime.
    • Responsible for the creation of the IEntity Controller and SavedData via the CreateEntity method.
  • [EntityName]DataAsset.cs — I[EntityName]DataAsset, ScriptableObject
    • The ScriptableObject that contains the Data. This is required for implementation in Unity. It also works with IGuidReferenceableAsset to connect Entities by id.
  • [EntityName]SavedData.cs — ISavedData
    • The serialized user data for the Entity. This is the dynamic data used by the IEntity controller which changes during gameplay.

Note

The View component of an IdleKit Entity would be implemented by the UI layer of a game. In order to be as flexible as possible, IdleKit does not provide any UI components. However, sample implementations can be found within the idlekit-tools section of IdleKit, and samples folder in the idlekit-showcase repository, or within the idlekit-layoutui-showcase repository.

Entity Life Cycle

IdleKit uses the Dependency Container to manage the way Entities are loaded and dependencies are handled. The IEntity life cycle contains multiple phases:

lifecycle

Creation

An IEntity is created by its owner object using the IEntityLoaderService. The constructor of the IEntity(string id) will be invoked, creating the object and caching the id. The id is then used in the Injection phase.

e.g: Content.cs -> ctor(string,string)

public Content(string id)
{
    _id = id;
}

Injection

After the IEntity is created, it is injected into the Dependency Container. In this phase, all of the Entity dependencies, such as required Services or the dependent Entities, are loaded. In turn, each of these Entities goes through the creation and injection process, making the loading process recursive. Before exiting this phase, the first IEntity created has been resolved, as well as all of its dependencies.

Note

For more information on how the injection works, refer to the Dependency Container.

e.g: Content.cs -> void Inject(IResolver)

public virtual void Inject(IResolver resolver)
{
  _entityLoaderService = resolver.Resolve<IEntityLoaderService>();
  _actionService = resolver.Resolve<IActionService>();
  _dataService = resolver.Resolve<IDataService>();

  _contentData = _dataService.GetData<IContentData>(Id);
  _contentSavedData = resolver.Resolve<IContentSavedData>(Id);
}

Initialization

Initialize is immediately called after the injection phase. In this phase, setup logic would be performed on the IEntity, which includes logic such as registration with the ITimerService, error validation, or subscribing to Actions. After this phase, the IEntity has been fully loaded and will persist until the Cleanup method within an IEntity is invoked.

e.g: Content.cs -> void Initialize()

public virtual void Initialize()
{
  BeginStage();

  if (!ContentSavedData.HasStarted)
  {
       NewContentStateAction newContentStateAction = _actionService.Get<NewContentStateAction>();
       newContentStateAction.Initialize(this);
       _actionService.Dispatch(newContentStateAction, this);
  }

  ContentInitializedAction contentInitializedAction = _actionService.Get<ContentInitializedAction>();
  contentInitializedAction.Content = this;
  _actionService.Dispatch(contentInitializedAction);
}

protected virtual void BeginStage()
{
    _currentStage = _entityLoaderService.LoadEntity<IStage, IStageData>(CurrentStageId);

    SetAvailableCurrencies();
}

Cleanup

Cleanup is called on the IEntity by the owner when it needs to be unloaded. This step works similarly to the Injection phase, but is unloading dependencies instead of loading. It is important to call Cleanup when references are no longer used, for example when switching Contents.

e.g: Content.cs -> void Cleanup(IEntityLoaderService)

public virtual void Cleanup(IEntityLoaderService entityLoaderService)
{
    EndStage();

    _contentData = null;
    _contentSavedData = null;

    _entityLoaderService = null;
    _actionService = null;
    _dataService = null;
}

protected virtual void EndStage()
{
    _entityLoaderService.UnloadEntity(_currentStage);
    _currentStage = null;
}

Entity Initialization Flow

The different phases of an IEntity initialization is triggered by the IEntityLoaderService. It encompasses the Entity Life Cycle and uses various components in IdleKit to create a new IEntity. initialization_flow

  1. The IEntity is initialized when it is required by another IEntity or when the owning IEntity is initialized (e.g.: IStage is initialized when the owner IContent is initialized).

  2. Initialization starts with a check to see if an IEntity with the given Id already exists in the IContainer. If the IEntity already exists, it has previously been initialized by another owner. Instead of being reloaded, the IEntity is retrieved from the Container and the Entity's initialization process is complete.

  3. If the IEntity does not exist in the IContainer, the IEntityLoaderService determines how to create the IEntity by getting its associated IEntityData.

  4. If the IEntityData is not found in the IContainer, a BindingException is thrown. Only the IEntityData contains the information (Type, method, ids) necessary to create the IEntity, so it is important to make sure the IEntityData is registered in the IContainer via the IDataService.

  5. If the IEntityData is found, the IEntityLoaderService invokes the constructor through the CreateEntity method. The Creation phase of the Entity life cycle is then invoked.

  6. To prevent duplication of IEntities with the same id and to enable dependency injection, as soon as the IEntity is created, it is bound in the IContainer based on its id. Then the Injection phase of IEntity is invoked, where each dependency is created recursively. All child Entities go through the same Entity Initialization Flow until all dependencies are fulfilled.

  7. After the Injection phase is completed and all dependencies have been resolved, Initialize is called to perform setup logic.

  8. Since this is the first instance of this IEntity, the reference of the IEntity loaded is tracked and an EntityAddedAction is dispatched to notify any listeners that a new IEntity has been initialized.

Entity Cleanup Flow

Since an IEntity is created and initialized by an owner, it also has to be cleaned up by its owner. Below shows the cleanup flow of an IEntity. cleanup_flow

  1. The cleanup process begins when the IEntity is no longer required (e.g. when the previous IStage is not required after ascending to a new IStage within an IContent) or when the owner IEntity is cleaned up (eg. the IContent that owns IStage is cleaned up when switching to a different IContent).

  2. Once the process begins, the IEntity is passed into the IEntityLoaderService to be unloaded.

  3. If the current IEntity is not referenced by another owner, the associated IEntityData and ISavedData is unbound from the IContainer. An EntityRemovedAction is then dispatched on the IEntity. If the IEntity is referenced by another Entity that is not cleaned up, the cleanup process is aborted.

  4. The Cleanup method on the IEntity is invoked, where all of the Entity dependencies are unloaded and cleaned up by setting them to null and initiating the Cleanup Flow on any child Entities. This starts the same process recursively on the child Entities until all Entities are cleaned up.