How to use ZO ObjectPool

From ESOUI Wiki

Jump to: navigation, search

Page up-to-date to API Version 101032

Contents

Object pools

If you need a dynamic number of the same objects (controls e.g.) you can use an object pool to create these dynamically, for controls e.g. based on a virtual XML template.
We could create them on the fly whenever a new control is needed and remove them again when we do not need it anymore, but it is better to create a pool for the controls and reuse them later as creating a control is expensive and creating just 1000 plain controls in a loop will already freeze the game for a noticeable moment.

You acquire each object you need and it will be automatically re-used from the pool. An example are the inventory scroll list rows, or notebook lables (described here in the Simple Notebook Tutorial part 7: https://wiki.esoui.com/SimpleNotebookTutorial/part7).
Check ZO_ControlPool below if you need to only create controls with a standard name.

ZO_Object_Pool

Luckily ZOs already provides us with a pool class for objects that we can use.
Read more about ZO_ObjectPool in ESO's source code -> Comments by ZOs: https://github.com/esoui/esoui/blob/pts7.2/esoui/libraries/utility/zo_objectpool.lua#L1
2021-11-18, API 101032 Deadlands

A generic pool to contain "active" and "free" objects.  Active objects
    are typically objects which:
        1. Have a relatively high construction cost
        2. Are not lightweight enough to create many of them at once
        3. Tend to be reused as dynamic elements of a larger container.
        
    The pool should "rapidly" reach a high-water mark of contained objects
    which should flow between active and free states on a regular basis.
    
    Ideal uses of the ZO_ObjectPool would be to contain objects such as:
        1. Scrolling combat text
        2. Tracked quests
        3. Buff icons
        
    The pools are not intended to be used to track a dynamic set of 
    contained objects whose membership grows to a predetermined size.
    As such, do NOT use the pool to track:
        1. Chat filters
        2. Inventory slots
        3. Action buttons (unless creating something like AutoBar)
        
    A common usage pattern is instantiating templated controls.  To facilitate this
    without bloating your own code you should use ZO_ObjectPool_CreateControl which has
    been written here as a convenience.  It creates a control named "template"..id where
    id is an arbitrary value that will not conflict with other generated id's.
    
    If your system depends on having well-known names for controls, you should not use the
    convenience function.    

It can be used to create controls e.g., check ZO_ControlPool below. But you can also use it to just define objects and re-use them.

Factory / Setup function

A factory function is used to define the control's XML template to use, and add data to the controls etc., as it is first created.

Reset / Release function

An optional reset function can be used whenever it is released, to e.g. hide the controls again, if they are not needed anymore/atm.
As long as we do not have any special requirements we could use ZO_ObjectPool_DefaultResetControl ZO_ObjectPool_DefaultResetObject as reet function.
Attention: As of 2021-11 ZO's code will NOT automatically call "ZO_ObjectPool_DefaultResetControl" anymore if you leave the resetFunction parameter nil!
If your controls somehow are not hidden properly you need to set the resetFunctionto your parameter resetFunc at the call to ZO_ObjectPool:New(function() ...), ZO_ObjectPool_DefaultResetControl)!

Create a pool control

For creating a control we also can use the helper function ZO_ObjectPool_CreateNamedControl that is defined in the same file as the pool class.

The first argument to ZO_ObjectPool_CreateNamedControl is used as a prefix for the control name and will be combined with some number.
The second string is the name of a virtual control that will be used as a template for the newly created control. This means we need a virtual XML control with the name of MyXMLVirtualTemplateNameOfThePoolControl which defines the control type of our pool, the size and look, events, etc.
Read more about virtualization of XML controls: https://wiki.esoui.com/UI_XML#Virtualization
The third argument is the parent control of the new created control.

Get the next control from the pool

Use the method
pool:AcquireObject(objectKey)
where pool is your reference to ZO_ObjectPool:New(factoryFunction, resetFunction)

