summaryrefslogtreecommitdiffhomepage
path: root/lib/felecs/system_manager.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/felecs/system_manager.rb')
-rw-r--r--lib/felecs/system_manager.rb258
1 files changed, 258 insertions, 0 deletions
diff --git a/lib/felecs/system_manager.rb b/lib/felecs/system_manager.rb
new file mode 100644
index 0000000..6cd260b
--- /dev/null
+++ b/lib/felecs/system_manager.rb
@@ -0,0 +1,258 @@
+# frozen_string_literal: true
+
+module FelECS
+ class Systems
+ # How early this System should be executed in a list of Systems
+ attr_accessor :priority
+
+ # The Constant name assigned to this System
+
+ # Allows overwriting the storage of triggers, such as for clearing.
+ # This method should generally only need to be used internally and
+ # not by a game developer.
+ # @!visibility private
+ attr_writer :addition_triggers, :removal_triggers, :attr_triggers
+
+ # Stores all the scenes this system is a part of.
+ attr_writer :scenes
+
+ def scenes
+ @scenes ||= []
+ end
+
+ def priority=(priority)
+ @priority = priority
+ scenes.each do |scene|
+ scene.systems = scene.systems.sort_by(&:priority)
+ end
+ end
+
+ # Stores references to components or their managers that trigger
+ # this component when a component or component from that manager
+ # is added to an entity.
+ # Do not edit this hash as it is managed by FelECS automatically.
+ # @return [Array<Component>]
+ def addition_triggers
+ @addition_triggers ||= []
+ end
+
+ # Stores references to components or their managers that trigger
+ # this component when a component or component from that manager
+ # is removed from an entity.
+ # Do not edit this hash as it is managed by FelECS automatically.
+ # @return [Array<Component>]
+ def removal_triggers
+ @removal_triggers ||= []
+ end
+
+ # Stores references to systems that should be triggered when an
+ # attribute from this manager is changed
+ # Do not edit this hash as it is managed by FelECS automatically.
+ # @return [Hash<Symbol, Array<Symbol>>]
+ def attr_triggers
+ @attr_triggers ||= {}
+ end
+
+ class << self
+ # Stores the systems in {FelECS::Components}. This
+ # is needed because calling `FelECS::Components.constants`
+ # will not let you iterate over the value of the constants
+ # but will instead give you an array of symbols. This caches
+ # the convertion of those symbols to the actual value of the
+ # constants
+ def const_cache
+ @const_cache || update_const_cache
+ end
+
+ # Updates the array that stores the constants.
+ # Used internally by FelECS
+ # @!visibility private
+ def update_const_cache
+ @const_cache = constants.map do |constant|
+ const_get constant
+ end
+ end
+
+ # Forwards undefined methods to the array of constants
+ # if the array can handle the request. Otherwise tells
+ # the programmer their code errored
+ # @!visibility private
+ def respond_to_missing?(method, *)
+ if const_cache.respond_to? method
+ true
+ else
+ super
+ end
+ end
+
+ # Makes system module behave like arrays with additional
+ # methods for managing the array
+ # @!visibility private
+ def method_missing(method, *args, **kwargs, &block)
+ if const_cache.respond_to? method
+ const_cache.send(method, *args, **kwargs, &block)
+ else
+ super
+ end
+ end
+ end
+
+ # Creates a new System which can be accessed as a constant under the namespace {FelECS::Systems}.
+ # The name given is what constant the system is assigned to
+ #
+ # @example
+ # FelECS::Systems.new('PassiveHeal', priority: -2) do
+ # FelECS::Components::Health.each do |component|
+ # component.hp += 5
+ # end
+ # end
+ # # Give it a low priority so other systems such as a
+ # # Poison system would kill the player first
+ #
+ # @param name [String] The name this system will use. Needs to to be in the Ruby Constant format.
+ # @param priority [Integer] Which priority order this system should be executed in relative to other systems. Higher means executed earlier.
+ # @param block [Proc] The code you wish to be executed when the system is triggered. Can be defined by using a +do end+ block or using +{ }+ braces.
+ def initialize(name, priority: 0, &block)
+ FelECS::Systems.const_set(name, self)
+ FelECS::Systems.update_const_cache
+ @priority = priority
+ @block = block
+ @scenes = []
+ end
+
+ # Manually execute the system a single time
+ def call
+ @block.call
+ end
+
+ # Redefine what code is executed by this System when it is called upon.
+ # @param block [Proc] The code you wish to be executed when the system is triggered. Can be defined by using a +do end+ block or using +{ }+ braces.
+ def redefine(&block)
+ @block = block
+ end
+
+ # Removes triggers from this system. This function is fairly flexible so it can accept a few different inputs
+ # For addition and removal triggers, you can optionally pass in a component, or a manager to clear specifically
+ # the relevant triggers for that one component or manager. If you do not pass a component or manager then it will
+ # clear triggers for all components and managers.
+ # For attr_triggers
+ # @example
+ # # To clear all triggers that execute this system when a component is added:
+ # FelECS::Systems::ExampleSystem.clear_triggers :addition_triggers
+ # # Same as above but for when a component is removed instead
+ # FelECS::Systems::ExampleSystem.clear_triggers :removal_triggers
+ # # Same as above but for when a component has a certain attribute changed
+ # FelECS::Systems::ExampleSystem.clear_triggers :attr_triggers
+ #
+ # # Clear a trigger from a specific component
+ # FelECS::Systems::ExampleSystem.clear_triggers :addition_triggers, FelECS::Component::ExampleComponent[0]
+ # # Clear a trigger from a specific component manager
+ # FelECS::Systems::ExampleSystem.clear_triggers :addition_triggers, FelECS::Component::ExampleComponent
+ #
+ # # Clear the trigger that executes a system when the ':example_attr' is changes
+ # FelECS::Systems::ExampleSystem.clear_triggers :attr_triggers, :example_attr
+ # @param trigger_types [:Symbols] One or more of the following trigger types: +:addition_triggers+, +:removal_triggers+, or +:attr_triggers+. If attr_triggers is used then you may pass attributes you wish to be cleared as symbols in this parameter as well
+ # @param component_or_manager [Component or ComponentManager] The object to clear triggers from. Use Nil to clear triggers from all components associated with this system.
+ # @return [Boolean] +true+
+ def clear_triggers(*trigger_types, component_or_manager: nil)
+ trigger_types = %i[addition_triggers removal_triggers attr_triggers] if trigger_types.empty?
+
+ if trigger_types.include? :attr_triggers
+ if (trigger_types - %i[addition_triggers
+ removal_triggers
+ attr_triggers]).empty?
+
+ if component_or_manager.nil?
+ # remove all attrs
+ attr_triggers.each do |cmp_or_mgr, attrs|
+ attrs.each do |attr|
+ next if cmp_or_mgr.attr_triggers[attr].nil?
+
+ cmp_or_mgr.attr_triggers[attr].delete self
+ end
+ self.attr_triggers = {}
+ end
+ else
+ # remove attrs relevant to comp_or_man
+ unless attr_triggers[component_or_manager].nil?
+ attr_triggers[component_or_manager].each do |attr|
+ component_or_manager.attr_triggers[attr].delete self
+ end
+ attr_triggers[component_or_manager] = []
+ end
+ end
+
+ elsif component_or_manager.nil?
+
+ (trigger_types - %i[addition_triggers removal_triggers attr_triggers]).each do |attr|
+ # remove attr
+ attr_triggers.each do |cmp_or_mgr, _attrs|
+ cmp_or_mgr.attr_triggers[attr].delete self
+ end
+ end
+ attr_triggers.delete(trigger_types - %i[addition_triggers
+ removal_triggers
+ attr_triggers])
+ else
+ # remove attr from component_or_manager
+ (trigger_types - %i[addition_triggers removal_triggers attr_triggers]).each do |attr|
+ next if component_or_manager.attr_triggers[attr].nil?
+
+ component_or_manager.attr_triggers[attr].delete self
+ end
+ attr_triggers[component_or_manager] -= trigger_types unless attr_triggers[component_or_manager].nil?
+
+ end
+ end
+
+ (trigger_types & %i[removal_triggers addition_triggers] - [:attr_triggers]).each do |trigger_type|
+ if component_or_manager.nil?
+ # remove all removal triggers
+ send(trigger_type).each do |trigger|
+ trigger.send(trigger_type).delete self
+ end
+ send("#{trigger_type}=", [])
+ else
+ # remove removal trigger relevant to comp/man
+ send(trigger_type).delete component_or_manager
+ component_or_manager.send(trigger_type).delete self
+ end
+ end
+ true
+ end
+
+ # Add a component or component manager so that it triggers this system when the component or a component from the component manager is added to an entity
+ # @param component_or_manager [Component or ComponentManager] The component or component manager to trigger this system when added
+ # @return [Boolean] +true+
+ def trigger_when_added(component_or_manager)
+ self.addition_triggers |= [component_or_manager]
+ component_or_manager.addition_triggers |= [self]
+ true
+ end
+
+ # Add a component or component manager so that it triggers this system when the component or a component from the component manager is removed from an entity
+ # @param component_or_manager [Component or ComponentManager] The component or component manager to trigger this system when removed
+ # @return [Boolean] +true+
+ def trigger_when_removed(component_or_manager)
+ self.removal_triggers |= [component_or_manager]
+ component_or_manager.removal_triggers |= [self]
+ true
+ end
+
+ # Add a component or component manager so that it triggers this system when a component's attribute is changed.
+ # @return [Boolean] +true+
+ def trigger_when_is_changed(component_or_manager, attr)
+ if component_or_manager.attr_triggers[attr].nil?
+ component_or_manager.attr_triggers[attr] = [self]
+ else
+ component_or_manager.attr_triggers[attr] |= [self]
+ end
+ if attr_triggers[component_or_manager].nil?
+ attr_triggers[component_or_manager] = [attr]
+ else
+ attr_triggers[component_or_manager] |= [attr]
+ end
+ true
+ end
+ end
+end