felflame 1.0.2 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,9 @@
1
- class FelFlame
2
- class Components
1
+ # frozen_string_literal: true
2
+
3
+ module FelFlame
4
+ module Components
3
5
  @component_map = []
4
- class <<self
5
- include Enumerable
6
+ class << self
6
7
  # Creates a new {FelFlame::ComponentManager component manager}.
7
8
  #
8
9
  # @example
@@ -21,13 +22,14 @@ class FelFlame
21
22
  raise(NameError.new, "Component Manager '#{component_name}' is already defined")
22
23
  end
23
24
 
24
-
25
25
  const_set(component_name, Class.new(FelFlame::ComponentManager) {})
26
+ update_const_cache
26
27
 
27
28
  attrs.each do |attr|
28
- if FelFlame::Components.const_get(component_name).method_defined?("#{attr}") || FelFlame::Components.const_get(component_name).method_defined?("#{attr}=")
29
- raise NameError.new "The attribute name \"#{attr}\" is already a method"
29
+ if FelFlame::Components.const_get(component_name).method_defined?(attr.to_s) || FelFlame::Components.const_get(component_name).method_defined?("#{attr}=")
30
+ raise NameError, "The attribute name \"#{attr}\" is already a method"
30
31
  end
32
+
31
33
  FelFlame::Components.const_get(component_name).attr_accessor attr
32
34
  end
33
35
  attrs_with_defaults.each do |attr, _default|
@@ -46,28 +48,54 @@ class FelFlame
46
48
  FelFlame::Components.const_get(component_name)
47
49
  end
48
50
 
49
- # Iterate over all existing component managers. You also call other enumerable methods instead of each, such as +each_with_index+ or +select+
50
- # @return [Enumerator]
51
- def each(&block)
52
- constants.each(&block)
51
+ # Stores the components managers in {FelFlame::Components}. This
52
+ # is needed because calling `FelFlame::Components.constants`
53
+ # will not let you iterate over the value of the constants
54
+ # but will instead give you an array of symbols. This caches
55
+ # the convertion of those symbols to the actual value of the
56
+ # constants
57
+ # @!visibility private
58
+ def const_cache
59
+ @const_cache || update_const_cache
60
+ end
61
+
62
+ # Updates the array that stores the constants.
63
+ # Used internally by FelFlame
64
+ # @!visibility private
65
+ def update_const_cache
66
+ @const_cache = constants.map do |constant|
67
+ const_get constant
68
+ end
69
+ end
70
+
71
+ # Forwards undefined methods to the array of constants
72
+ # if the array can handle the request. Otherwise tells
73
+ # the programmer their code errored
74
+ # @!visibility private
75
+ def respond_to_missing?(method, *)
76
+ if const_cache.respond_to? method
77
+ true
78
+ else
79
+ super
80
+ end
81
+ end
82
+
83
+ # Makes component module behave like arrays with additional
84
+ # methods for managing the array
85
+ # @!visibility private
86
+ def method_missing(method, *args, **kwargs, &block)
87
+ if const_cache.respond_to? method
88
+ const_cache.send(method, *args, **kwargs, &block)
89
+ else
90
+ super
91
+ end
53
92
  end
54
93
  end
55
94
  end
56
95
 
57
96
  # Component Managers are what is used to create individual components which can be attached to entities.
58
- # 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.
97
+ # 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 {#to_h} and {#update_attrs} methods instead.
59
98
  class ComponentManager
60
-
61
- # 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.
62
- # @return [Integer]
63
- attr_reader :id
64
-
65
- # A seperate attr_writer was made for documentation readability reasons.
66
- # Yard will list attr_reader is readonly which is my intention.
67
- # This value needs to be changable as it is set by other functions.
68
- # @!visibility private
69
- attr_writer :id
70
-
71
99
  # Allows overwriting the storage of triggers, such as for clearing.
72
100
  # This method should generally only need to be used internally and
73
101
  # not by a game developer.
