summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorrealtradam <[email protected]>2021-06-10 19:57:56 -0400
committerrealtradam <[email protected]>2021-06-10 19:57:56 -0400
commit1145a2bf43d87256676f49ae9612ff0c49cd5901 (patch)
treec5c76095be31e32e6b1eca3da76820235fb8df75
parent26a23996164f3e056c440ac142c8eb71c892b501 (diff)
downloadFelECS-1145a2bf43d87256676f49ae9612ff0c49cd5901.tar.gz
FelECS-1145a2bf43d87256676f49ae9612ff0c49cd5901.zip
added proper implementation
-rw-r--r--component_manager.rb128
-rw-r--r--entity_manager.rb136
-rw-r--r--felflame.rb72
-rw-r--r--helpers/01_component.rb59
4 files changed, 233 insertions, 162 deletions
diff --git a/component_manager.rb b/component_manager.rb
index 33cb931..16d6300 100644
--- a/component_manager.rb
+++ b/component_manager.rb
@@ -6,24 +6,14 @@ class FelFlame
class Components
@component_map = []
class <<self
- def entity_destroyed(entity_id)
- #TODO: make it 'search by entity' to remove
- @component_map.delete(entity_id)
- constants.each do |component| #TODO: change delete to remove
- component.delete(entity_id) unless (component.signature & FelFlame::Entity.signatures[entity_id]).zero?
- end
- end
-
- #def entity_created(entity_id)
- # #TODO: probably delete this, I dont think its needed in the new system
- # constants.each do |component|
- # const_get(component.to_s).add(entity_id) unless (const_get(component.to_s).signature & FelFlame::Entity.signatures[entity_id]).zero?
- # end
- # @component_map[entity_id] = []
- #end
-
+ include Enumerable
+ # Creates a new component manager.
+ #
+ # @param component_name [String] Name of your new component manager. Must be stylized in the format of constants in Ruby
+ # @param attrs [:Symbols] New components made with this manager will include these symbols as accessors, the values of these accessors will default to nil
+ # @param attrs_with_defaults [Keywords] New components made with this manager will include these keywords as accessors, their defaults set to the values given to the keywords
def new(component_name, *attrs, **attrs_with_defaults)
- const_set(component_name, Class.new(FelFlame::Helper::BaseComponent) {})
+ const_set(component_name, Class.new(FelFlame::Helper::ComponentManagerTemplate) {})
attrs.each do |attr|
FelFlame::Components.const_get(component_name).attr_accessor attr
end
@@ -36,6 +26,110 @@ class FelFlame
end
end
end
+
+ # Iterate over all existing component managers. You also call other enumerable methods instead of each, such as `each_with_index` or `select`
+ # @return [Enumerator]
+ def each(&block)
+ constants.each(&block)
+ end
+ end
+ end
+ # Namespace for helper functions and inheritance classes
+ class Helper
+ # Component Managers are what is used to create individual components which can be attached to entities.
+ # When a Component is created from a Component Manager that has accessors given to it, you can set or get the values of those accessors using standard ruby message sending (e.g `@component.var = 5`), or by using the {#attrs} and {#update_attrs} methods instead.
+ class ComponentManagerTemplate
+ # Holds the {id unique ID} of a component. The {id ID} is only unique within the scope of the component manager it was created from.
+ attr_accessor :id
+
+ class <<self
+ # @return [Array] Array of all Components that belong to a given component manager
+ # @!visibility private
+ def data
+ @data ||= []
+ end
+
+ # Gets a Component from the given {id unique ID}. Usage is simular to how an Array lookup works.
+ # @param component_id [Integer]
+ # @return [Component] Returns the Component that uses the given unique {id ID}, nil if there is no Component associated with the given {id ID}
+ def [](component_id)
+ data[component_id]
+ end
+
+ # Creates a new component and sets the values of the attributes given to it. If an attritbute is not passed then it will remain as the default.
+ def new(**attrs)
+ new_component = super
+
+ # Generate ID
+ new_id = self.data.find_index { |i| i.nil? }
+ new_id = self.data.size if new_id.nil?
+ new_component.id = new_id
+
+ # Fill params
+ attrs.each do |key, value|
+ new_component.send "#{key}=", value
+ end
+
+ # Save Component
+ data[new_id] = new_component
+ end
+
+ # Iterates over all components within the component manager
+ # @return [Enumerator]
+ def each
+ data.each do |component|
+ yield component
+ end
+ end
+ end
+
+ # An alias for the ID reader
+ # @return [Integer]
+ def to_i
+ id
+ end
+
+ # A list of components that are linked to the component
+ # @return [Array]
+ def linked_entities
+ @linked_entities ||= []
+ end
+
+ # Update attribute values using a hash or keywords.
+ # @return Hash of updated attributes
+ def update_attrs(**opts)
+ opts.each do |key, value|
+ send "#{key}=", value
+ end
+ 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.
+ # @return [Boolean] true.
+ def delete
+ linked_entities.each do |entity_id|
+ FelFlame::Entities[entity_id].remove self
+ end
+ self.class.data[id] = nil
+ @linked_entities = nil
+ instance_variables.each do |var|
+ instance_variable_set(var, nil)
+ end
+ true
+ end
+
+ # @return [Hash] Returns a hash, where all the keys are attributes linked to their respective values.
+ def attrs
+ instance_variables.each_with_object({}) do |key, final|
+ final[key.to_s.delete_prefix('@').to_sym] = instance_variable_get(key)
+ end
+ end
+
+ # 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
+ # should return a json or hash of all data in this component
+ end
end
end
end
diff --git a/entity_manager.rb b/entity_manager.rb
index 177ce1a..f699c95 100644
--- a/entity_manager.rb
+++ b/entity_manager.rb
@@ -1,64 +1,108 @@
class FelFlame
class Entities
+ # Holds the unique ID of this entity
+ # @return [Integer]
attr_accessor :id
- def initialize(*signature)
- final_signature = 0
- signature.each do |sig|
- final_signature += sig
+ # Creating a new component
+ # @param components [Component] Can be any number of components, identical duplicated will be automatically purged however different components from the same component manager are allowed.
+ def initialize(*components)
+ # Assign new unique ID
+ new_id = self.class.data.find_index { |i| i.nil? }
+ new_id = self.class.data.size if new_id.nil?
+ self.id = new_id
+
+ # Add each component
+ components.uniq.each do |component|
+ add component
end
- @id = Entities.generate_new_id
- self.class.all.push self
- self.class.signatures.push final_signature
- Components.entity_created(@id)
+ self.class.data[id] = self
end
- class <<self
- # All entities that exist
- def all
- @all ||= []
+ # A hash that uses component manager constant names as keys, and where the values of those keys are arrays that contain the IDs of components attached to this entity.
+ # @return [Hash]
+ def components
+ @components ||= {}
+ end
+
+ # An alias for the {#id ID reader}
+ # @return [Integer]
+ def to_i
+ id
+ 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.
+ # @return [Boolean] true.
+ def delete
+ components.each do |component_manager, component_array|
+ component_array.each do |component_id|
+ FelFlame.const_get(
+ component_manager.to_s.delete_prefix('FelFlame::')
+ )[component_id].linked_entities.delete(id)
+ end
end
+ FelFlame::Entities.data[id] = nil
+ @id = nil
+ @components = nil
+ true
+ end
- def id_queue
- @id_queue ||= []
+ # Returns true when added, or false if it already belongs to the Entity
+ # Add a component to the Entity
+ # @param component [Component] A component created from any component manager
+ # @return [Boolean] true if component is added, false if it already is attached
+ def add component
+ if components[component.class.to_s.to_sym].nil?
+ components[component.class.to_s.to_sym] = [component.id]
+ component.linked_entities.push id
+ true
+ elsif !components[component.class.to_s.to_sym].include? component.id
+ components[component.class.to_s.to_sym].push component.id
+ component.linked_entities.push id
+ true
+ else
+ false
end
+ end
- def generate_new_id
- if id_queue.empty?
- all.size
- else
- id_queue.shift
- end
+ # Remove a component from the Entity
+ # @param component [Component] A component created from any component manager
+ # @return [Boolean] true if component is removed, false if it wasnt attached to component
+ def remove component
+ components[component.class.to_s.to_sym].delete component.id
+ if component.linked_entities.delete id
+ true
+ else
+ false
+ end
+ end
+
+ # 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
+
+ class <<self
+ include Enumerable
+ # @return [Array] Array of all Entities that exist
+ # @!visibility private
+ def data
+ @data ||= []
end
- # What components a given entity uses
- def signatures
- @signatures ||= []
+ # Gets an Entity from the given unique ID. Usage is simular to how an Array lookup works
+ # @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]
end
- def destroy_entity(entity_id)
- if all[entity_id].nil?
- puts 'Entity can not be destroyed, id out of bounds'
- elsif entity_id < all.size - 1
- Components.constants.each do |constant|
- unless (signatures[entity_id] & Components::const_get(constant).id).zero?
- Components::const_get(constant).delete(entity_id)
- end
- end
- all[entity_id] = nil
- signatures[entity_id] = nil
- id_queue.push entity_id
- elsif entity_id == all.size - 1
- Components.constants.each do |constant|
- unless (signatures[entity_id] & Components::const_get(constant).id).zero?
- Components::const_get(constant).delete(entity_id)
- end
- end
- all.pop
- signatures.pop
- else
- puts 'Unknown error with destroy_entity, entity not destroyed'
- end
+ # Iterates over all entities. In general when using ECS the use of this method should never be neccassary unless you are doing something very wrong, however I will not stop you.
+ # You also call other enumerable methods instead of each, such as `each_with_index` or `select`
+ # @return [Enumerator]
+ def each(&block)
+ data.each(&block)
end
end
end
diff --git a/felflame.rb b/felflame.rb
index 89c7f70..981211d 100644
--- a/felflame.rb
+++ b/felflame.rb
@@ -4,48 +4,38 @@ require_relative './system_manager.rb'
require_relative './scene_manager.rb'
require_relative './stage_manager.rb'
-class FelFlame
- class <<self
- def dump
- end
-
- def load
- end
-
- def Ent
- FelFlame::Entities
- end
-
- def Cmp
- FelFlame::Components
- end
-
- def Sys
- FelFlame::Systems
- end
-
- def Scn
- FelFlame::Scene
- end
+require_relative './helpers/00_tileset.rb'
+require_relative './helpers/01_component.rb'
- def Stg
- FelFlame::Stage
- end
-
- def const_missing(name)
- FelFlame.send name.to_s
- end
-
- def method_missing(name)
- if name[0] == name[0].upcase
- # TODO throw NameError
- super
- else
- super
- end
- end
+class FelFlame
+ # Creates and manages Entities. Allows accessing Entities using their {FelFlame::Entities#id ID}
+ #
+ # TODO: Improve Entity overview
+ class Entities
+ end
+ # Creates component managers and allows accessing them them under the {FelFlame::Components} namespace as Constants
+ #
+ # To see how component managers are used please look at the {FelFlame::Helper::ComponentManagerTemplate} documentation.
+ #
+ # TODO: Improve Component overview
+ class Components
+ end
+ # Creates an manages Systems.
+ #
+ # TODO: Improve System overview
+ class Systems
end
end
-FF = FelFlame # TODO Maybe find better solution?
-
+# An alias for {FelFlame}
+FF = FelFlame
+# An alias for {FelFlame::Entities}
+FF::Ent = FelFlame::Entities
+# An alias for {FelFlame::Components}
+FF::Cmp = FelFlame::Components
+# An alias for {FelFlame::Systems}
+FF::Sys = FelFlame::Systems
+# An alias for {FelFlame::Scenes}
+#FF::Sce = FelFlame::Scenes
+# An alias for {FelFlame::Stage}
+#FF::Stg = FelFlame::Stage
diff --git a/helpers/01_component.rb b/helpers/01_component.rb
index 580b5d8..f68a233 100644
--- a/helpers/01_component.rb
+++ b/helpers/01_component.rb
@@ -1,64 +1,7 @@
class FelFlame
class Helper
- class BaseComponent
- class <<self
- def signature
- @signature ||= FelFlame::Signature.create_new_signature\
- FelFlame::Helper::ComponentHelper.underscore(ancestors[0].name.split('::').last)
- end
-
- def data
- @data ||= []
- end
-
- def new(**opts)
- new_component = super
-
- # Generate ID
- new_id = self.data.find_index { |i| i.nil? }
- new_id = self.data.size if new_id.nil?
- new_component.id = new_id
-
- # Fill params
- opts.each do |key, value|
- new_component.send "#{key}=", value
- end
-
- # Save Component
- data[new_id] = new_component
- end
-
- #def add(entity_id)
- # data[entity_id] = new
- #end
-
- #def delete(entity_id)
- # data.delete entity_id
- #end
- end
-
- def set(**opts)
- opts.each do |key, value|
- send "#{key}=", value
- end
- end
-
- #def create_data(name, default = nil)
- # #TODO: fill this out
- #end
-
- def get #TODO: maybe optimize removing the @ symbol
- instance_variables.each_with_object({}) do |key, final|
- final[key.to_s.delete_prefix('@').to_sym] = instance_variable_get(key)
- end
- end
-
- def dump #TODO: Needs to get id and stuff?
- # should return a json or hash of all data in this component
- end
- end
- class Level < FelFlame::Helper::BaseComponent
+ class Level < FelFlame::Helper::ComponentManagerTemplate
class <<self
def data
@data ||= { add: [], remove: [], grid: FelFlame::Helper::Array2D.new }