draco 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1886b23bb2d098362aa4452b25922fc98fb033fd86f0b8360586da1406bd5810
4
- data.tar.gz: 9c0e7314c301c7b879ec71bbe8eb8966b04d6c85093d176a2d0c658cb0ad5b0b
3
+ metadata.gz: 9c330a2195d1f7b17f0bb1be16914cef57ccfcf5094ed034cf50a64a3abae52a
4
+ data.tar.gz: efa72ad0c7facd4c3143f858a07adc5be8e6b225635d54b4b60fc983343f33be
5
5
  SHA512:
6
- metadata.gz: 29ebe2a5000a3966d4269df1ee0fb7046b0c830f8f5ed589e918d953665da8e0d1f8992d71fb9f2f2b76339fd390821f4732b60488f84daad6ceec3638caa715
7
- data.tar.gz: 3f617f62d43e4c680a7983278b465cf5498594883581ad0571368a10ef59e80a35969a1c7cdfbdab114ee808b718782433e467761fdf8fc952229643d6d2e27e
6
+ metadata.gz: b5223959feeb20099bca35233f704b91270757ed33d1f59cd3091e6ea8793bb28a3cc4467670ac305931f85c5bb12fe036ac2ae2655aa8c597e5d02ff3d19993
7
+ data.tar.gz: cb182bfbcbe2b69fc5a84adf2aceabc1dddc7d75c2159cb4b13fcfb4f3f3ead4659dd1d6e114152c8ba61c8d8100672fa2672b65b267feb413ff0aece91caf42
data/.gitignore CHANGED
@@ -9,3 +9,5 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+
13
+ .vscode/
@@ -1,3 +1,22 @@
1
+ ## 0.6.0
2
+
3
+ Features:
4
+
5
+ - `#serialize` now includes the class name for Entities, Components, and Systems.
6
+ - Added callbacks to Entities, Components, Systems and Worlds to better enable plugins.
7
+ - Added a sample plugin to `lib/draco/benchmark.rb` to benchmark your systems.
8
+
9
+ How to use:
10
+
11
+ ```ruby
12
+ class World < Draco::World
13
+ include Draco::Benchmark
14
+ end
15
+
16
+ world.system_timers
17
+ # => { :system => 0.001 }
18
+ ```
19
+
1
20
  ## 0.5.1 (December 31, 2020)
2
21
 
3
22
  Features:
@@ -3,43 +3,45 @@ GEM
3
3
  specs:
4
4
  ast (2.4.1)
5
5
  diff-lcs (1.4.4)
6
- docile (1.3.2)
7
- parallel (1.19.2)
8
- parser (2.7.2.0)
6
+ docile (1.3.4)
7
+ parallel (1.20.1)
8
+ parser (3.0.0.0)
9
9
  ast (~> 2.4.1)
10
10
  rainbow (3.0.0)
11
11
  rake (12.3.3)
12
- regexp_parser (1.8.2)
12
+ regexp_parser (2.0.3)
13
13
  rexml (3.2.4)
14
14
  rspec (3.10.0)
15
15
  rspec-core (~> 3.10.0)
16
16
  rspec-expectations (~> 3.10.0)
17
17
  rspec-mocks (~> 3.10.0)
18
- rspec-core (3.10.0)
18
+ rspec-core (3.10.1)
19
19
  rspec-support (~> 3.10.0)
20
- rspec-expectations (3.10.0)
20
+ rspec-expectations (3.10.1)
21
21
  diff-lcs (>= 1.2.0, < 2.0)
22
22
  rspec-support (~> 3.10.0)
23
- rspec-mocks (3.10.0)
23
+ rspec-mocks (3.10.1)
24
24
  diff-lcs (>= 1.2.0, < 2.0)
25
25
  rspec-support (~> 3.10.0)
26
- rspec-support (3.10.0)
27
- rubocop (1.2.0)
26
+ rspec-support (3.10.1)
27
+ rubocop (1.7.0)
28
28
  parallel (~> 1.10)
29
29
  parser (>= 2.7.1.5)
30
30
  rainbow (>= 2.2.2, < 4.0)
31
- regexp_parser (>= 1.8)
31
+ regexp_parser (>= 1.8, < 3.0)
32
32
  rexml
33
- rubocop-ast (>= 1.0.1)
33
+ rubocop-ast (>= 1.2.0, < 2.0)
34
34
  ruby-progressbar (~> 1.7)
35
35
  unicode-display_width (>= 1.4.0, < 2.0)
36
- rubocop-ast (1.1.1)
36
+ rubocop-ast (1.4.0)
37
37
  parser (>= 2.7.1.5)