@@ -104,24 +132,40 @@ class FelFlame
104
132
  def initialize(**attrs)
105
133
  # Prepare the object
106
134
  # (this is a function created with metaprogramming
107
- # in FelFlame::Components
135
+ # in FelFlame::Components)
108
136
  set_defaults
109
137
 
110
- # Generate ID
111
- new_id = self.class.data.find_index { |i| i.nil? }
112
- new_id = self.class.data.size if new_id.nil?
113
- @id = new_id
114
-
115
138
  # Fill params
116
139
  attrs.each do |key, value|
117
140
  send "#{key}=", value
118
141
  end
119
142
 
120
143
  # Save Component
121
- self.class.data[new_id] = self
144
+ self.class.push self
122
145
  end
123
146
 
124
- class <<self
147
+ class << self
148
+ # Makes component managers behave like arrays with additional
149
+ # methods for managing the array
150
+ # @!visibility private
151
+ def respond_to_missing?(method, *)
152
+ if _data.respond_to? method
153
+ true
154
+ else
155
+ super
156
+ end
157
+ end
158
+
159
+ # Makes component managers behave like arrays with additional
160
+ # methods for managing the array
161
+ # @!visibility private
162
+ def method_missing(method, *args, **kwargs, &block)
163
+ if _data.respond_to? method
164
+ _data.send(method, *args, **kwargs, &block)
165
+ else
166
+ super
167
+ end
168
+ end
125
169
 
126
170
  # Allows overwriting the storage of triggers, such as for clearing.
127
171
  # This method should generally only need to be used internally and
@@ -155,41 +199,28 @@ class FelFlame
155
199
 
156
200
  # @return [Array<Component>] Array of all Components that belong to a given component manager
157
201
  # @!visibility private
158
- def data
202
+ def _data
159
203
  @data ||= []
160
204
  end
161
-
162
- # Gets a Component from the given {id unique ID}. Usage is simular to how an Array lookup works.
163
- #
164
- # @example
165
- # # this gets the 'Health' Component with ID 7
166
- # FelFlame::Components::Health[7]
167
- # @param component_id [Integer]
168
- # @return [Component] Returns the Component that uses the given unique {id ID}, nil if there is no Component associated with the given {id ID}
169
- def [](component_id)
170
- data[component_id]
171
- end
172
-
173
- # Iterates over all components within the component manager.
174
- # Special Enumerable methods like +map+ or +each_with_index+ are not implemented
175
- # @return [Enumerator]
176
- def each(&block)
177
- data.compact.each(&block)
178
- end
179
- end
180
-
181
- # An alias for the {id ID Reader}
182
- # @return [Integer]
183
- def to_i
184
- id
185
205
  end
186
206
 
187
- # A list of entity ids that are linked to the component
188
- # @return [Array<Integer>]
207
+ # Entities that have this component
208
+ # @return [Array<Component>]
189
209
  def entities
190
210
  @entities ||= []
191
211
  end
192
212
 
213
+ # A single entity. Use this if you expect the component to only belong to one entity and you want to access it.
214
+ # @return [Component]
215
+ def entity
216
+ if entities.empty?
217
+ Warning.warn("This component belongs to NO entities but you called the method that is intended for components belonging to a single entity.\nYou may have a bug in your logic.")
218
+ elsif entities.length > 1
219
+ 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.")
220
+ end
221
+ entities.first
222
+ end
223
+
193
224
  # Update attribute values using a hash or keywords.
194
225
  # @return [Hash<Symbol, Value>] Hash of updated attributes
195
226
  def update_attrs(**opts)
@@ -200,38 +231,35 @@ class FelFlame
200
231
 
201
232
  # Execute systems that have been added to execute on variable change
202
233
  # @return [Boolean] +true+
234
+ # @!visibility private
203
235
  def attr_changed_trigger_systems(attr)
204
236
  systems_to_execute = self.class.attr_triggers[attr]
205
237
  systems_to_execute = [] if systems_to_execute.nil?
206
238
 
