Developer Guide (Version 1.11)

Creating and Destroying Static Entities with Pools

The Entity system is currently on a path to deprecation in favor of the Lumberyard Component Entity System.

This topic covers issues related to handling static entities.

Entity Pool Bookmarks

When an entity is marked to be created through the pool, it is not instantiated during the level load process. Instead, an entity pool bookmark is generated for it. The bookmark contains several items:

  • Entity ID reserved for the entity, assigned when the level was exported. You will use this entity ID later to tell the system to create the entity.

  • Static instanced data that makes the entity unique. This includes the <EntityInfo> section from the mission.xml file, which contains area information, flow graph information, child/parent links, PropertiesInstance table, etc.

  • Serialized state of the entity if it has been returned to the pool in the past. See more details in Serialization.

In each entity's <EntityInfo> section in the mission.xml file (generated when the level is exported from the Editor), there's a CreatedThroughPool property. This property can be referenced from the SEntitySpawnParams struct. If set to TRUE, the EntityLoadManager module will not create a CEntity instance for the entity. Instead, it will delegate the static instanced data and reserved entity ID to the EntityPoolManager to create a bookmark.

CEntityLoadManager::ParseEntities SEntityLoadParams loadParams; if (ExtractEntityLoadParams(entityNode, loadParams)) { if (bEnablePoolUse && loadParams.spawnParams.bCreatedThroughPool) { CEntityPoolManager *pPoolManager = m_pEntitySystem->GetEntityPoolManager(); bSuccess = (pPoolManager && pPoolManager->AddPoolBookmark(loadParams)); } // Default to creating the entity if (!bSuccess) { EntityId usingId = 0; bSuccess = CreateEntity(loadParams, usingId); } }

Preparing a Static Entity

To prepare a static entity, call IEntityPoolManager::PrepareFromPool, passing in the entity ID associated with the static entity you want to create. In response, the following execution flow takes place:

  1. System determines if the request can be processed in this frame. It will attempt to queue up multiple requests per frame and spread them out. If the parameter bPrepareNow is set to TRUE or if no prepare requests have been handled this frame, the request will be handled immediately. Otherwise, it will be added to the queue. Inside CEntityPoolManager::LoadBookmarkedFromPool, the EntityLoadManager is requested to create the entity.


    Note: If this activity is happening in the Editor, the entity will simply have its Enable event called. This will mimic enabling the entity via Flow Graph (unhide it). In this situation, the execution flow skips to the final step.

  2. System searches for an entity container (either empty, or still in use) to hold the requested entity. The function CEntityPoolManager::GetPoolEntity looks through the active entity pools to find one that contains the entity class of the given static entity. Once the correct pool is found, the container is retrieved from it. The actual order is as follows:

    1. If a forcedPoolId (entity ID of one of the empty containers created to populate the pool) is requested, find that entity container and return it.

    2. If no forcedPoolId is requested, get an entity container from the inactive set (entity containers not currently in use).

    3. If no inactive containers are available, get one from the active set (entity containers currently in use). This action uses a "weight" value to determine which container to return. A special Lua function in the script is used to request weights for each empty container (CEntityPoolManager::GetReturnToPoolWeight). A negative weight means it should not be used at all if possible. The system might pass in an urgent flag, which means the pool is at its maximum size.

    4. If an empty container can still not be found, an urgent flag will be ignored and the system will try to grow the pool. This is only possible if the pool was not created at its maximum size (this happens when the maximum pool size is overridden for a level with a smaller maximum size). In this case, a new entity container is generated, added to the pool, and immediately used.

  3. The retrieved entity container, along with the static instanced data and reserved entity ID gathered from its bookmark, is passed on through the functionCEntityLoadManager::CreateEntity, which begins the Reload process. CreateEntity uses the provided entity container instead of creating a new CEntity instance. It will handle calling the Reload pipeline on the entity container, and then install all the static instanced data for the prepared static entity. The Reload pipeline is as follows:

    1. The function CEntity::ReloadEntity is called on the entity container. The CEntity instance will clean itself up internally and begin using the static instanced data of the entity being prepared. The Lua script also performs cleanup using the function OnBeingReused.

    2. The Entity system's salt buffer and other internal containers are updated to reflect that this entity container now holds the reserved entity ID and can be retrieved using it.

    3. Entity proxies are prompted to reload using the static instanced data provided. This is done by calling IEntityProxy::Reload; each proxy is expected to correctly reset itself with the new data provided. The Script proxy is always the first to be reloaded so that the Lua script can be correctly associated before the other proxies attempt to use it.

      If the game object is being used as the User proxy, all the game object extensions for the container are also prompted to reload. This is done by calling IGameObjectExtension::ReloadExtension on all extensions. If this function returns FALSE, the extension will be deleted. Once this is done, IGameObjectExtension::PostReloadExtension is called on all extensions. This behavior mimics the Init and PostInit logic. Each extension is expected to correctly reset itself with the new data provided.

  4. If any serialized data exists within the bookmark, the entity container is loaded with that data. This ensures that the static entity resumes the state it was in last time it was returned to the pool. This process is skipped if this is the first time the static entity is being prepared.

At this point, calling CEntity::GetEntity or CEntity::FindEntityByName will return the entity container that is now housing the static entity and its information.

Returning a Static Entity to the Pool

To return a static entity, call the function IEntityPoolManager::ReturnToPool. You must pass in the entity ID associated with the static entity. In response, the following execution flow takes place:

  1. The function CEntityPoolManager::ReturnToPool finds the bookmark and the entity pool containing the current entity container housing the static entity.

  2. Depending on the bSaveState argument, the CEntity instance is (saved) and its serialized information is added to the bookmark. This ensures that if the static entity is prepared again later, it will resume its current state.

  3. The entity container goes through the Reload process again. This time, however, the entity container is reloaded using its empty class, effectively removing all references to loaded assets/content and put it back into a minimal state.

At this point, calling CEntity::GetEntity or CEntity::FindEntityByName to find the static entity will return NULL.