felecs 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.byebug_history +20 -0
  3. data/.gitignore +17 -0
  4. data/.inch.yml +11 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +13 -0
  7. data/.ruby-version +1 -0
  8. data/CHANGELOG.mdown +80 -0
  9. data/Gemfile +10 -0
  10. data/Gemfile.lock +87 -0
  11. data/LICENSE +21 -0
  12. data/README.mdown +465 -0
  13. data/Rakefile +66 -0
  14. data/bin/console +15 -0
  15. data/bin/setup +8 -0
  16. data/docs/CNAME +1 -0
  17. data/docs/FelECS/ComponentManager.html +1239 -0
  18. data/docs/FelECS/Components.html +337 -0
  19. data/docs/FelECS/Entities.html +792 -0
  20. data/docs/FelECS/Order.html +251 -0
  21. data/docs/FelECS/Scenes.html +765 -0
  22. data/docs/FelECS/Stage.html +572 -0
  23. data/docs/FelECS/Systems.html +1505 -0
  24. data/docs/FelECS.html +335 -0
  25. data/docs/FelFlame/ComponentManager.html +1239 -0
  26. data/docs/FelFlame/Components.html +333 -0
  27. data/docs/FelFlame/Entities.html +792 -0
  28. data/docs/FelFlame/Helper/ComponentManager.html +1627 -0
  29. data/docs/FelFlame/Helper.html +142 -0
  30. data/docs/FelFlame/Order.html +251 -0
  31. data/docs/FelFlame/Scenes.html +765 -0
  32. data/docs/FelFlame/Stage.html +572 -0
  33. data/docs/FelFlame/Systems.html +1505 -0
  34. data/docs/FelFlame.html +319 -0
  35. data/docs/Felflame_.html +143 -0
  36. data/docs/_index.html +188 -0
  37. data/docs/class_list.html +51 -0
  38. data/docs/css/common.css +1 -0
  39. data/docs/css/full_list.css +58 -0
  40. data/docs/css/style.css +497 -0
  41. data/docs/file.README.html +560 -0
  42. data/docs/file.version.html +74 -0
  43. data/docs/file_list.html +56 -0
  44. data/docs/frames.html +17 -0
  45. data/docs/index.html +560 -0
  46. data/docs/js/app.js +314 -0
  47. data/docs/js/full_list.js +216 -0
  48. data/docs/js/jquery.js +4 -0
  49. data/docs/method_list.html +419 -0
  50. data/docs/top-level-namespace.html +137 -0
  51. data/felecs.gemspec +45 -0
  52. data/lib/felecs/component_manager.rb +279 -0
  53. data/lib/felecs/entity_manager.rb +160 -0
  54. data/lib/felecs/order.rb +24 -0
  55. data/lib/felecs/scene_manager.rb +69 -0
  56. data/lib/felecs/stage_manager.rb +47 -0
  57. data/lib/felecs/system_manager.rb +258 -0
  58. data/lib/felecs/version.rb +9 -0
  59. data/lib/felecs.rb +67 -0
  60. data/mrbgem/mrbgem.rake +4 -0
  61. data/mrbgem/mrblib/felecs.rb +913 -0
  62. metadata +229 -0
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FelECS
4
+ module Components
5
+ @component_map = []
6
+ class << self
7
+ # Creates a new {FelECS::ComponentManager component manager}.
8
+ #
9
+ # @example
10
+ # # Here color is set to default to red
11
+ # # while max and current are nil until set.
12
+ # # When you make a new component using this component manager
13
+ # # these are the values and accessors it will have.
14
+ # FelECS::Component.new('Health', :max, :current, color: 'red')
15
+ #
16
+ # @param component_name [String] Name of your new component manager. Must be stylized in the format of constants in Ruby
17
+ # @param attrs [:Symbols] New components made with this manager will include these symbols as accessors, the values of these accessors will default to nil
18
+ # @param attrs_with_defaults [Keyword: DefaultValue] New components made with this manager will include these keywords as accessors, their defaults set to the values given to the keywords
19
+ # @return [ComponentManager]
20
+ def new(component_name, *attrs, **attrs_with_defaults)
21
+ if FelECS::Components.const_defined?(component_name)
22
+ raise(NameError.new, "Component Manager '#{component_name}' is already defined")
23
+ end
24
+
25
+ const_set(component_name, Class.new(FelECS::ComponentManager) {})
26
+ update_const_cache
27
+
28
+ attrs.each do |attr|
29
+ if FelECS::Components.const_get(component_name).method_defined?(attr.to_s) || FelECS::Components.const_get(component_name).method_defined?("#{attr}=")
30
+ raise NameError, "The attribute name \"#{attr}\" is already a method"
31
+ end
32
+
33
+ FelECS::Components.const_get(component_name).attr_accessor attr
34
+ end
35
+ attrs_with_defaults.each do |attr, _default|
36
+ attrs_with_defaults[attr] = _default.dup
37
+ FelECS::Components.const_get(component_name).attr_reader attr
38
+ FelECS::Components.const_get(component_name).define_method("#{attr}=") do |value|
39
+ unless value.equal? send(attr)
40
+ instance_variable_set("@#{attr}", value)
41
+ attr_changed_trigger_systems(attr)
42
+ end
43
+ end
44
+ end
45
+ FelECS::Components.const_get(component_name).define_method(:set_defaults) do
46
+ attrs_with_defaults.each do |attr, default|
47
+ instance_variable_set("@#{attr}", default.dup)
48
+ end
49
+ end
50
+ FelECS::Components.const_get(component_name)
51
+ end
52
+
53
+ # Stores the components managers in {FelECS::Components}. This
54
+ # is needed because calling `FelECS::Components.constants`
55
+ # will not let you iterate over the value of the constants
56
+ # but will instead give you an array of symbols. This caches
57
+ # the convertion of those symbols to the actual value of the
58
+ # constants
59
+ # @!visibility private
60
+ def const_cache
61
+ @const_cache || update_const_cache
62
+ end
63
+
64
+ # Updates the array that stores the constants.
65
+ # Used internally by FelECS
66
+ # @!visibility private
67
+ def update_const_cache
68
+ @const_cache = constants.map do |constant|
69
+ const_get constant
70
+ end
71
+ end
72
+
73
+ # Forwards undefined methods to the array of constants
74
+ # if the array can handle the request. Otherwise tells
75
+ # the programmer their code errored
76
+ # @!visibility private
77
+ def respond_to_missing?(method, *)
78
+ if const_cache.respond_to? method
79
+ true
80
+ else
81
+ super
82
+ end
83
+ end
84
+
85
+ # Makes component module behave like arrays with additional
86
+ # methods for managing the array
87
+ # @!visibility private
88
+ def method_missing(method, *args, **kwargs, &block)
89
+ if const_cache.respond_to? method
90
+ const_cache.send(method, *args, **kwargs, &block)
91
+ else
92
+ super
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ # Component Managers are what is used to create individual components which can be attached to entities.
99
+ # 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.
100
+ class ComponentManager
101
+ # Allows overwriting the storage of triggers, such as for clearing.
102
+ # This method should generally only need to be used internally and
103
+ # not by a game developer.
104
+ # @!visibility private
105
+ attr_writer :addition_triggers, :removal_triggers, :attr_triggers
106
+
107
+ # Stores references to systems that should be triggered when a
108
+ # component from this manager is added.
109
+ # Do not edit this array as it is managed by FelECS automatically.
110
+ # @return [Array<System>]
111
+ def addition_triggers
112
+ @addition_triggers ||= []
113
+ end
114
+
115
+ # Stores references to systems that should be triggered when a
116
+ # component from this manager is removed.
117
+ # Do not edit this array as it is managed by FelECS automatically.
118
+ # @return [Array<System>]
119
+ def removal_triggers
120
+ @removal_triggers ||= []
121
+ end
122
+
123
+ # Stores references to systems that should be triggered when an
124
+ # attribute from this manager is changed.
125
+ # Do not edit this hash as it is managed by FelECS automatically.
126
+ # @return [Hash<Symbol, Array<System>>]
127
+ def attr_triggers
128
+ @attr_triggers ||= {}
129
+ end
130
+
131
+ # 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.
132
+ # @param attrs [Keyword: Value] You can pass any number of Keyword-Value pairs
133
+ # @return [Component]
134
+ def initialize(**attrs)
135
+ # Prepare the object
136
+ # (this is a function created with metaprogramming
137
+ # in FelECS::Components)
138
+ set_defaults
139
+
140
+ # Fill params
141
+ attrs.each do |key, value|
142
+ send "#{key}=", value
143
+ end
144
+
145
+ # Save Component
146
+ self.class.push self
147
+ end
148
+
149
+ class << self
150
+ # Makes component managers behave like arrays with additional
151
+ # methods for managing the array
152
+ # @!visibility private
153
+ def respond_to_missing?(method, *)
154
+ if _data.respond_to? method
155
+ true
156
+ else
157
+ super
158
+ end
159
+ end
160
+
161
+ # Makes component managers behave like arrays with additional
162
+ # methods for managing the array
163
+ # @!visibility private
164
+ def method_missing(method, *args, **kwargs, &block)
165
+ if _data.respond_to? method
166
+ _data.send(method, *args, **kwargs, &block)
167
+ else
168
+ super
169
+ end
170
+ end
171
+
172
+ # Allows overwriting the storage of triggers, such as for clearing.
173
+ # This method should generally only need to be used internally and
174
+ # not by a game developer.
175
+ # @!visibility private
176
+ attr_writer :addition_triggers, :removal_triggers, :attr_triggers
177
+
178
+ # Stores references to systems that should be triggered when this
179
+ # component is added to an enitity.
180
+ # Do not edit this array as it is managed by FelECS automatically.
181
+ # @return [Array<System>]
182
+ def addition_triggers
183
+ @addition_triggers ||= []
184
+ end
185
+
186
+ # Stores references to systems that should be triggered when this
187
+ # component is removed from an enitity.
188
+ # Do not edit this array as it is managed by FelECS automatically.
189
+ # @return [Array<System>]
190
+ def removal_triggers
191
+ @removal_triggers ||= []
192
+ end
193
+
194
+ # Stores references to systems that should be triggered when an
195
+ # attribute from this component changed.
196
+ # Do not edit this hash as it is managed by FelECS automatically.
197
+ # @return [Hash<Symbol, System>]
198
+ def attr_triggers
199
+ @attr_triggers ||= {}
200
+ end
201
+
202
+ # @return [Array<Component>] Array of all Components that belong to a given component manager
203
+ # @!visibility private
204
+ def _data
205
+ @data ||= []
206
+ end
207
+ end
208
+
209
+ # Entities that have this component
210
+ # @return [Array<Component>]
211
+ def entities
212
+ @entities ||= []
213
+ end
214
+
215
+ # A single entity. Use this if you expect the component to only belong to one entity and you want to access it.
216
+ # @return [Component]
217
+ def entity
218
+ if entities.empty?
219
+ 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.")
220
+ elsif entities.length > 1
221
+ 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.")
222
+ end
223
+ entities.first
224
+ end
225
+
226
+ # Update attribute values using a hash or keywords.
227
+ # @return [Hash<Symbol, Value>] Hash of updated attributes
228
+ def update_attrs(**opts)
229
+ opts.each do |key, value|
230
+ send "#{key}=", value
231
+ end
232
+ end
233
+
234
+ # Execute systems that have been added to execute on variable change
235
+ # @return [Boolean] +true+
236
+ # @!visibility private
237
+ def attr_changed_trigger_systems(attr)
238
+ systems_to_execute = self.class.attr_triggers[attr]
239
+ systems_to_execute = [] if systems_to_execute.nil?
240
+
241
+ systems_to_execute |= attr_triggers[attr] unless attr_triggers[attr].nil?
242
+
243
+ systems_to_execute.sort_by(&:priority).reverse_each(&:call)
244
+ true
245
+ end
246
+
247
+ # Removes this component from the list and purges all references to this Component from other Entities, as well as its data.
248
+ # @return [Boolean] +true+.
249
+ def delete
250
+ addition_triggers.each do |system|
251
+ system.clear_triggers component_or_manager: self
252
+ end
253
+ entities.reverse_each do |entity|
254
+ entity.remove self
255
+ end
256
+ self.class._data.delete self
257
+ instance_variables.each do |var|
258
+ instance_variable_set(var, nil)
259
+ end
260
+ true
261
+ end
262
+
263
+ # @return [Hash<Symbol, Value>] A hash, where all the keys are attributes storing their respective values.
264
+ def to_h
265
+ return_hash = instance_variables.each_with_object({}) do |key, final|
266
+ final[key.to_s.delete_prefix('@').to_sym] = instance_variable_get(key)
267
+ end
268
+ return_hash.delete(:attr_triggers)
269
+ return_hash
270
+ end
271
+
272
+ # Export all data into a JSON String, which could then later be loaded or saved to a file
273
+ # TODO: This function is not yet complete
274
+ # @return [String] a JSON formatted String
275
+ # def to_json
276
+ # # should return a json or hash of all data in this component
277
+ # end
278
+ end
279
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FelECS
4
+ class Entities
5
+ # Creating a new Entity
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.
7
+ # @return [Entity]
8
+ def initialize(*components)
9
+ # Add each component
10
+ add(*components)
11
+ self.class._data.push self
12
+ end
13
+
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.
15
+ # @return [Hash<Component_Manager, Array<Integer>>]
16
+ def components
17
+ @components ||= {}
18
+ end
19
+
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
+ FelECS::Entities.component_redirect.entity = self
29
+ FelECS::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
39
+ end
40
+
41
+ # Removes this Entity from the list and purges all references to this Entity from other Components, as well as its data.
42
+ # @return [Boolean] +true+
43
+ def delete
44
+ components.each do |_component_manager, component_array|
45
+ component_array.reverse_each do |component|
46
+ component.entities.delete(self)
47
+ end
48
+ end
49
+ FelECS::Entities._data.delete self
50
+ @components = {}
51
+ true
52
+ end
53
+
54
+ # Add any number components to the Entity.
55
+ # @param components_to_add [Component] Any number of components created from any component manager
56
+ # @return [Boolean] +true+
57
+ def add(*components_to_add)
58
+ components_to_add.each do |component|
59
+ if components[component.class].nil?
60
+ components[component.class] = [component]
61
+ component.entities.push self
62
+ check_systems component, :addition_triggers
63
+ elsif !components[component.class].include? component
64
+ components[component.class].push component
65
+ component.entities.push self
66
+ check_systems component, :addition_triggers
67
+ end
68
+ end
69
+ true
70
+ end
71
+
72
+ # triggers every system associated with this component's trigger
73
+ # @return [Boolean] +true+
74
+ # @!visibility private
75
+ def check_systems(component, trigger_type)
76
+ component_calls = component.class.send(trigger_type)
77
+ component.send(trigger_type).each do |system|
78
+ component_calls |= [system]
79
+ end
80
+ component_calls.sort_by(&:priority).reverse.each(&:call)
81
+ true
82
+ end
83
+
84
+ # Remove a component from the Entity
85
+ # @param components_to_remove [Component] A component created from any component manager
86
+ # @return [Boolean] +true+
87
+ def remove(*components_to_remove)
88
+ components_to_remove.each do |component|
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
+ end
94
+ true
95
+ end
96
+
97
+ # Export all data into a JSON String which can then be saved into a file
98
+ # TODO: This function is not yet complete
99
+ # @return [String] A JSON formatted String
100
+ # def to_json() end
101
+
102
+ class << self
103
+ # Makes component managers behave like arrays with additional
104
+ # methods for managing the array
105
+ # @!visibility private
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
123
+ end
124
+
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
145
+ end
146
+
147
+ # @return [Array<Entity>] Array of all Entities that exist
148
+ # @!visibility private
149
+ def _data
150
+ @data ||= []
151
+ end
152
+
153
+ # Creates a new entity using the data from a JSON string
154
+ # TODO: This function is not yet complete
155
+ # @param json_string [String] A string that was exported originally using the {FelECS::Entities#to_json to_json} function
156
+ # @param opts [Keywords] What values(its {FelECS::Entities#id ID} or the {FelECS::ComponentManager#id component IDs}) should be overwritten TODO: this might change
157
+ # def from_json(json_string, **opts) end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FelECS
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
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FelECS
4
+ class Scenes
5
+ # Allows overwriting the storage of systems, such as for clearing.
6
+ # This method should generally only need to be used internally and
7
+ # not by a game developer/
8
+ # @!visibility private
9
+ attr_writer :systems
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
+ FelECS::Stage.scenes = FelECS::Stage.scenes.sort_by(&:priority)
17
+ priority
18
+ end
19
+
20
+ # Create a new Scene using the name given
21
+ # @param name [String] String format must follow requirements of a constant
22
+ def initialize(name, priority: 0)
23
+ self.priority = priority
24
+ FelECS::Scenes.const_set(name, self)
25
+ end
26
+
27
+ # The list of Systems this Scene contains
28
+ # @return [Array<System>]
29
+ def systems
30
+ @systems ||= []
31
+ end
32
+
33
+ # Execute all systems in this Scene, in the order of their priority
34
+ # @return [Boolean] +true+
35
+ def call
36
+ systems.each(&:call)
37
+ true
38
+ end
39
+
40
+ # Adds any number of Systems to this Scene
41
+ # @return [Boolean] +true+
42
+ def add(*systems_to_add)
43
+ self.systems |= systems_to_add
44
+ self.systems = systems.sort_by(&:priority)
45
+ systems_to_add.each do |system|
46
+ system.scenes |= [self]
47
+ end
48
+ true
49
+ end
50
+
51
+ # Removes any number of Systems from this Scene
52
+ # @return [Boolean] +true+
53
+ def remove(*systems_to_remove)
54
+ self.systems -= systems_to_remove
55
+ true
56
+ end
57
+
58
+ # Removes all Systems from this Scene
59
+ # @return [Boolean] +true+
60
+ def clear
61
+ systems.each do |system|
62
+ system.scenes.delete self
63
+ end
64
+ systems.clear
65
+ # FelECS::Stage.update_systems_list if FelECS::Stage.scenes.include? self
66
+ true
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FelECS
4
+ module Stage
5
+ class << self
6
+ # Allows clearing of scenes and systems.
7
+ # Used internally by FelECS and shouldn't need to be ever used by developers
8
+ # @!visibility private
9
+ attr_writer :scenes
10
+
11
+ # Add any number of Scenes to the Stage
12
+ # @return [Boolean] +true+
13
+ def add(*scenes_to_add)
14
+ self.scenes |= scenes_to_add
15
+ self.scenes = scenes.sort_by(&:priority)
16
+ true
17
+ end
18
+
19
+ # Remove any number of Scenes from the Stage
20
+ # @return [Boolean] +true+
21
+ def remove(*scenes_to_remove)
22
+ self.scenes -= scenes_to_remove
23
+ true
24
+ end
25
+
26
+ # Clears all Scenes that were added to the Stage
27
+ # @return [Boolean] +true+
28
+ def clear
29
+ self.scenes.clear
30
+ true
31
+ end
32
+
33
+ # Executes one frame of the game. This executes all the Scenes added to the Stage in order of their priority.
34
+ # @return [Boolean] +true+
35
+ def call
36
+ self.scenes.each(&:call)
37
+ true
38
+ end
39
+
40
+ # Contains all the Scenes added to the Stage
41
+ # @return [Array<Scene>]
42
+ def scenes
43
+ @scenes ||= []
44
+ end
45
+ end
46
+ end
47
+ end