Appkit: /component_manager.lua — code sample - Stingray Lua API Reference

Appkit: /component_manager.lua — code sample

Code

--[[

`ComponentManager` provides a Lua Entity/Component system that use a Lua object
as the Entity.

NOTE - This is currently unrelated to the engine's Component and Entity system.
The appkit and game templates will be updated to use it at a later time.

The `ComponentManager` provides automatic component calls to update, render,
shutdown, etc and also allows for component-based aggregation of behaviors
using an arbitrary lua object shared among related components.

--------------------------------------------------------------------------------
Example Component:
--------------------------------------------------------------------------------

    -- The component's class
    SomeComponent = Appkit.class(SomeComponent)

    -- Register the component class with the ComponentManager
    Appkit.ComponentManager.give_manager(SomeComponent)

    -- Pass an object to the component's constructor so the component can
    -- register with the owner, and so it can obtain other components from the
    -- owner.
    function SomeComponent:init(owner, other_argument)
        self.owner = owner

        .
        .
        .

        SomeComponent.manager:add_component(owner, self, level) -- pass level to register for on_level_shutdown
    end

--------------------------------------------------------------------------------
Example Entity composition:
--------------------------------------------------------------------------------

    -- An object to attach components to
    local player = {}

    -- Add components, assuming two available Components named Health and DamageGiver
    local health = Health(player)
    local damage_giver = DamageGiver(player)

    -- At this point we have an object (player) with two components registered
    -- to it. The components will get updated every frame.

    -- To get a handle to a health component from an object
    local health = Health.manager:get(player)

    -- To clean up the object and its components
    Appkit.ComponentManager.remove_components(player)
    player = nil
]]--

require 'core/appkit/lua/class'

Appkit.ComponentManager = Appkit.class(Appkit.ComponentManager)
local ComponentManager = Appkit.ComponentManager

ComponentManager.managers = ComponentManager.managers or {}

ComponentManager.update_pre_world = 1
ComponentManager.update_post_world = 2

--------------------------------------------------------------------------------
-- This Lua ComponentManager is a temp system which will be replaced by
-- Components using the engine entity system

--------------------------------------------------------------------------------
-- ComponentManager Global functionality

function ComponentManager.give_manager(type, update_priority)
    type.manager = type.manager or ComponentManager(update_priority)
    type.get_manager = function()
        return type.manager
    end
end

-- Call this on an entity to unregister all of its components from their managers
-- and to call shutdown on each component.
function ComponentManager.remove_components(entity)
    local managers = ComponentManager.managers
    for _, manager in ipairs(managers) do
        local component_wrapper = manager.components<a id="exa_0__appkit___component__manager__lua_cat_entity"></a><span class="hasTooltip" data-reftooltip="cat_entity" data-reftooltip-refid="lua_ref"><a class="el" href="cat_entity.html">entity</a></span>
        if component_wrapper then
            local component = component_wrapper[1]
            if component.shutdown then
                component:shutdown()
            end
            manager.components<a id="exa_0__appkit___component__manager__lua_cat_entity"></a><span class="hasTooltip" data-reftooltip="cat_entity" data-reftooltip-refid="lua_ref"><a class="el" href="cat_entity.html">entity</a></span> = nil
        end
    end
end

--------------------------------------------------------------------------------
-- Instance functionality