207
239
  systems_to_execute |= attr_triggers[attr] unless attr_triggers[attr].nil?
208
240
 
209
- systems_to_execute.sort_by(&:priority).reverse.each(&:call)
241
+ systems_to_execute.sort_by(&:priority).reverse_each(&:call)
210
242
  true
211
243
  end
212
244
 
213
- # Removes this component from the list and purges all references to this Component from other Entities, as well as its {id ID} and data.
245
+ # Removes this component from the list and purges all references to this Component from other Entities, as well as its data.
214
246
  # @return [Boolean] +true+.
215
247
  def delete
216
248
  addition_triggers.each do |system|
217
249
  system.clear_triggers component_or_manager: self
218
250
  end
219
- # This needs to be cloned because indices get deleted as
220
- # the remove command is called, breaking the loop if it
221
- # wasn't referencing a clone(will get Nil errors)
222
- iter = entities.map(&:clone)
223
- iter.each do |entity_id|
224
- FelFlame::Entities[entity_id].remove self #unless FelFlame::Entities[entity_id].nil?
251
+ entities.reverse_each do |entity|
252
+ entity.remove self
225
253
  end
226
- self.class.data[id] = nil
254
+ self.class._data.delete self
227
255
  instance_variables.each do |var|
228
256
  instance_variable_set(var, nil)
229
257
  end
230
258
  true
231
259
  end
232
260
 
233
- # @return [Hash<Symbol, Value>] A hash, where all the keys are attributes linked to their respective values.
234
- def attrs
261
+ # @return [Hash<Symbol, Value>] A hash, where all the keys are attributes storing their respective values.
262
+ def to_h
235
263
  return_hash = instance_variables.each_with_object({}) do |key, final|
236
264
  final[key.to_s.delete_prefix('@').to_sym] = instance_variable_get(key)
237
265
  end
@@ -242,8 +270,8 @@ class FelFlame
242
270
  # Export all data into a JSON String, which could then later be loaded or saved to a file
243
271
  # TODO: This function is not yet complete
244
272
  # @return [String] a JSON formatted String
245
- #def to_json
273
+ # def to_json
246
274
  # # should return a json or hash of all data in this component
247
- #end
275
+ # end
248
276
  end
249
277
  end
@@ -1,54 +1,53 @@
1
- class FelFlame
2
- class Entities
3
- # Holds the unique ID of this entity
4
- # @return [Integer]
5
- attr_reader :id
6
-
7
- # A seperate attr_writer was made for documentation readability reasons.
8
- # Yard will list attr_reader is readonly which is my intention.
9
- # This value needs to be changable as it is set by other functions.
10
- # @!visibility private
11
- attr_writer :id
1
+ # frozen_string_literal: true
12
2
 
3
+ module FelFlame
4
+ class Entities
13
5
  # Creating a new Entity
14
6
  # @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.
15
7
  # @return [Entity]
16
8
  def initialize(*components)
17
- # Assign new unique ID
18
- new_id = self.class.data.find_index(&:nil?)
19
- new_id = self.class.data.size if new_id.nil?
20
- self.id = new_id
21
-
22
9
  # Add each component
23
10
  add(*components)
24
-
25
- self.class.data[id] = self
11
+ self.class._data.push self
26
12
  end
27
13
 
28
- # 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.
14
+ # 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.
29
15
  # @return [Hash<Component_Manager, Array<Integer>>]
30
16
  def components
31
17
  @components ||= {}
32
18
  end
33
19
 
34
- # An alias for the {#id ID reader}
35
- # @return [Integer]
36
- def to_i
37
- id
20
+ # 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.
21
+ # @example
22
+ # @entity.component[@component_manager] # array notation(the standard)
23
+ # @entity.component(@component_manager) # method notation
24
+ # @param manager [ComponentManager] If you pass nil you can then use array notation to access the same value.
25
+ # @return [Component]
26
+ def component(manager = nil)
27
+ if manager.nil?
28
+ FelFlame::Entities.component_redirect.entity = self
29
+ FelFlame::Entities.component_redirect
30
+ else
31
+ if components[manager].nil?
32
+ raise "This entity(#{self}) doesnt have any components of this type: #{manager}"
33
+ elsif components[manager].length > 1
34
+ 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.")
35
+ end
36
+
37
+ components[manager].first
38
+ end
38
39
  end
