draco 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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.