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,913 @@
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
161
+
162
+ # frozen_string_literal: true
163
+
164
+ module FelECS
165
+ module Components
166
+ @component_map = []
167
+ class << self
168
+ # Creates a new {FelECS::ComponentManager component manager}.
169
+ #
170
+ # @example
171
+ # # Here color is set to default to red
172
+ # # while max and current are nil until set.
173
+ # # When you make a new component using this component manager
174
+ # # these are the values and accessors it will have.
175
+ # FelECS::Component.new('Health', :max, :current, color: 'red')
176
+ #
177
+ # @param component_name [String] Name of your new component manager. Must be stylized in the format of constants in Ruby
178
+ # @param attrs [:Symbols] New components made with this manager will include these symbols as accessors, the values of these accessors will default to nil
179
+ # @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
180
+ # @return [ComponentManager]
181
+ def new(component_name, *attrs, **attrs_with_defaults)
182
+ if FelECS::Components.const_defined?(component_name)
183
+ raise(NameError.new, "Component Manager '#{component_name}' is already defined")
184
+ end
185
+
186
+ const_set(component_name, Class.new(FelECS::ComponentManager) {})
187
+ update_const_cache
188
+
189
+ attrs.each do |attr|
190
+ if FelECS::Components.const_get(component_name).method_defined?(attr.to_s) || FelECS::Components.const_get(component_name).method_defined?("#{attr}=")
191
+ raise NameError, "The attribute name \"#{attr}\" is already a method"
192
+ end
193
+
194
+ FelECS::Components.const_get(component_name).attr_accessor attr
195
+ end
196
+ attrs_with_defaults.each do |attr, _default|
197
+ attrs_with_defaults[attr] = _default.dup
198
+ FelECS::Components.const_get(component_name).attr_reader attr
199
+ FelECS::Components.const_get(component_name).define_method("#{attr}=") do |value|
200
+ unless value.equal? send(attr)
201
+ instance_variable_set("@#{attr}", value)
202
+ attr_changed_trigger_systems(attr)
203
+ end
204
+ end
205
+ end
206
+ FelECS::Components.const_get(component_name).define_method(:set_defaults) do
207
+ attrs_with_defaults.each do |attr, default|
208
+ instance_variable_set("@#{attr}", default.dup)
209
+ end
210
+ end
211
+ FelECS::Components.const_get(component_name)
212
+ end
213
+
214
+ # Stores the components managers in {FelECS::Components}. This
215
+ # is needed because calling `FelECS::Components.constants`
216
+ # will not let you iterate over the value of the constants
217
+ # but will instead give you an array of symbols. This caches
218
+ # the convertion of those symbols to the actual value of the
219
+ # constants
220
+ # @!visibility private
221
+ def const_cache
222
+ @const_cache || update_const_cache
223
+ end
224
+
225
+ # Updates the array that stores the constants.
226
+ # Used internally by FelECS
227
+ # @!visibility private
228
+ def update_const_cache
229
+ @const_cache = constants.map do |constant|
230
+ const_get constant
231
+ end
232
+ end
233
+
234
+ # Forwards undefined methods to the array of constants
235
+ # if the array can handle the request. Otherwise tells
236
+ # the programmer their code errored
237
+ # @!visibility private
238
+ def respond_to_missing?(method, *)
239
+ if const_cache.respond_to? method
240
+ true
241
+ else
242
+ super
243
+ end
244
+ end
245
+
246
+ # Makes component module behave like arrays with additional
247
+ # methods for managing the array
248
+ # @!visibility private
249
+ def method_missing(method, *args, **kwargs, &block)
250
+ if const_cache.respond_to? method
251
+ const_cache.send(method, *args, **kwargs, &block)
252
+ else
253
+ super
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ # Component Managers are what is used to create individual components which can be attached to entities.
260
+ # 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.
261
+ class ComponentManager
262
+ # Allows overwriting the storage of triggers, such as for clearing.
263
+ # This method should generally only need to be used internally and
264
+ # not by a game developer.
265
+ # @!visibility private
266
+ attr_writer :addition_triggers, :removal_triggers, :attr_triggers
267
+
268
+ # Stores references to systems that should be triggered when a
269
+ # component from this manager is added.
270
+ # Do not edit this array as it is managed by FelECS automatically.
271
+ # @return [Array<System>]
272
+ def addition_triggers
273
+ @addition_triggers ||= []
274
+ end
275
+
276
+ # Stores references to systems that should be triggered when a
277
+ # component from this manager is removed.
278
+ # Do not edit this array as it is managed by FelECS automatically.
279
+ # @return [Array<System>]
280
+ def removal_triggers
281
+ @removal_triggers ||= []
282
+ end
283
+
284
+ # Stores references to systems that should be triggered when an
285
+ # attribute from this manager is changed.
286
+ # Do not edit this hash as it is managed by FelECS automatically.
287
+ # @return [Hash<Symbol, Array<System>>]
288
+ def attr_triggers
289
+ @attr_triggers ||= {}
290
+ end
291
+
292
+ # 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.
293
+ # @param attrs [Keyword: Value] You can pass any number of Keyword-Value pairs
294
+ # @return [Component]
295
+ def initialize(**attrs)
296
+ # Prepare the object
297
+ # (this is a function created with metaprogramming
298
+ # in FelECS::Components)
299
+ set_defaults
300
+
301
+ # Fill params
302
+ attrs.each do |key, value|
303
+ send "#{key}=", value
304
+ end
305
+
306
+ # Save Component
307
+ self.class.push self
308
+ end
309
+
310
+ class << self
311
+ # Makes component managers behave like arrays with additional
312
+ # methods for managing the array
313
+ # @!visibility private
314
+ def respond_to_missing?(method, *)
315
+ if _data.respond_to? method
316
+ true
317
+ else
318
+ super
319
+ end
320
+ end
321
+
322
+ # Makes component managers behave like arrays with additional
323
+ # methods for managing the array
324
+ # @!visibility private
325
+ def method_missing(method, *args, **kwargs, &block)
326
+ if _data.respond_to? method
327
+ _data.send(method, *args, **kwargs, &block)
328
+ else
329
+ super
330
+ end
331
+ end
332
+
333
+ # Allows overwriting the storage of triggers, such as for clearing.
334
+ # This method should generally only need to be used internally and
335
+ # not by a game developer.
336
+ # @!visibility private
337
+ attr_writer :addition_triggers, :removal_triggers, :attr_triggers
338
+
339
+ # Stores references to systems that should be triggered when this
340
+ # component is added to an enitity.
341
+ # Do not edit this array as it is managed by FelECS automatically.
342
+ # @return [Array<System>]
343
+ def addition_triggers
344
+ @addition_triggers ||= []
345
+ end
346
+
347
+ # Stores references to systems that should be triggered when this
348
+ # component is removed from an enitity.
349
+ # Do not edit this array as it is managed by FelECS automatically.
350
+ # @return [Array<System>]
351
+ def removal_triggers
352
+ @removal_triggers ||= []
353
+ end
354
+
355
+ # Stores references to systems that should be triggered when an
356
+ # attribute from this component changed.
357
+ # Do not edit this hash as it is managed by FelECS automatically.
358
+ # @return [Hash<Symbol, System>]
359
+ def attr_triggers
360
+ @attr_triggers ||= {}
361
+ end
362
+
363
+ # @return [Array<Component>] Array of all Components that belong to a given component manager
364
+ # @!visibility private
365
+ def _data
366
+ @data ||= []
367
+ end
368
+ end
369
+
370
+ # Entities that have this component
371
+ # @return [Array<Component>]
372
+ def entities
373
+ @entities ||= []
374
+ end
375
+
376
+ # A single entity. Use this if you expect the component to only belong to one entity and you want to access it.
377
+ # @return [Component]
378
+ def entity
379
+ if entities.empty?
380
+ 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.")
381
+ elsif entities.length > 1
382
+ 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.")
383
+ end
384
+ entities.first
385
+ end
386
+
387
+ # Update attribute values using a hash or keywords.
388
+ # @return [Hash<Symbol, Value>] Hash of updated attributes
389
+ def update_attrs(**opts)
390
+ opts.each do |key, value|
391
+ send "#{key}=", value
392
+ end
393
+ end
394
+
395
+ # Execute systems that have been added to execute on variable change
396
+ # @return [Boolean] +true+
397
+ # @!visibility private
398
+ def attr_changed_trigger_systems(attr)
399
+ systems_to_execute = self.class.attr_triggers[attr]
400
+ systems_to_execute = [] if systems_to_execute.nil?
401
+
402
+ systems_to_execute |= attr_triggers[attr] unless attr_triggers[attr].nil?
403
+
404
+ systems_to_execute.sort_by(&:priority).reverse_each(&:call)
405
+ true
406
+ end
407
+
408
+ # Removes this component from the list and purges all references to this Component from other Entities, as well as its data.
409
+ # @return [Boolean] +true+.
410
+ def delete
411
+ addition_triggers.each do |system|
412
+ system.clear_triggers component_or_manager: self
413
+ end
414
+ entities.reverse_each do |entity|
415
+ entity.remove self
416
+ end
417
+ self.class._data.delete self
418
+ instance_variables.each do |var|
419
+ instance_variable_set(var, nil)
420
+ end
421
+ true
422
+ end
423
+
424
+ # @return [Hash<Symbol, Value>] A hash, where all the keys are attributes storing their respective values.
425
+ def to_h
426
+ return_hash = instance_variables.each_with_object({}) do |key, final|
427
+ final[key.to_s.delete_prefix('@').to_sym] = instance_variable_get(key)
428
+ end
429
+ return_hash.delete(:attr_triggers)
430
+ return_hash
431
+ end
432
+
433
+ # Export all data into a JSON String, which could then later be loaded or saved to a file
434
+ # TODO: This function is not yet complete
435
+ # @return [String] a JSON formatted String
436
+ # def to_json
437
+ # # should return a json or hash of all data in this component
438
+ # end
439
+ end
440
+ end
441
+
442
+ # frozen_string_literal: true
443
+
444
+ module FelECS
445
+ class Systems
446
+ # How early this System should be executed in a list of Systems
447
+ attr_accessor :priority
448
+
449
+ # The Constant name assigned to this System
450
+
451
+ # Allows overwriting the storage of triggers, such as for clearing.
452
+ # This method should generally only need to be used internally and
453
+ # not by a game developer.
454
+ # @!visibility private
455
+ attr_writer :addition_triggers, :removal_triggers, :attr_triggers
456
+
457
+ # Stores all the scenes this system is a part of.
458
+ attr_writer :scenes
459
+
460
+ def scenes
461
+ @scenes ||= []
462
+ end
463
+
464
+ def priority=(priority)
465
+ @priority = priority
466
+ scenes.each do |scene|
467
+ scene.systems = scene.systems.sort_by(&:priority)
468
+ end
469
+ end
470
+
471
+ # Stores references to components or their managers that trigger
472
+ # this component when a component or component from that manager
473
+ # is added to an entity.
474
+ # Do not edit this hash as it is managed by FelECS automatically.
475
+ # @return [Array<Component>]
476
+ def addition_triggers
477
+ @addition_triggers ||= []
478
+ end
479
+
480
+ # Stores references to components or their managers that trigger
481
+ # this component when a component or component from that manager
482
+ # is removed from an entity.
483
+ # Do not edit this hash as it is managed by FelECS automatically.
484
+ # @return [Array<Component>]
485
+ def removal_triggers
486
+ @removal_triggers ||= []
487
+ end
488
+
489
+ # Stores references to systems that should be triggered when an
490
+ # attribute from this manager is changed
491
+ # Do not edit this hash as it is managed by FelECS automatically.
492
+ # @return [Hash<Symbol, Array<Symbol>>]
493
+ def attr_triggers
494
+ @attr_triggers ||= {}
495
+ end
496
+
497
+ class << self
498
+ # Stores the systems in {FelECS::Components}. This
499
+ # is needed because calling `FelECS::Components.constants`
500
+ # will not let you iterate over the value of the constants
501
+ # but will instead give you an array of symbols. This caches
502
+ # the convertion of those symbols to the actual value of the
503
+ # constants
504
+ def const_cache
505
+ @const_cache || update_const_cache
506
+ end
507
+
508
+ # Updates the array that stores the constants.
509
+ # Used internally by FelECS
510
+ # @!visibility private
511
+ def update_const_cache
512
+ @const_cache = constants.map do |constant|
513
+ const_get constant
514
+ end
515
+ end
516
+
517
+ # Forwards undefined methods to the array of constants
518
+ # if the array can handle the request. Otherwise tells
519
+ # the programmer their code errored
520
+ # @!visibility private
521
+ def respond_to_missing?(method, *)
522
+ if const_cache.respond_to? method
523
+ true
524
+ else
525
+ super
526
+ end
527
+ end
528
+
529
+ # Makes system module behave like arrays with additional
530
+ # methods for managing the array
531
+ # @!visibility private
532
+ def method_missing(method, *args, **kwargs, &block)
533
+ if const_cache.respond_to? method
534
+ const_cache.send(method, *args, **kwargs, &block)
535
+ else
536
+ super
537
+ end
538
+ end
539
+ end
540
+
541
+ # Creates a new System which can be accessed as a constant under the namespace {FelECS::Systems}.
542
+ # The name given is what constant the system is assigned to
543
+ #
544
+ # @example
545
+ # FelECS::Systems.new('PassiveHeal', priority: -2) do
546
+ # FelECS::Components::Health.each do |component|
547
+ # component.hp += 5
548
+ # end
549
+ # end
550
+ # # Give it a low priority so other systems such as a
551
+ # # Poison system would kill the player first
552
+ #
553
+ # @param name [String] The name this system will use. Needs to to be in the Ruby Constant format.
554
+ # @param priority [Integer] Which priority order this system should be executed in relative to other systems. Higher means executed earlier.
555
+ # @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.
556
+ def initialize(name, priority: 0, &block)
557
+ FelECS::Systems.const_set(name, self)
558
+ FelECS::Systems.update_const_cache
559
+ @priority = priority
560
+ @block = block
561
+ @scenes = []
562
+ end
563
+
564
+ # Manually execute the system a single time
565
+ def call
566
+ @block.call
567
+ end
568
+
569
+ # Redefine what code is executed by this System when it is called upon.
570
+ # @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.
571
+ def redefine(&block)
572
+ @block = block
573
+ end
574
+
575
+ # Removes triggers from this system. This function is fairly flexible so it can accept a few different inputs
576
+ # For addition and removal triggers, you can optionally pass in a component, or a manager to clear specifically
577
+ # the relevant triggers for that one component or manager. If you do not pass a component or manager then it will
578
+ # clear triggers for all components and managers.
579
+ # For attr_triggers
580
+ # @example
581
+ # # To clear all triggers that execute this system when a component is added:
582
+ # FelECS::Systems::ExampleSystem.clear_triggers :addition_triggers
583
+ # # Same as above but for when a component is removed instead
584
+ # FelECS::Systems::ExampleSystem.clear_triggers :removal_triggers
585
+ # # Same as above but for when a component has a certain attribute changed
586
+ # FelECS::Systems::ExampleSystem.clear_triggers :attr_triggers
587
+ #
588
+ # # Clear a trigger from a specific component
589
+ # FelECS::Systems::ExampleSystem.clear_triggers :addition_triggers, FelECS::Component::ExampleComponent[0]
590
+ # # Clear a trigger from a specific component manager
591
+ # FelECS::Systems::ExampleSystem.clear_triggers :addition_triggers, FelECS::Component::ExampleComponent
592
+ #
593
+ # # Clear the trigger that executes a system when the ':example_attr' is changes
594
+ # FelECS::Systems::ExampleSystem.clear_triggers :attr_triggers, :example_attr
595
+ # @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
596
+ # @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.
597
+ # @return [Boolean] +true+
598
+ def clear_triggers(*trigger_types, component_or_manager: nil)
599
+ trigger_types = %i[addition_triggers removal_triggers attr_triggers] if trigger_types.empty?
600
+
601
+ if trigger_types.include? :attr_triggers
602
+ if (trigger_types - %i[addition_triggers
603
+ removal_triggers
604
+ attr_triggers]).empty?
605
+
606
+ if component_or_manager.nil?
607
+ # remove all attrs
608
+ attr_triggers.each do |cmp_or_mgr, attrs|
609
+ attrs.each do |attr|
610
+ next if cmp_or_mgr.attr_triggers[attr].nil?
611
+
612
+ cmp_or_mgr.attr_triggers[attr].delete self
613
+ end
614
+ self.attr_triggers = {}
615
+ end
616
+ else
617
+ # remove attrs relevant to comp_or_man
618
+ unless attr_triggers[component_or_manager].nil?
619
+ attr_triggers[component_or_manager].each do |attr|
620
+ component_or_manager.attr_triggers[attr].delete self
621
+ end
622
+ attr_triggers[component_or_manager] = []
623
+ end
624
+ end
625
+
626
+ elsif component_or_manager.nil?
627
+
628
+ (trigger_types - %i[addition_triggers removal_triggers attr_triggers]).each do |attr|
629
+ # remove attr
630
+ attr_triggers.each do |cmp_or_mgr, _attrs|
631
+ cmp_or_mgr.attr_triggers[attr].delete self
632
+ end
633
+ end
634
+ attr_triggers.delete(trigger_types - %i[addition_triggers
635
+ removal_triggers
636
+ attr_triggers])
637
+ else
638
+ # remove attr from component_or_manager
639
+ (trigger_types - %i[addition_triggers removal_triggers attr_triggers]).each do |attr|
640
+ next if component_or_manager.attr_triggers[attr].nil?
641
+
642
+ component_or_manager.attr_triggers[attr].delete self
643
+ end
644
+ attr_triggers[component_or_manager] -= trigger_types unless attr_triggers[component_or_manager].nil?
645
+
646
+ end
647
+ end
648
+
649
+ (trigger_types & %i[removal_triggers addition_triggers] - [:attr_triggers]).each do |trigger_type|
650
+ if component_or_manager.nil?
651
+ # remove all removal triggers
652
+ send(trigger_type).each do |trigger|
653
+ trigger.send(trigger_type).delete self
654
+ end
655
+ send("#{trigger_type}=", [])
656
+ else
657
+ # remove removal trigger relevant to comp/man
658
+ send(trigger_type).delete component_or_manager
659
+ component_or_manager.send(trigger_type).delete self
660
+ end
661
+ end
662
+ true
663
+ end
664
+
665
+ # 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
666
+ # @param component_or_manager [Component or ComponentManager] The component or component manager to trigger this system when added
667
+ # @return [Boolean] +true+
668
+ def trigger_when_added(component_or_manager)
669
+ self.addition_triggers |= [component_or_manager]
670
+ component_or_manager.addition_triggers |= [self]
671
+ true
672
+ end
673
+
674
+ # 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
675
+ # @param component_or_manager [Component or ComponentManager] The component or component manager to trigger this system when removed
676
+ # @return [Boolean] +true+
677
+ def trigger_when_removed(component_or_manager)
678
+ self.removal_triggers |= [component_or_manager]
679
+ component_or_manager.removal_triggers |= [self]
680
+ true
681
+ end
682
+
683
+ # Add a component or component manager so that it triggers this system when a component's attribute is changed.
684
+ # @return [Boolean] +true+
685
+ def trigger_when_is_changed(component_or_manager, attr)
686
+ if component_or_manager.attr_triggers[attr].nil?
687
+ component_or_manager.attr_triggers[attr] = [self]
688
+ else
689
+ component_or_manager.attr_triggers[attr] |= [self]
690
+ end
691
+ if attr_triggers[component_or_manager].nil?
692
+ attr_triggers[component_or_manager] = [attr]
693
+ else
694
+ attr_triggers[component_or_manager] |= [attr]
695
+ end
696
+ true
697
+ end
698
+ end
699
+ end
700
+
701
+ # frozen_string_literal: true
702
+
703
+ module FelECS
704
+ class Scenes
705
+ # Allows overwriting the storage of systems, such as for clearing.
706
+ # This method should generally only need to be used internally and
707
+ # not by a game developer/
708
+ # @!visibility private
709
+ attr_writer :systems
710
+
711
+ # How early this Scene should be executed in a list of Scenes
712
+ attr_accessor :priority
713
+
714
+ def priority=(priority)
715
+ @priority = priority
716
+ FelECS::Stage.scenes = FelECS::Stage.scenes.sort_by(&:priority)
717
+ priority
718
+ end
719
+
720
+ # Create a new Scene using the name given
721
+ # @param name [String] String format must follow requirements of a constant
722
+ def initialize(name, priority: 0)
723
+ self.priority = priority
724
+ FelECS::Scenes.const_set(name, self)
725
+ end
726
+
727
+ # The list of Systems this Scene contains
728
+ # @return [Array<System>]
729
+ def systems
730
+ @systems ||= []
731
+ end
732
+
733
+ # Execute all systems in this Scene, in the order of their priority
734
+ # @return [Boolean] +true+
735
+ def call
736
+ systems.each(&:call)
737
+ true
738
+ end
739
+
740
+ # Adds any number of Systems to this Scene
741
+ # @return [Boolean] +true+
742
+ def add(*systems_to_add)
743
+ self.systems |= systems_to_add
744
+ self.systems = systems.sort_by(&:priority)
745
+ systems_to_add.each do |system|
746
+ system.scenes |= [self]
747
+ end
748
+ true
749
+ end
750
+
751
+ # Removes any number of Systems from this Scene
752
+ # @return [Boolean] +true+
753
+ def remove(*systems_to_remove)
754
+ self.systems -= systems_to_remove
755
+ true
756
+ end
757
+
758
+ # Removes all Systems from this Scene
759
+ # @return [Boolean] +true+
760
+ def clear
761
+ systems.each do |system|
762
+ system.scenes.delete self
763
+ end
764
+ systems.clear
765
+ # FelECS::Stage.update_systems_list if FelECS::Stage.scenes.include? self
766
+ true
767
+ end
768
+ end
769
+ end
770
+
771
+ # frozen_string_literal: true
772
+
773
+ module FelECS
774
+ module Stage
775
+ class << self
776
+ # Allows clearing of scenes and systems.
777
+ # Used internally by FelECS and shouldn't need to be ever used by developers
778
+ # @!visibility private
779
+ attr_writer :scenes
780
+
781
+ # Add any number of Scenes to the Stage
782
+ # @return [Boolean] +true+
783
+ def add(*scenes_to_add)
784
+ self.scenes |= scenes_to_add
785
+ self.scenes = scenes.sort_by(&:priority)
786
+ true
787
+ end
788
+
789
+ # Remove any number of Scenes from the Stage
790
+ # @return [Boolean] +true+
791
+ def remove(*scenes_to_remove)
792
+ self.scenes -= scenes_to_remove
793
+ true
794
+ end
795
+
796
+ # Clears all Scenes that were added to the Stage
797
+ # @return [Boolean] +true+
798
+ def clear
799
+ self.scenes.clear
800
+ true
801
+ end
802
+
803
+ # Executes one frame of the game. This executes all the Scenes added to the Stage in order of their priority.
804
+ # @return [Boolean] +true+
805
+ def call
806
+ self.scenes.each(&:call)
807
+ true
808
+ end
809
+
810
+ # Contains all the Scenes added to the Stage
811
+ # @return [Array<Scene>]
812
+ def scenes
813
+ @scenes ||= []
814
+ end
815
+ end
816
+ end
817
+ end
818
+
819
+ # frozen_string_literal: true
820
+
821
+ module FelECS
822
+ module Order
823
+ # Sets the priority of all items passed into this method
824
+ # according to the order they were passed.
825
+ # If an array is one of the elements then it will give all
826
+ # of those elements in the array the same priority.
827
+ # @param sortables [(Systems and Array<Systems>) or (Scenes and Array<Scenes>)]
828
+ # @return [Boolean] +true+.
829
+ def self.sort(*sortables)
830
+ sortables.each_with_index do |sorted, index|
831
+ if sorted.respond_to? :priority
832
+ sorted.priority = index
833
+ else
834
+ sorted.each do |item|
835
+ item.priority = index
836
+ end
837
+ end
838
+ end
839
+ true
840
+ end
841
+ end
842
+ end
843
+
844
+ # frozen_string_literal: true
845
+
846
+ # :nocov:
847
+ # Keeps the version of the Gem
848
+ module FelECS
849
+ # The version of the Gem
850
+ VERSION = '5.0.0'
851
+ end
852
+ # :nocov:
853
+
854
+ # frozen_string_literal: true
855
+
856
+
857
+
858
+ # The FelECS namespace where all its functionality resides under.
859
+ module FelECS
860
+ class << self
861
+ # :nocov:
862
+
863
+ # An alias for {FelECS::Stage.call}. It executes a single frame in the game.
864
+ def call
865
+ FelECS::Stage.call
866
+ end
867
+ # :nocov:
868
+ end
869
+
870
+ # Creates and manages Entities. Entities are just collections of Components.
871
+ # You can use array methods directly on this class to access Entities.
872
+ class Entities; end
873
+
874
+ # Creates component managers and allows accessing them them under the {FelECS::Components} namespace as Constants.
875
+ # You can use array methods directly on this class to access Component Managers.
876
+ #
877
+ # To see how component managers are used please look at the {FelECS::ComponentManager} documentation.
878
+ module Components; end
879
+
880
+ # Creates and manages Systems. Systems are the logic of the game and do not contain any data within them. Any systems you create are accessable under the {FelECS::Systems} namespace as Constants.
881
+ # You can use array methods directly on this class to access Systems.
882
+ class Systems; end
883
+
884
+ # Creates and manages Scenes. Scenes are collections of Systems, and execute all the Systems when called upon. Any scenes you create are accessable under the {FelECS::Scenes} namespace as Constants.
885
+ class Scenes; end
886
+
887
+ # Stores Scenes you add to it which you want to execute on each frame. When called upon will execute all Systems in the Scenes in the Stage and will execute them according to their priority order.
888
+ module Stage; end
889
+
890
+ # Sets the priority of a list of Systems or Scenes for you in the order you pass them to this class.
891
+ module Order; end
892
+ end
893
+
894
+ # An alias for {FelECS}
895
+ FECS = FelECS
896
+
897
+ # An alias for {FelECS::Entities}
898
+ FECS::Ent = FelECS::Entities
899
+
900
+ # An alias for {FelECS::Components}
901
+ FECS::Cmp = FelECS::Components
902
+
903
+ # An alias for {FelECS::Systems}
904
+ FECS::Sys = FelECS::Systems
905
+
906
+ # An alias for {FelECS::Scenes}
907
+ FECS::Scn = FelECS::Scenes
908
+
909
+ # An alias for {FelECS::Stage}
910
+ FECS::Stg = FelECS::Stage
911
+
912
+ # An alias for {FelECS::
913
+ FECS::Odr = FelECS::Order