39
40
 
40
- # Removes this Entity from the list and purges all references to this Entity from other Components, as well as its {id ID} and data.
41
+ # Removes this Entity from the list and purges all references to this Entity from other Components, as well as its data.
41
42
  # @return [Boolean] +true+
42
43
  def delete
43
- components.each do |component_manager, component_array|
44
- component_array.each do |component_id|
45
- component_manager[component_id].entities.delete(id)
46
- #self.remove FelFlame::Components.const_get(component_manager.name)[component_id]
44
+ components.each do |_component_manager, component_array|
45
+ component_array.reverse_each do |component|
46
+ component.entities.delete(self)
47
47
  end
48
48
  end
49
- FelFlame::Entities.data[id] = nil
49
+ FelFlame::Entities._data.delete self
50
50
  @components = {}
51
- @id = nil
52
51
  true
53
52
  end
54
53
 
@@ -58,12 +57,12 @@ class FelFlame
58
57
  def add(*components_to_add)
59
58
  components_to_add.each do |component|
60
59
  if components[component.class].nil?
61
- components[component.class] = [component.id]
62
- component.entities.push id
60
+ components[component.class] = [component]
61
+ component.entities.push self
63
62
  check_systems component, :addition_triggers
64
- elsif !components[component.class].include? component.id
65
- components[component.class].push component.id
66
- component.entities.push id
63
+ elsif !components[component.class].include? component
64
+ components[component.class].push component
65
+ component.entities.push self
67
66
  check_systems component, :addition_triggers
68
67
  end
69
68
  end
@@ -87,9 +86,10 @@ class FelFlame
87
86
  # @return [Boolean] +true+
88
87
  def remove(*components_to_remove)
89
88
  components_to_remove.each do |component|
90
- check_systems component, :removal_triggers if component.entities.include? id
91
- component.entities.delete id
92
- components[component.class].delete component.id
89
+ check_systems component, :removal_triggers if component.entities.include? self
90
+ component.entities.delete self
91
+ components[component.class].delete component
92
+ components.delete component.class if components[component.class].empty?
93
93
  end
94
94
  true
95
95
  end
@@ -97,39 +97,64 @@ class FelFlame
97
97
  # Export all data into a JSON String which can then be saved into a file
98
98
  # TODO: This function is not yet complete
99
99
  # @return [String] A JSON formatted String
100
- #def to_json() end
100
+ # def to_json() end
101
101
 
102
- class <<self
103
- include Enumerable
104
- # @return [Array<Entity>] Array of all Entities that exist
102
+ class << self
103
+ # Makes component managers behave like arrays with additional
104
+ # methods for managing the array
105
105
  # @!visibility private
106
- def data
107
- @data ||= []
106
+ def respond_to_missing?(method, *)
107
+ if _data.respond_to? method
108
+ true
109
+ else
110
+ super
111
+ end
112
+ end
113
+
114
+ # Makes component managers behave like arrays with additional
115
+ # methods for managing the array
116
+ # @!visibility private
117
+ def method_missing(method, *args, **kwargs, &block)
118
+ if _data.respond_to? method
119
+ _data.send(method, *args, **kwargs, &block)
120
+ else
121
+ super
122
+ end
108
123
  end
109
124
 