and objectKey is optional the number key of the object (if it already exists with a given key you can acquire this one again. If you do not specify the objectKey the function ZO_ObjectPool:GetNextFree() will return the next number of the objectKey for you dynamicalyfrom self.m_NextFree + 1

Release a pool control

Basically releasing of objects will be handled by the game (e.g. at a reload of the UI, logout, ...). You should not manually release the objects if not explicitly needed.
If you need to release a specific object of a pool you can use:

ZO_ObjectPool:ReleaseObject(objectKey)

objectKey is the same key used as the pool object was acquired via ZO_PoolObject:AcquireObject(objectKey) (Remember: If AcquireObject() was called without a given ojectKey, GetNextFree() has automatically created the number of objectKey for you from self.m_NextFree + 1).

If you need to release all objects of a pool you can use:

ZO_ObjectPool:ReleaseAllObjects()

Example syntax

This example will be creating a pool of controls of the virtual template MyXMLVirtualTemplateNameOfThePoolControl (CT_LABELs).
You could aslo just use a ZO_ControlPool instead (see below at other pool types).

 local factoryFunction(objectPool)
    return ZO_ObjectPool_CreateNamedControl("$(parent)MyControl", "MyXMLVirtualTemplateNameOfThePoolControl", objectPool, parentControlOfThePoolControl)
 end
local pool = ZO_ObjectPool:New(factoryFunction,  
   --OPTIONAL 2nd parameter "resetFunction" if needed (read above)
   function(controlOrObject) 
      --e.g. hide your control again if it's a list row, which needs to be hidden/reset on scroll etc.
      --controlOrObject:SetHidden(true) or simply use ZO_ObjectPool_DefaultResetControl as function for param resetFunctin, as described above
   end
)
 
--Get the next control from the pool
local myControl = pool:AcquireObject()

Virtual XML template for the control

<GuiXml>
    <Controls>
        <Label name="MyXMLVirtualTemplateNameOfThePoolControl" font="ZoFontWinT2" text="Some Test Label" virtual="true">
            <Anchor point="TOPLEFT" offsetX="10" offsetY="10" />
        </Label>
    </Controls>
</GuiXml>

Helping functions of ZO_ObjectPool

ZO_ObjectPool provides additional helper functions.

Default create object function

These functions can be passed in as default factoryFunction parameter of the ZO_ObjectPool:New(factoryFuncton, resetFunction).

function ZO_ObjectPool_CreateControl(templateName, objectPool, parentControl)
    return CreateControlFromVirtual(templateName, parentControl, templateName, objectPool:GetNextControlId())
end

function ZO_ObjectPool_CreateNamedControl(name, templateName, objectPool, parentControl)
    return CreateControlFromVirtual(name, parentControl, templateName, objectPool:GetNextControlId())
end

Default reset object functions

These functions can be passed in as default resetFunction parameter of the ZO_ObjectPool:New(factoryFuncton, resetFunction).

function ZO_ObjectPool_DefaultResetObject(object)
    object:Reset()
end

function ZO_ObjectPool_DefaultResetControl(control)
    control:SetHidden(true)
end


After factory function was called

-- Optional supplementary behavior that can be run after the factory has created the object
-- Often used in conjunction with extension pool classes (e.g.: ZO_ControlPool) when you want all the default factory behavior, plus something extra
function ZO_ObjectPool:SetCustomFactoryBehavior(customFactoryBehavior)

After reset function was called

-- Optional supplementary behavior that can be run after the primary reset function has been run on the object
-- Often used in conjunction with extension pool classes (e.g.: ZO_ControlPool) when you want all the default reset behavior, plus something extra
function ZO_ObjectPool:SetCustomResetBehavior(customResetBehavior)

Other functions

Please see at the ZOs source file: https://github.com/esoui/esoui/blob/pts7.2/esoui/libraries/utility/zo_objectpool.lua


-- Define the primary factory behavior
function ZO_ObjectPool:SetFactory(factoryFunctionOrObjectClass)

-- Define the primary reset behavior
function ZO_ObjectPool:SetResetFunction(resetFunction)

function ZO_ObjectPool:GetNextFree()

function ZO_ObjectPool:GetNextControlId()

function ZO_ObjectPool:GetTotalObjectCount()

function ZO_ObjectPool:GetActiveObjectCount()

function ZO_ObjectPool:HasActiveObjects()

function ZO_ObjectPool:GetActiveObjects()

function ZO_ObjectPool:GetActiveObject(objectKey)

function ZO_ObjectPool:GetFreeObjectCount()

function ZO_ObjectPool:SetCustomAcquireBehavior(customAcquireBehavior)

function ZO_ObjectPool:CreateObject(objectKey)

function ZO_ObjectPool:ResetObject(object)

function ZO_ObjectPool:DestroyFreeObject(objectKey, destroyFunction)

function ZO_ObjectPool:DestroyAllFreeObjects(destroyFunction)

Iterator functions of ZO_ObjectPool

Iterate over objects, with filter

Iterate over the objects of a pool, optionally using a filterfunction (must return boolean) to filter some objects.

ZO_ObjectPool:ActiveObjectIterator(filterFunctions)
Example
function ZO_MetaPool:ActiveObjectIterator(filterFunctions)
    return ZO_FilteredNonContiguousTableIterator(self.activeObjects, filterFunctions)
end

Iterate over active objects, with filter

Iterate over the active objects of a pool, optionally using a filterfunction (must return boolean) to filter some objects.

ZO_ObjectPool:ActiveAndFreeObjectIterator(filterFunctions)
Example
for _, control in self.bonusesControlPool:ActiveAndFreeObjectIterator() do
   control:MarkStyleDirty()
end

Other pool types (Sub-classes of ZO_ObjectPool)

Some other object pool types are defined in this ZOs file: https://github.com/esoui/esoui/blob/pts7.2/esoui/libraries/zo_templates/objectpooltemplates.lua


ZO_AnimationPool (providing animations)

https://github.com/esoui/esoui/blob/pts7.2/esoui/libraries/zo_templates/objectpooltemplates.lua#L5
Provides a special animation pool which calls ANIMATION_MANAGER:CreateTimelineFromVirtual(templateName) as default factoryFunction
and animationTimeline:Stop() as default resetFunction.

    local function AnimationReset(timeline)
        timeline:Stop()
    end
 
    function ZO_AnimationPool:Initialize(templateName)
        local function AnimationFactory()
            return ANIMATION_MANAGER:CreateTimelineFromVirtual(templateName)
        end
 
        ZO_ObjectPool.Initialize(self, AnimationFactory, AnimationReset)
    end
 
 
function ZO_ReversibleAnimationProvider:Initialize(virtualTimelineName)
    self.animationPool = ZO_AnimationPool:New(virtualTimelineName)
 
    self.animationPool:SetCustomFactoryBehavior(function(animationTimeline)
        animationTimeline:SetHandler("OnStop", function(timeline, completedPlaying)
            if completedPlaying and timeline:IsPlayingBackward() then
                local control = timeline:GetFirstAnimation():GetAnimatedControl()
                self.controlToAnimationTimeline[control] = nil
                local poolKey = self.animationTimelineToPoolKey[timeline]
                self.animationPool:ReleaseObject(poolKey)
                self.animationTimelineToPoolKey[timeline] = nil
            end
        end)
    end)
 
    self.controlToAnimationTimeline = {}
    self.animationTimelineToPoolKey = {}
end

ZO_ControlPool (providing controls)

https://github.com/esoui/esoui/blob/pts7.2/esoui/libraries/zo_templates/objectpooltemplates.lua#L25
A pool of controls, so basically the pool to use if you want to define controls, and not only objects.

It contains a standard reset function e.g.

local function ControlFactory(pool)
        return ZO_ObjectPool_CreateNamedControl(pool.name, pool.templateName, pool, pool.parent)
    end
 
    local function ControlReset(control)
        control:SetHidden(true)
        control:ClearAnchors()
    end
 
    function ZO_ControlPool:Initialize(templateName, parent, overrideName)
        ZO_ObjectPool.Initialize(self, ControlFactory, ControlReset)
 
        local controlName = overrideName or templateName
 
        parent = parent or GuiRoot
        if parent ~= GuiRoot then
            controlName = parent:GetName() .. controlName
        end
 
        self.name = controlName
        self.parent = parent
        self.templateName = templateName
    end

ZO_EntryDataPool (using a data source)

https://github.com/esoui/esoui/blob/pts7.2/esoui/libraries/zo_templates/objectpooltemplates.lua#L69

EntryData classes typically take dataSource in their constructors, so they normally do not use ZO_ObjectPool's default factory object behavior, which constructs with the pool and key.
Means you pass in a class to the :New(className) function which provides the datasource handling (See ZO_EntryData class below).

Data sources for an entry

DataSources should be an object of class ZO_DataSourceObject (https://github.com/esoui/esoui/blob/pts7.2/esoui/libraries/utility/baseobject.lua#L181)
Another dataSource type could be ZO_GridSquareEntryData_Shared for a grid list (https://github.com/esoui/esoui/blob/pts7.2/esoui/common/zo_entrydata/zo_gridsquareentrydata_shared.lua)

--The ZO_EntryDataPool:Initialize (called from :New(entryDataObjectClass)) function
function ZO_EntryDataPool:Initialize(entryDataObjectClass, factoryFunction, resetFunction)
    factoryFunction = factoryFunction or function()
        -- EntryData classes typically take dataSource in their constructors,
        -- so we can't use ZO_ObjectPool's default factory object behavior, which constructs with the pool and key
        return self.entryDataObjectClass:New()
    end
 
    resetFunction = resetFunction or function(entryData)
        entryData:SetDataSource(nil)
    end
 
    ZO_ObjectPool.Initialize(self, factoryFunction, resetFunction)
 
    self.entryDataObjectClass = entryDataObjectClass
end


--At the item sets book, used for the "grid list" entries
function ZO_ItemSetsBook_Keyboard:InitializeGridList()
...    
   self.entryDataObjectPool = ZO_EntryDataPool:New(ZO_EntryData) 
   --ZO_EntryData is the class that get's passed to ZO_EntryDataPool to define the data source -> See below at ZO_EntryData:Initialize(dataSource)
   --It calls ZO_EntryDataPool:Initialize with first parameter entryDataObjectClass = ZO_EntryData and factoryFunction = nil and resetFunction = nil.
   --So factoryFunction will be "return self.entryDataObjectClass:New(), means it will call ZO_EntryData:New() -> And here ZO_DataSourceObject.New(self) will be created and returned
...
end
 
--Class ZO_EntryData which handles the dataSource of the ZO_EntryDataPool entries
ZO_EntryData = ZO_DataSourceObject:Subclass()
 
function ZO_EntryData:New(...)
    local entryData = ZO_DataSourceObject.New(self)
    entryData:Initialize(...)
    return entryData
end
 
function ZO_EntryData:Initialize(dataSource)
    self:SetDataSource(dataSource)
end

ZO_MetaPool (using a pre-defined ZO_ObjectPool)

https://github.com/esoui/esoui/blob/pts7.2/esoui/libraries/zo_templates/objectpooltemplates.lua#L103

An object pool using a predefined sourcePool you pass to the New function. Skills, as one example, use this and pass in a pool via function SKILLS_DATA_MANAGER:GetSkillProgressionObjectPool()
which returns one of the defined ZO_ObjectPools, that were created as the SKILLS_DATA_MANAGER (class ZO_SkillsDataManager) was initialized).

ZO_MetaPool = ZO_Object:Subclass()
 
function ZO_MetaPool:New(sourcePool)
    local pool = ZO_Object.New(self)
    pool.sourcePool = sourcePool
    pool.activeObjects = {}
    return pool
end
 
--Skills
function ZO_SkillsDataManager:Initialize()
...
    self.activeSkillProgressionObjectPool = ZO_ObjectPool:New(ZO_ActiveSkillProgressionData, ZO_ObjectPool_DefaultResetObject)     --pre-defned ZO_ObjectPool, used via function ZO_SkillsDataManager:GetSkillProgressionObjectPool in ZO_SkillData:Initialize() -> ZO_MetaPool:New (see below)
    self.passiveSkillProgressionObjectPool = ZO_ObjectPool:New(ZO_PassiveSkillProgressionData, ZO_ObjectPool_DefaultResetObject)
...
end
 
function ZO_SkillsDataManager:GetSkillProgressionObjectPool(isPassive)
    if isPassive then
        return self.passiveSkillProgressionObjectPool
    else
        return self.activeSkillProgressionObjectPool
    end
end
 
----
ZO_SkillData = ZO_SkillData_Base:Subclass()
 
function ZO_SkillData:Initialize()
    self.skillProgressions = {}
    self.progressionObjectMetaPool = ZO_MetaPool:New(SKILLS_DATA_MANAGER:GetSkillProgressionObjectPool(self:IsPassive())) --SKILLS_DATA_MANAGER is the object of the class ZO_SkillsDataManager, see above
end
 
function ZO_SkillData:Reset()
    ZO_ClearTable(self.skillProgressions)
    self.progressionObjectMetaPool:ReleaseAllObjects()
end
Personal tools
Namespaces
Variants
Actions
Menu
Wiki
Toolbox