38
- ruby-progressbar (1.10.1)
39
- simplecov (0.19.0)
38
+ ruby-progressbar (1.11.0)
39
+ simplecov (0.21.0)
40
40
  docile (~> 1.1)
41
41
  simplecov-html (~> 0.11)
42
- simplecov-html (0.12.2)
42
+ simplecov_json_formatter (~> 0.1)
43
+ simplecov-html (0.12.3)
44
+ simplecov_json_formatter (0.1.2)
43
45
  unicode-display_width (1.7.0)
44
46
 
45
47
  PLATFORMS
@@ -52,4 +54,4 @@ DEPENDENCIES
52
54
  simplecov
53
55
 
54
56
  BUNDLED WITH
55
- 2.1.4
57
+ 2.2.3
data/README.md CHANGED
@@ -8,14 +8,17 @@ objects.
8
8
 
9
9
  ## Sample Application
10
10
 
11
- This repository includes a sample application in `samples/teeny-tiny`.
11
+ This repository includes sample applications in the `samples/` directory.
12
+
13
+ * teeny-tiny - A 20 second space shooter created during the DragonRuby TeenyTiny Game Jam
14
+ * gorillas-basic - A recreation of the gorillas_basic sample game that comes with DragonRuby
12
15
 
13
16
  ### Running the sample application
14
17
 