110
- # Gets an Entity from the given {id unique ID}. Usage is simular to how an Array lookup works
111
- #
112
- # @example
113
- # # This gets the Entity with ID 7
114
- # FelFlame::Entities[7]
115
- # @param entity_id [Integer]
116
- # @return [Entity] returns the Entity that uses the given unique ID, nil if there is no Entity associated with the given ID
117
- def [](entity_id)
118
- data[entity_id]
125
+ # Fancy method redirection for when the `component` method is called
126
+ # in an Entity
127
+ # WARNING: This method will not correctly work with multithreading
128
+ # @!visibility private
129
+ def component_redirect
130
+ if @component_redirect
131
+ else
132
+ @component_redirect = Object.new
133
+ @component_redirect.instance_variable_set(:@entity, nil)
134
+ @component_redirect.define_singleton_method(:entity) do
135
+ instance_variable_get(:@entity)
136
+ end
137
+ @component_redirect.define_singleton_method(:entity=) do |value|
138
+ instance_variable_set(:@entity, value)
139
+ end
140
+ @component_redirect.define_singleton_method(:[]) do |component_manager|
141
+ entity.component(component_manager)
142
+ end
143
+ end
144
+ @component_redirect
119
145
  end
120
146
 
121
- # Iterates over all entities. The data is compacted so that means index does not correlate to ID.
122
- # You also call other enumerable methods instead of each, such as +each_with_index+ or +select+
123
- # @return [Enumerator]
124
- def each(&block)
125
- data.compact.each(&block)
147
+ # @return [Array<Entity>] Array of all Entities that exist
148
+ # @!visibility private
149
+ def _data
150
+ @data ||= []
126
151
  end
127
152
 
128
153
  # Creates a new entity using the data from a JSON string
129
154
  # TODO: This function is not yet complete
130
155
  # @param json_string [String] A string that was exported originally using the {FelFlame::Entities#to_json to_json} function
131
156
  # @param opts [Keywords] What values(its {FelFlame::Entities#id ID} or the {FelFlame::ComponentManager#id component IDs}) should be overwritten TODO: this might change
132
- #def from_json(json_string, **opts) end
157
+ # def from_json(json_string, **opts) end
133
158
  end
134
159
  end
135
160
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FelFlame
4
+ module Order
5
+ # Sets the priority of all items passed into this method
6
+ # according to the order they were passed.
7
+ # If an array is one of the elements then it will give all
8
+ # of those elements in the array the same priority.
9
+ # @param sortables [(Systems and Array<Systems>) or (Scenes and Array<Scenes>)]
10
+ # @return [Boolean] +true+.
11
+ def self.sort(*sortables)
12
+ sortables.each_with_index do |sorted, index|
13
+ if sorted.respond_to? :priority
14
+ sorted.priority = index
15
+ else
16
+ sorted.each do |item|
17
+ item.priority = index
18
+ end
19
+ end
20
+ end
21
+ true
22
+ end
23
+ end
24
+ end
@@ -1,19 +1,27 @@
1
- class FelFlame
2
- class Scenes
3
- # The Constant name assigned to this Scene
4
- attr_reader :const_name
1
+ # frozen_string_literal: true
5
2
 
3
+ module FelFlame
4
+ class Scenes
6
5
  # Allows overwriting the storage of systems, such as for clearing.
7
6
  # This method should generally only need to be used internally and
8
7
  # not by a game developer/
9
8
  # @!visibility private
10
9
  attr_writer :systems
11
10
 
11
+ # How early this Scene should be executed in a list of Scenes
12
+ attr_accessor :priority
13
+
14
+ def priority=(priority)
15
+ @priority = priority
16
+ FelFlame::Stage.scenes = FelFlame::Stage.scenes.sort_by(&:priority)
17
+ priority
18
+ end
19
+
12
20
  # Create a new Scene using the name given
13
21
  # @param name [String] String format must follow requirements of a constant
14
- def initialize(name)
22
+ def initialize(name, priority: 0)
23
+ self.priority = priority
15
24
  FelFlame::Scenes.const_set(name, self)
16
- @const_name = name
17
25
  end
18
26
 
19
27
  # The list of Systems this Scene contains
@@ -33,25 +41,28 @@ class FelFlame
33
41
  # @return [Boolean] +true+
34
42
  def add(*systems_to_add)
35
43
  self.systems |= systems_to_add
