--[[ `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