15
18
  1. Download the [latest release](https://github.com/guitsaru/draco/archive/main.zip) of this repository.
16
19
  2. Create a copy of DragonRuby GTK in a new folder.
17
- 3. Copy the teeny-tiny directory from draco into your new DragonRuby GTK folder. `cp -r draco/samples/teeny-tiny dragonruby/teeny-tiny`.
18
- 4. Run it using `./dragonruby teeny-tiny`.
20
+ 3. Copy the teeny-tiny directory from draco into your new DragonRuby GTK folder. `cp -r draco/samples/ dragonruby/`.
21
+ 4. Run them using `./dragonruby teeny-tiny` or `./dragonruby gorillas-basic`.
19
22
 
20
23
  ## Installation
21
24
 
@@ -4,8 +4,10 @@
4
4
  #
5
5
  # An Entity Component System is an architectural pattern used in game development to decouple behavior from objects.
6
6
  module Draco
7
+ class NotAComponentError < StandardError; end
8
+
7
9
  # Public: The version of the library. Draco uses semver to version releases.
8
- VERSION = "0.5.1"
10
+ VERSION = "0.6.0"
9
11
 
10
12
  # Public: A general purpose game object that consists of a unique id and a collection of Components.
11
13
  class Entity
@@ -73,15 +75,33 @@ module Draco
73
75
  def initialize(args = {})
74
76
  @id = args.fetch(:id, @@next_id)
75
77
  @@next_id = [@id + 1, @@next_id].max
76
- @components = ComponentStore.new(self)
77
78
  @subscriptions = []
78
79
 
80
+ setup_components(args)
81
+ after_initialize
82
+ end
83
+
84
+ # Internal: Sets up the default components for the class.
85
+ #
86
+ # args - A hash of arguments to pass into the generated components.
87
+ #
88
+ # Returns nothing.
89
+ def setup_components(args)
90
+ @components = ComponentStore.new(self)
91
+
79
92
  self.class.default_components.each do |component, default_args|
80
93
  arguments = default_args.merge(args[Draco.underscore(component.name.to_s).to_sym] || {})
81
94
  @components << component.new(arguments)
82
95
  end
83
96
  end
84
97
 
98
+ # Public: Callback run after the entity is initialized.
99
+ #
100
+ # This is empty by default but is present to allow plugins to tie into.
101
+ #
102
+ # Returns nothing.
103
+ def after_initialize; end
104
+
85
105
  # Public: Subscribe to an Entity's Component updates.
86
106
  #
87
107
  # subscriber - The object to notify when Components change.
@@ -91,18 +111,49 @@ module Draco
91
111
  @subscriptions << subscriber
92
112
  end
93
113
 
94
- # Internal: Notifies subscribers that components have been updated.
114
+ # Public: Callback run before a component is added.
95
115
  #
96
- # Returns nothing.
97
- def components_updated
98
- @subscriptions.each { |sub| sub.entity_updated(self) }
116
+ # component - The component that will be added.
117
+ #
118
+ # Returns the component to add.
119
+ def before_component_added(component)
120
+ component
121
+ end
122
+
123
+ # Public: Callback run after a component is added.
124
+ #
125
+ # component - The component that was added.
126
+ #
127
+ # Returns the added component.
128
+ def after_component_added(component)
129
+ @subscriptions.each { |sub| sub.component_added(self, component) }
130
+ component
131
+ end
132
+
133
+ # Public: Callback run before a component is deleted.
134
+ #
135
+ # component - The component that will be removed.
136
+ #
137
+ # Returns the component to remove.
138
+ def before_component_removed(component)
139
+ component
140
+ end
141
+
142
+ # Public: Callback run after a component is deleted.
143
+ #
144
+ # component - The component that was removed.
145
+ #
146
+ # Returns the removed component.
147
+ def after_component_removed(component)
148
+ @subscriptions.each { |sub| sub.component_removed(self, component) }
149
+ component
99
150
  end
100
151
 
101
152
  # Public: Serializes the Entity to save the current state.
102
153
  #
103
154
  # Returns a Hash representing the Entity.
104
155
  def serialize
105
- serialized = { id: id }
156
+ serialized = { class: self.class.name.to_s, id: id }
106
157
 
107
158
  components.each do |component|
108
159
  serialized[Draco.underscore(component.class.name.to_s).to_sym] = component.serialize
@@ -174,7 +225,6 @@ module Draco
174
225
  def <<(*components)
175
226
  components.flatten.each { |component| add(component) }
176
227
 
177
- @parent.components_updated
178
228
  self
179
229
  end
180
230
 
@@ -195,10 +245,16 @@ module Draco
195
245
  #
196
246
  # Returns the ComponentStore.
197
247
  def add(component)
248
+ unless component.is_a?(Draco::Component)
249
+ message = component.is_a?(Class) ? " You might need to initialize the component before you add it." : ""
250
+ raise Draco::NotAComponentError, "The given value is not a component.#{message}"
251
+ end
252
+
253
+ component = @parent.before_component_added(component)
198
254
  name = Draco.underscore(component.class.name.to_s).to_sym
199
255
  @components[name] = component
256
+ @parent.after_component_added(component)
200
257
 
201
- @parent.components_updated
202
258
  self
203
259
  end
204
260
 
@@ -210,10 +266,11 @@ module Draco
210
266
  #
211
267
  # Returns the ComponentStore.
212
268
  def delete(component)
269
+ component = @parent.before_component_removed(component)
213
270
  name = Draco.underscore(component.class.name.to_s).to_sym
214
271
  @components.delete(name)
272
+ @parent.after_component_removed(component)
215
273
 
216
- @parent.components_updated
217
274
  self
218
275
  end
219
276
 
@@ -264,6 +321,15 @@ module Draco
264
321
  attr_reader :attribute_options
265
322
  end
266
323
 
324
+ # Public: Creates a tag Component. If the tag already exists, return it.
325
+ #
326
+ # name - The string or symbol name of the component.
327
+ #
328
+ # Returns a class with subclass Draco::Component.
329
+ def self.Tag(name) # rubocop:disable Naming/MethodName
330
+ Draco::Tag(name)
331
+ end
332
+
267
333
  # Public: Initializes a new Component.
268
334
  #
269
335
  # values - The Hash of values to set for the Component instance (default: {}).
@@ -282,13 +348,21 @@ module Draco
282
348
  value = values.fetch(name.to_sym, options[:default])
283
349
  instance_variable_set("@#{name}", value)
284
350
  end
351
+ after_initialize
285
352
  end
286
353
 
354
+ # Public: Callback run after the component is initialized.
355
+ #
356
+ # This is empty by default but is present to allow plugins to tie into.
357
+ #
358
+ # Returns nothing.
359
+ def after_initialize; end
360
+
287
361
  # Public: Serializes the Component to save the current state.
288
362
  #
289
363
  # Returns a Hash representing the Component.
290
364
  def serialize
291
- attrs = {}
365
+ attrs = { class: self.class.name.to_s }
292
366
 
293
367
  instance_variables.each do |attr|
294
368
  name = attr.to_s.gsub("@", "").to_sym
@@ -371,8 +445,37 @@ module Draco
371
445
  def initialize(entities: [], world: nil)
372
446
  @entities = entities
373
447
  @world = world
448
+ after_initialize
374
449
  end
375
450
 
451
+ # Public: Callback run after the system is initialized.
452
+ #
453
+ # This is empty by default but is present to allow plugins to tie into.
454
+ #
455
+ # Returns nothing.
456
+ def after_initialize; end
457
+
458
+ # Public: Runs the system tick function.
459
+ #
460
+ # context - The context object of the current tick from the game engine. In DragonRuby this is `args`.
461
+ #
462
+ # Returns nothing.
463
+ def call(context)
464
+ before_tick(context)
465
+ tick(context)
466
+ after_tick(context)
467
+ self
468
+ end
469
+
470
+ # Public: Callback run before #tick is called.
471
+ #
472
+ # This is empty by default but is present to allow plugins to tie into.
473
+ #
474
+ # context - The context object of the current tick from the game engine. In DragonRuby this is `args`.
475
+ #
476
+ # Returns nothing.
477
+ def before_tick(context); end
478
+
376
479
  # Public: Runs the System logic for the current game engine tick.
377
480
  #
378
481
  # This is where the logic is implemented and it should be overriden for each System.
@@ -382,11 +485,19 @@ module Draco
382
485
  # Returns nothing
383
486
  def tick(context); end
384
487
 
488
+ # Public: Callback run after #tick is called.
489
+ #
490
+ # This is empty by default but is present to allow plugins to tie into.
491
+ #
492
+ # Returns nothing.
493
+ def after_tick(context); end
494
+
385
495
  # Public: Serializes the System to save the current state.
386
496
  #
387
497
  # Returns a Hash representing the System.
388
498
  def serialize
389
499
  {
500
+ class: self.class.name.to_s,
390
501
  entities: entities.map(&:serialize),
391
502
  world: world ? world.serialize : nil
392
503
  }
@@ -481,8 +592,29 @@ module Draco
481
592
  entity
482
593
  end
483
594
 
484
- @entities = EntityStore.new(default_entities + entities)
595
+ @entities = EntityStore.new(self, default_entities + entities)
485
596
  @systems = self.class.default_systems + systems
597
+ after_initialize
598
+ end
599
+
600
+ # Public: Callback run after the world is initialized.
601
+ #
602
+ # This is empty by default but is present to allow plugins to tie into.
603
+ #
604
+ # Returns nothing.
605
+ def after_initialize; end
606
+
607
+ # Public: Callback run before #tick is called.
608
+ #
609
+ # context - The context object of the current tick from the game engine. In DragonRuby this is `args`.
610
+ #
611
+ # Returns the systems to run during this tick.
612
+ def before_tick(_context)
613
+ systems.map do |system|
614
+ entities = filter(system.filter)
615
+
616
+ system.new(entities: entities, world: self)
617
+ end
486
618
  end
487
619
 
488
620
  # Public: Runs all of the Systems every tick.
@@ -491,13 +623,39 @@ module Draco
491
623
  #
492
624
  # Returns nothing
493
625
  def tick(context)
494
- systems.each do |system|
495
- entities = filter(system.filter)
496
-
497
- system.new(entities: entities, world: self).tick(context)
626
+ results = before_tick(context).map do |system|
627
+ system.call(context)
498
628
  end
629
+
630
+ after_tick(context, results)
499
631
  end
500
632
 
633
+ # Public: Callback run after #tick is called.
634
+ #
635
+ # This is empty by default but is present to allow plugins to tie into.
636
+ #
637
+ # context - The context object of the current tick from the game engine. In DragonRuby this is `args`.
638
+ # results - The System instances that were run.
639
+ #
640
+ # Returns nothing.
641
+ def after_tick(context, results); end
642
+
643
+ # Public: Callback to run when a component is added to an existing Entity.
644
+ #
645
+ # entity - The Entity the Component was added to.
646
+ # component - The Component that was added to the Entity.
647
+ #
648
+ # Returns nothing.
649
+ def component_added(entity, component); end
650
+
651
+ # Public: Callback to run when a component is added to an existing Entity.
652
+ #
653
+ # entity - The Entity the Component was removed from.
654
+ # component - The Component that was removed from the Entity.
655
+ #
656
+ # Returns nothing.
657
+ def component_removed(entity, component); end
658
+
501
659
  # Public: Finds all Entities that contain all of the given Components.
502
660
  #
503
661
  # components - An Array of Component classes to match.
@@ -512,6 +670,7 @@ module Draco
512
670
  # Returns a Hash representing the World.
513
671
  def serialize
514
672
  {
673
+ class: self.class.name.to_s,
515
674
  entities: @entities.map(&:serialize),
516
675
  systems: @systems.map { |system| system.name.to_s }
517
676
  }
@@ -531,10 +690,13 @@ module Draco
531
690
  class EntityStore
532
691
  include Enumerable
533
692
 
693
+ attr_reader :parent
694
+
534
695
  # Internal: Initializes a new EntityStore
535
696
  #
536
697
  # entities - The Entities to add to the EntityStore
537
- def initialize(*entities)
698
+ def initialize(parent, *entities)
699
+ @parent = parent
538
700
  @entity_to_components = Hash.new { |hash, key| hash[key] = Set.new }
539
701
  @component_to_entities = Hash.new { |hash, key| hash[key] = Set.new }
540
702
  @entity_ids = {}
@@ -586,13 +748,11 @@ module Draco
586
748
  entity.subscribe(self)
587
749
 
588
750
  @entity_ids[entity.id] = entity
589
-
590
751
  components = entity.components.map(&:class)
591
752
  @entity_to_components[entity].merge(components)
592
753
 
593
- components.each do |component|
594
- @component_to_entities[component].add(entity)
595
- end
754
+ components.each { |component| @component_to_entities[component].add(entity) }
755
+ entity.components.each { |component| @parent.component_added(entity, component) }
596
756
 
597
757
  self
598
758
  end
@@ -621,21 +781,26 @@ module Draco
621
781
  @entity_to_components.keys.each(&block)
622
782
  end
623
783
 
624
- # Internal: Updates the EntityStore when an Entity's Components are modified.
784
+ # Internal: Updates the EntityStore when an Entity's Components are added.
625
785
  #
626
- # entity - The Entity whose Components were updated.
786
+ # entity - The Entity the Component was added to.
787
+ # component - The Component that was added to the Entity.
627
788
  #
628
789
  # Returns nothing.
629
- def entity_updated(entity)
630
- old = @entity_to_components[entity].to_a
631
- components = entity.components.map(&:class)
632
- @entity_to_components[entity] = components
633
-
634
- added = components - old
635
- deleted = old - components
790
+ def component_added(entity, component)
791
+ @component_to_entities[component.class].add(entity)
792
+ @parent.component_added(entity, component)
793
+ end
636
794
 
637
- added.each { |component| @component_to_entities[component].add(entity) }
638
- deleted.each { |component| @component_to_entities[component].delete(entity) }
795
+ # Internal: Updates the EntityStore when an Entity's Components are removed.
796
+ #
797
+ # entity - The Entity the Component was removed from.
798
+ # component - The Component that was removed from the Entity.
799
+ #
800
+ # Returns nothing.
801
+ def component_removed(entity, component)
802
+ @component_to_entities[component.class].delete(entity)
803
+ @parent.component_removed(entity, component)
639
804
  end
640
805
  end
641
806
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Draco
4
+ # Public: Calculates the average time of
5
+ module Benchmark
6
+ def self.included(mod)
7
+ mod.prepend(World)
8
+ end
9
+
10
+ # Internal: Plugin implementation for World.
11
+ module World
12
+ def before_tick(context)
13
+ super
14
+ @system_timer_data = Hash.new { |h, k| h[k] = [] }
15
+ @systems.each { |system| system.prepend(System) }
16
+ end
17
+
18
+ def after_tick(context, results)
19
+ super
20
+ results.each do |system|
21
+ name = Draco.underscore(system.class.name.to_s).to_sym
22
+ @system_timer_data[name] = (system.timer * 1000.0).round(5)
23
+ end
24
+ end
25
+
26
+ def system_timers
27
+ @system_timer_data.sort_by { |_k, v| -v }.to_h
28
+ end
29
+ end
30
+
31
+ # Internal: Plugin implementation for System.
32
+ module System
33
+ def after_initialize
34
+ super
35
+ @timer = 0
36
+ end
37
+
38
+ def before_tick(context)
39
+ super
40
+ @start_time = Time.now
41
+ end
42
+
43
+ def after_tick(context)
44
+ super
45
+ @timer = Time.now - @start_time
46
+ end
47
+
48
+ def timer
49
+ @timer
50
+ end
51
+ end
52
+ end
53
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: draco
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Pruitt
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-31 00:00:00.000000000 Z
11
+ date: 2021-01-05 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A library for Entities, Components, and Systems in games.
14
14
  email:
@@ -35,6 +35,7 @@ files:
35
35
  - bin/setup
36
36
  - draco.gemspec
37
37
  - lib/draco.rb
38
+ - lib/draco/benchmark.rb
38
39
  homepage: https://github.com/guitsaru/draco
39
40
  licenses: []
40
41
  metadata:
@@ -55,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
56
  - !ruby/object:Gem::Version
56
57
  version: '0'
57
58
  requirements: []
58
- rubygems_version: 3.1.2
59
+ rubygems_version: 3.2.3
59
60
  signing_key:
60
61
  specification_version: 4
61
62
  summary: An ECS library.