36
- systems.sort_by!(&:priority)
37
- FelFlame::Stage.update_systems_list if FelFlame::Stage.scenes.include? self
44
+ self.systems = systems.sort_by(&:priority)
45
+ systems_to_add.each do |system|
46
+ system.scenes |= [self]
47
+ end
38
48
  true
39
49
  end
40
50
 
41
- # Removes any number of SystemS from this Scene
51
+ # Removes any number of Systems from this Scene
42
52
  # @return [Boolean] +true+
43
53
  def remove(*systems_to_remove)
44
54
  self.systems -= systems_to_remove
45
- systems.sort_by!(&:priority)
46
- FelFlame::Stage.update_systems_list if FelFlame::Stage.scenes.include? self
47
55
  true
48
56
  end
49
57
 
50
58
  # Removes all Systems from this Scene
51
59
  # @return [Boolean] +true+
52
60
  def clear
61
+ systems.each do |system|
62
+ system.scenes.delete self
63
+ end
53
64
  systems.clear
54
- FelFlame::Stage.update_systems_list if FelFlame::Stage.scenes.include? self
65
+ # FelFlame::Stage.update_systems_list if FelFlame::Stage.scenes.include? self
55
66
  true
56
67
  end
57
68
  end
@@ -1,19 +1,18 @@
1
- class FelFlame
2
- class Stage
3
- class <<self
1
+ # frozen_string_literal: true
2
+
3
+ module FelFlame
4
+ module Stage
5
+ class << self
4
6
  # Allows clearing of scenes and systems.
5
7
  # Used internally by FelFlame and shouldn't need to be ever used by developers
6
8
  # @!visibility private
7
- attr_writer :scenes, :systems
9
+ attr_writer :scenes
8
10
 
9
11
  # Add any number of Scenes to the Stage
10
12
  # @return [Boolean] +true+
11
13
  def add(*scenes_to_add)
12
14
  self.scenes |= scenes_to_add
13
- scenes_to_add.each do |scene|
14
- self.systems |= scene.systems
15
- end
16
- systems.sort_by!(&:priority)
15
+ self.scenes = scenes.sort_by(&:priority)
17
16
  true
18
17
  end
19
18
 
@@ -21,35 +20,20 @@ class FelFlame
21
20
  # @return [Boolean] +true+
22
21
  def remove(*scenes_to_remove)
23
22
  self.scenes -= scenes_to_remove
24
- update_systems_list
25
- true
26
- end
27
-
28
- # Updates the list of systems from the Scenes added to the Stage and make sure they are ordered correctly
29
- # This is used internally by FelFlame and shouldn't need to be ever used by developers
30
- # @return [Boolean] +true+
31
- # @!visibility private
32
- def update_systems_list
33
- systems.clear
34
- scenes.each do |scene|
35
- self.systems |= scene.systems
36
- end
37
- systems.sort_by!(&:priority)
38
23
  true
39
24
  end
40
25
 
41
26
  # Clears all Scenes that were added to the Stage
42
27
  # @return [Boolean] +true+
43
28
  def clear
44
- systems.clear
45
- scenes.clear
29
+ self.scenes.clear
46
30
  true
47
31
  end
48
32
 
49
- # Executes one frame of the game. This executes all the Systems in the Scenes added to the Stage. Systems that exist in two or more different Scenes will still only get executed once.
33
+ # Executes one frame of the game. This executes all the Scenes added to the Stage in order of their priority.
50
34
  # @return [Boolean] +true+
51
35
  def call
52
- systems.each(&:call)
36
+ self.scenes.each(&:call)
53
37
  true
54
38
  end
55
39
 
@@ -58,13 +42,6 @@ class FelFlame
58
42
  def scenes
59
43
  @scenes ||= []
60
44
  end
61
-
62
- # Stores systems in the order the stage manager needs to call them
63
- # This method should generally only need to be used internally and not by a game developer
64
- # @!visibility private
65
- def systems
66
- @systems ||= []
67
- end
68
45
  end
69
46
  end
70
47
  end