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.
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(String) 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-examples
and idlekit-tools
repositories.
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:
Creation
An IEntity is created by its owner object using the IEntityLoaderService. The constructor of the IEntity(string instanceId, string staticDataId) will be invoked, creating the object and caching the instanceId
and staticDataId
. The id is then used in the Injection phase.
e.g: Content.cs
-> ctor(string,string)
public Content(string instanceId, string staticDataId)
{
_instanceId = instanceId;
_staticDataId = staticDataId;
}
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>();
_dataResolver = resolver.Resolve<IDataResolver>();
_contentData = resolver.Resolve<IContentData>(staticDataId);
_contentSavedData = resolver.Resolve<IContentSavedData>(instanceId);
}
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;
_dataResolver = 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.
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).
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.If the IEntity does not exist in the IContainer, the IEntityLoaderService determines how to create the IEntity by getting its associated IEntityData.
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 IDataLoaderService.If the IEntityData is found, the IEntityLoaderService invokes the constructor through the CreateEntity(String) method. The
Creation
phase of the Entity life cycle is then invoked.To prevent duplication of IEntities with the same
instanceId
and to enable dependency injection, as soon as the IEntity is created, it is bound in the IContainer based on itsinstanceId
. Then theInjection
phase of IEntity is invoked, where each dependency is created recursively. Any child Entities go through the same Entity Initialization Flow until all dependencies are fulfilled.After the
Injection
phase is completed and all dependencies have been resolved, Initialize() is called to perform setup logic.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.
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).
Once the process begins, the IEntity is passed into the IEntityLoaderService to be unloaded.
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.
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.