--[[
`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
]]--
Appkit = Appkit or {}
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