From a0f792d8feadf919290b8349dbc0eac143545927 Mon Sep 17 00:00:00 2001 From: _Tradam Date: Mon, 3 Jan 2022 08:26:24 -0500 Subject: Major 4.0 Update (#16) See Changelog --- lib/felflame.rb | 36 ++++---- lib/felflame/component_manager.rb | 169 ++++++++++++++++++++++---------------- lib/felflame/entity_manager.rb | 124 +++++++++++++++++----------- lib/felflame/order.rb | 24 ++++++ lib/felflame/scene_manager.rb | 35 +++++--- lib/felflame/stage_manager.rb | 43 +++------- lib/felflame/system_manager.rb | 139 ++++++++++++++++++++----------- lib/felflame/version.rb | 2 +- 8 files changed, 345 insertions(+), 227 deletions(-) create mode 100644 lib/felflame/order.rb (limited to 'lib') diff --git a/lib/felflame.rb b/lib/felflame.rb index cf58990..21a6eaf 100644 --- a/lib/felflame.rb +++ b/lib/felflame.rb @@ -1,14 +1,17 @@ +# frozen_string_literal: true + require_relative 'felflame/entity_manager' require_relative 'felflame/component_manager' require_relative 'felflame/system_manager' require_relative 'felflame/scene_manager' require_relative 'felflame/stage_manager' +require_relative 'felflame/order' -require_relative "felflame/version" +require_relative 'felflame/version' # The FelFlame namespace where all its functionality resides under. -class FelFlame - class < 1 + Warning.warn("This component belongs to MANY entities but you called the method that is intended for components belonging to a single entity.\nYou may have a bug in your logic.") + end + entities.first + end + # Update attribute values using a hash or keywords. # @return [Hash] Hash of updated attributes def update_attrs(**opts) @@ -200,39 +231,35 @@ class FelFlame # Execute systems that have been added to execute on variable change # @return [Boolean] +true+ + # @!visibility private def attr_changed_trigger_systems(attr) systems_to_execute = self.class.attr_triggers[attr] systems_to_execute = [] if systems_to_execute.nil? systems_to_execute |= attr_triggers[attr] unless attr_triggers[attr].nil? - systems_to_execute.sort_by(&:priority).reverse.each(&:call) + systems_to_execute.sort_by(&:priority).reverse_each(&:call) true end - # Removes this component from the list and purges all references to this Component from other Entities, as well as its {id ID} and data. + # Removes this component from the list and purges all references to this Component from other Entities, as well as its data. # @return [Boolean] +true+. def delete addition_triggers.each do |system| system.clear_triggers component_or_manager: self end - # This needs to be cloned because indices get deleted as - # the remove command is called, breaking the loop if it - # wasn't referencing a clone(will get Nil errors) - iter = entities.map(&:clone) - iter.each do |entity| - #FelFlame::Entities[entity_id].remove self #unless FelFlame::Entities[entity_id].nil? + entities.reverse_each do |entity| entity.remove self end - self.class.data[id] = nil + self.class._data.delete self instance_variables.each do |var| instance_variable_set(var, nil) end true end - # @return [Hash] A hash, where all the keys are attributes linked to their respective values. - def attrs + # @return [Hash] A hash, where all the keys are attributes storing their respective values. + def to_h return_hash = instance_variables.each_with_object({}) do |key, final| final[key.to_s.delete_prefix('@').to_sym] = instance_variable_get(key) end @@ -243,8 +270,8 @@ class FelFlame # Export all data into a JSON String, which could then later be loaded or saved to a file # TODO: This function is not yet complete # @return [String] a JSON formatted String - #def to_json + # def to_json # # should return a json or hash of all data in this component - #end + # end end end diff --git a/lib/felflame/entity_manager.rb b/lib/felflame/entity_manager.rb index a05ef93..ef70510 100644 --- a/lib/felflame/entity_manager.rb +++ b/lib/felflame/entity_manager.rb @@ -1,53 +1,53 @@ -class FelFlame - class Entities - # Holds the unique ID of this entity - # @return [Integer] - attr_reader :id - - # A seperate attr_writer was made for documentation readability reasons. - # Yard will list attr_reader is readonly which is my intention. - # This value needs to be changable as it is set by other functions. - # @!visibility private - attr_writer :id +# frozen_string_literal: true +module FelFlame + class Entities # Creating a new Entity # @param components [Components] Can be any number of components, identical duplicates will be automatically purged however different components from the same component manager are allowed. # @return [Entity] def initialize(*components) - # Assign new unique ID - new_id = self.class.data.find_index(&:nil?) - new_id = self.class.data.size if new_id.nil? - self.id = new_id - # Add each component add(*components) - - self.class.data[id] = self + self.class._data.push self end - # A hash that uses component manager constant names as keys, and where the values of those keys are arrays that contain the {FelFlame::ComponentManager#id IDs} of the components attached to this entity. + # A hash that uses component manager constant names as keys, and where the values of those keys are arrays that contain the the components attached to this entity. # @return [Hash>] def components @components ||= {} end - # An alias for the {#id ID reader} - # @return [Integer] - def to_i - id + # A single component from a component manager. Use this if you expect the component to only belong to one entity and you want to access it. Access the component using either parameter notation or array notation. Array notation is conventional for better readablility. + # @example + # @entity.component[@component_manager] # array notation(the standard) + # @entity.component(@component_manager) # method notation + # @param manager [ComponentManager] If you pass nil you can then use array notation to access the same value. + # @return [Component] + def component(manager = nil) + if manager.nil? + FelFlame::Entities.component_redirect.entity = self + FelFlame::Entities.component_redirect + else + if components[manager].nil? + raise "This entity(#{self}) doesnt have any components of this type: #{manager}" + elsif components[manager].length > 1 + Warning.warn("This entity has MANY of this component but you called the method that is intended for having a single of this component type.\nYou may have a bug in your logic.") + end + + components[manager].first + end end - # Removes this Entity from the list and purges all references to this Entity from other Components, as well as its {id ID} and data. + # Removes this Entity from the list and purges all references to this Entity from other Components, as well as its data. # @return [Boolean] +true+ def delete - components.each do |component_manager, component_array| - component_array.each do |component| + components.each do |_component_manager, component_array| + component_array.reverse_each do |component| component.entities.delete(self) end end - FelFlame::Entities.data[id] = nil + FelFlame::Entities._data.delete self @components = {} - @id = nil true end @@ -89,6 +89,7 @@ class FelFlame check_systems component, :removal_triggers if component.entities.include? self component.entities.delete self components[component.class].delete component + components.delete component.class if components[component.class].empty? end true end @@ -96,39 +97,64 @@ class FelFlame # Export all data into a JSON String which can then be saved into a file # TODO: This function is not yet complete # @return [String] A JSON formatted String - #def to_json() end + # def to_json() end - class <] Array of all Entities that exist + class << self + # Makes component managers behave like arrays with additional + # methods for managing the array # @!visibility private - def data - @data ||= [] + def respond_to_missing?(method, *) + if _data.respond_to? method + true + else + super + end + end + + # Makes component managers behave like arrays with additional + # methods for managing the array + # @!visibility private + def method_missing(method, *args, **kwargs, &block) + if _data.respond_to? method + _data.send(method, *args, **kwargs, &block) + else + super + end end - # Gets an Entity from the given {id unique ID}. Usage is simular to how an Array lookup works - # - # @example - # # This gets the Entity with ID 7 - # FelFlame::Entities[7] - # @param entity_id [Integer] - # @return [Entity] returns the Entity that uses the given unique ID, nil if there is no Entity associated with the given ID - def [](entity_id) - data[entity_id] + # Fancy method redirection for when the `component` method is called + # in an Entity + # WARNING: This method will not correctly work with multithreading + # @!visibility private + def component_redirect + if @component_redirect + else + @component_redirect = Object.new + @component_redirect.instance_variable_set(:@entity, nil) + @component_redirect.define_singleton_method(:entity) do + instance_variable_get(:@entity) + end + @component_redirect.define_singleton_method(:entity=) do |value| + instance_variable_set(:@entity, value) + end + @component_redirect.define_singleton_method(:[]) do |component_manager| + entity.component(component_manager) + end + end + @component_redirect end - # Iterates over all entities. The data is compacted so that means index does not correlate to ID. - # You also call other enumerable methods instead of each, such as +each_with_index+ or +select+ - # @return [Enumerator] - def each(&block) - data.compact.each(&block) + # @return [Array] Array of all Entities that exist + # @!visibility private + def _data + @data ||= [] end # Creates a new entity using the data from a JSON string # TODO: This function is not yet complete # @param json_string [String] A string that was exported originally using the {FelFlame::Entities#to_json to_json} function # @param opts [Keywords] What values(its {FelFlame::Entities#id ID} or the {FelFlame::ComponentManager#id component IDs}) should be overwritten TODO: this might change - #def from_json(json_string, **opts) end + # def from_json(json_string, **opts) end end end end diff --git a/lib/felflame/order.rb b/lib/felflame/order.rb new file mode 100644 index 0000000..c11438d --- /dev/null +++ b/lib/felflame/order.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module FelFlame + module Order + # Sets the priority of all items passed into this method + # according to the order they were passed. + # If an array is one of the elements then it will give all + # of those elements in the array the same priority. + # @param sortables [(Systems and Array) or (Scenes and Array)] + # @return [Boolean] +true+. + def self.sort(*sortables) + sortables.each_with_index do |sorted, index| + if sorted.respond_to? :priority + sorted.priority = index + else + sorted.each do |item| + item.priority = index + end + end + end + true + end + end +end diff --git a/lib/felflame/scene_manager.rb b/lib/felflame/scene_manager.rb index 315dd55..0024815 100644 --- a/lib/felflame/scene_manager.rb +++ b/lib/felflame/scene_manager.rb @@ -1,19 +1,27 @@ -class FelFlame - class Scenes - # The Constant name assigned to this Scene - attr_reader :const_name +# frozen_string_literal: true +module FelFlame + class Scenes # Allows overwriting the storage of systems, such as for clearing. # This method should generally only need to be used internally and # not by a game developer/ # @!visibility private attr_writer :systems + # How early this Scene should be executed in a list of Scenes + attr_accessor :priority + + def priority=(priority) + @priority = priority + FelFlame::Stage.scenes = FelFlame::Stage.scenes.sort_by(&:priority) + priority + end + # Create a new Scene using the name given # @param name [String] String format must follow requirements of a constant - def initialize(name) + def initialize(name, priority: 0) + self.priority = priority FelFlame::Scenes.const_set(name, self) - @const_name = name end # The list of Systems this Scene contains @@ -33,25 +41,28 @@ class FelFlame # @return [Boolean] +true+ def add(*systems_to_add) self.systems |= systems_to_add - systems.sort_by!(&:priority) - FelFlame::Stage.update_systems_list if FelFlame::Stage.scenes.include? self + self.systems = systems.sort_by(&:priority) + systems_to_add.each do |system| + system.scenes |= [self] + end true end - # Removes any number of SystemS from this Scene + # Removes any number of Systems from this Scene # @return [Boolean] +true+ def remove(*systems_to_remove) self.systems -= systems_to_remove - systems.sort_by!(&:priority) - FelFlame::Stage.update_systems_list if FelFlame::Stage.scenes.include? self true end # Removes all Systems from this Scene # @return [Boolean] +true+ def clear + systems.each do |system| + system.scenes.delete self + end systems.clear - FelFlame::Stage.update_systems_list if FelFlame::Stage.scenes.include? self + # FelFlame::Stage.update_systems_list if FelFlame::Stage.scenes.include? self true end end diff --git a/lib/felflame/stage_manager.rb b/lib/felflame/stage_manager.rb index 87ee955..a192b67 100644 --- a/lib/felflame/stage_manager.rb +++ b/lib/felflame/stage_manager.rb @@ -1,19 +1,18 @@ -class FelFlame - class Stage - class <