function ComponentManager:init(update_priority)
    update_priority = update_priority or ComponentManager.update_pre_world

    local managers = ComponentManager.managers
    managers[#managers + 1] = self
    self.components = {}

    ComponentManager.add_update_handler(self, self.on_update, update_priority)
    ComponentManager.add_shutdown_handler(self, self.on_shutdown)
    ComponentManager.add_level_shutdown_handler(self, self.on_level_shutdown)
end

-- Call to add a component to an entity. An entity can be anything,
-- e.g. an empty table that represents an object, but must be unique.
-- If owning_level is passed, then component will receive an on_level_shutdown() callback
-- and will be removed from the component manager immediately before the level is destroyed
function ComponentManager:add_component(entity, component, owning_level)
    self.components<a id="exa_0__appkit___component__manager__lua_cat_entity"></a><span class="hasTooltip" data-reftooltip="cat_entity" data-reftooltip-refid="lua_ref"><a class="el" href="cat_entity.html">entity</a></span> = {component, owning_level}
end

-- Gets the component for the given entity or nil if this manager does not
-- have a component for the given entity. Max one component per entity per manager.
function ComponentManager:get(entity)
    local component_wrapper = self.components<a id="exa_0__appkit___component__manager__lua_cat_entity"></a><span class="hasTooltip" data-reftooltip="cat_entity" data-reftooltip-refid="lua_ref"><a class="el" href="cat_entity.html">entity</a></span>
    if not component_wrapper then return nil end
    return component_wrapper[1]
end

--------------------------------------------------------------------------------
-- Appkit Internal

-- Called when a world is ticked.
-- Handler function signature: on_update(object, dt)
ComponentManager.update_handlers = ComponentManager.update_handlers or {}

-- Called when the app is shutdown.
-- Handler function signature: on_shutdown(object)
ComponentManager.shutdown_handlers = ComponentManager.shutdown_handlers or {}

-- Called when a level is being destroyed, befor it is destroyed.
-- Handler function signature: on_level_shutdown(object, level)
ComponentManager.level_shutdown_handlers = ComponentManager.level_shutdown_handlers or {}

-- Priority in descending order starting from 1. For example all
-- handlers with priority 1 will update before all handlers with priority > 1.
function ComponentManager.add_update_handler(object, callback, priority)
    priority = priority or 1
    local update_handlers = ComponentManager.update_handlers
    local handler_group = update_handlers[priority]
    if not handler_group then
        handler_group = {}
        update_handlers[priority] = handler_group
    end
    handler_group[#handler_group + 1] = {callback, object}
end

function ComponentManager.remove_update_handler(object, callback)
    local handler_group
    local handler
    local update_handlers = ComponentManager.update_handlers
    for i = #update_handlers, 1, -1 do
        handler_group = update_handlers[i]
        for j = #handler_group, 1, -1 do
            handler = handler_group[j]
            if handler[1] == callback and handler[2] == object then
                table.remove(handler_group, j)
            end
        end
        if next(handler_group) == nil then table.remove(update_handlers, i) end
    end
end

local call_update_handlers = function (dt, priority)
    local handler_group = ComponentManager.update_handlers[priority]
    if not handler_group then return end

    for _, handler in ipairs(handler_group) do
        handler[1](handler[2], dt)
    end
end

function ComponentManager.add_shutdown_handler(object, callback)
    local shutdown_handlers = ComponentManager.shutdown_handlers
    shutdown_handlers[#shutdown_handlers + 1] = {callback, object}
end

function ComponentManager.remove_shutdown_handler(object, callback)
    local handler
    local shutdown_handlers = ComponentManager.shutdown_handlers
    for i = #shutdown_handlers, 1, -1 do
        handler = shutdown_handlers[i]
        if (handler[1] == callback and handler[2] == object) then
            table.remove(shutdown_handlers, i)
        end
    end
end

local call_shutdown_handlers = function ()
    local handler
    local shutdown_handlers = ComponentManager.shutdown_handlers
    for i = #shutdown_handlers, 1, -1 do
        handler = shutdown_handlers[i]
        handler[1](handler[2])
    end
end

function ComponentManager.add_level_shutdown_handler(object, callback)
    local level_shutdown_handlers = ComponentManager.level_shutdown_handlers
    level_shutdown_handlers[#level_shutdown_handlers + 1] = {callback, object}
end

function ComponentManager.remove_level_shutdown_handler(object, callback)
    local handler
    local level_shutdown_handlers = ComponentManager.level_shutdown_handlers
    for i = #level_shutdown_handlers, 1, -1 do
        handler = level_shutdown_handlers[i]
        if (handler[1] == callback and handler[2] == object) then
            table.remove(level_shutdown_handlers, i)
        end
    end
end

local call_level_shutdown_handlers = function (level)
    local handler
    local level_shutdown_handlers = ComponentManager.level_shutdown_handlers
    for i = #level_shutdown_handlers, 1, -1 do
        handler = level_shutdown_handlers[i]
        handler[1](handler[2], level)
    end
end

function ComponentManager.update_managers(dt, priority)
    call_update_handlers(dt, priority)
end

function ComponentManager.shutdown_managers()
    call_shutdown_handlers()
end

function ComponentManager.notify_managers_level_shutdown(level)
    call_level_shutdown_handlers()
end

function ComponentManager:on_update(dt)
    for entity, component_wrapper in pairs(self.components) do
        component_wrapper[1]:update(dt)
    end
end

function ComponentManager:on_level_shutdown(level)
    local components = self.components
    for entity, component_wrapper in pairs(components) do
        if component_wrapper[2] == level then component_wrapper[1]:on_level_shutdown(level) end
        components<a id="exa_0__appkit___component__manager__lua_cat_entity"></a><span class="hasTooltip" data-reftooltip="cat_entity" data-reftooltip-refid="lua_ref"><a class="el" href="cat_entity.html">entity</a></span> = nil
    end
    --print ("on level shutdown finished. manager: ", self, "#components: ", #components)
end

function ComponentManager:on_shutdown()
    local components = self.components
    for entity, component_wrapper in pairs(components) do
        component_wrapper[1]:shutdown()
    end
    self.components = {}
    --print ("on shutdown finished. manager: ", self, "#components: ", #components)
end

return ComponentManager