draco 0.1.0 → 0.2.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: 69a39f9f030f1724d09938774a480edfbbe5d342dd12e3e7cc4f652ec62ce88a
4
- data.tar.gz: 8c904f8141775453f61519ecdb9b8d9fc4ceeefdf066e38243a0da34b36efa48
3
+ metadata.gz: fabf6d184411f5d8dc5782499dfd2e7d07174333f6927566fb3fe51d59ae0df3
4
+ data.tar.gz: 5a647308729162793c8d4841b86f954298640d319dbfa836f8cb072f491c04cb
5
5
  SHA512:
6
- metadata.gz: 64bbfeac768aacc1edcd6dc9c7691deb7c5ce52b29299ee8f73693920ae7a470561667c113caa7a9410779f961de075ddc6561a639af343f7fad26e8e5d1bc38
7
- data.tar.gz: cf4631a868baea5f76f65e4f20be596825f3587f373b5d1bb9119a320d02ccf4cbfd65092e0b53be8ed7e10011c21f27a8f47b34b71051719af58bbc9bee542f
6
+ metadata.gz: 6e754598051acec8e35c62d40d52c255bf2187fa68b6e2e18112de90661af7faa53b9bb7fc402b8425df99bac3fb6d9d6a3cf15dabb0f956004dd43a63d04ae7
7
+ data.tar.gz: a558139ee8f227e9307c406453b18a5280ceac36ee0386fbd3998eebfe975cb0fc6e21effff52292aab4b594fbef56498a69623aa75586d6229a0b34740613a2
@@ -9,31 +9,32 @@ name: Ruby
9
9
 
10
10
  on:
11
11
  push:
12
- branches: [ main ]
12
+ branches: [main]
13
13
  pull_request:
14
- branches: [ main ]
14
+ branches: [main]
15
15
 
16
16
  jobs:
17
17
  test:
18
-
19
18
  runs-on: ubuntu-latest
20
19
 
21
20
  steps:
22
- - uses: actions/checkout@v2
23
- - name: Set up Ruby
24
- # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
25
- # change this to (see https://github.com/ruby/setup-ruby#versioning):
26
- # uses: ruby/setup-ruby@v1
27
- uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0
28
- with:
29
- ruby-version: 2.6
30
- - name: Install dependencies
31
- run: bundle install
32
- - name: Run tests
33
- run: rake spec
34
- - name: Upload coverage results
35
- uses: actions/upload-artifact@master
36
- if: always()
37
- with:
38
- name: coverage-report
39
- path: coverage
21
+ - uses: actions/checkout@v2
22
+ - name: Set up Ruby
23
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
24
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
25
+ # uses: ruby/setup-ruby@v1
26
+ uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0
27
+ with:
28
+ ruby-version: 2.6
29
+ - name: Install dependencies
30
+ run: bundle install
31
+ - name: Run tests
32
+ run: bundle exec rake spec
33
+ - name: Upload coverage results
34
+ uses: actions/upload-artifact@master
35
+ if: always()
36
+ with:
37
+ name: coverage-report
38
+ path: coverage
39
+ - name: Rubocop
40
+ run: bundle exec rubocop
@@ -0,0 +1,12 @@
1
+ Metrics/BlockLength:
2
+ Exclude:
3
+ - "Rakefile"
4
+ - "spec/**/*.rb"
5
+
6
+ AllCops:
7
+ NewCops: enable
8
+ Exclude:
9
+ - "benchmark.rb"
10
+
11
+ Style/StringLiterals:
12
+ EnforcedStyle: double_quotes
data/Gemfile CHANGED
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
 
5
+ # Commented out because we have no dependencies and it interferes with simplecov
3
6
  # Specify your gem's dependencies in draco.gemspec
4
- gemspec
7
+ # gemspec
5
8
 
6
9
  gem "rake", "~> 12.0"
7
10
  gem "rspec", "~> 3.0"
8
- gem 'simplecov', require: false
11
+ gem "rubocop", require: false
12
+ gem "simplecov", require: false
@@ -1,14 +1,16 @@
1
- PATH
2
- remote: .
3
- specs:
4
- draco (0.1.0)
5
-
6
1
  GEM
7
2
  remote: https://rubygems.org/
8
3
  specs:
4
+ ast (2.4.1)
9
5
  diff-lcs (1.4.4)
10
6
  docile (1.3.2)
7
+ parallel (1.19.2)
8
+ parser (2.7.2.0)
9
+ ast (~> 2.4.1)
10
+ rainbow (3.0.0)
11
11
  rake (12.3.3)
12
+ regexp_parser (1.8.2)
13
+ rexml (3.2.4)
12
14
  rspec (3.10.0)
13
15
  rspec-core (~> 3.10.0)
14
16
  rspec-expectations (~> 3.10.0)
@@ -22,18 +24,31 @@ GEM
22
24
  diff-lcs (>= 1.2.0, < 2.0)
23
25
  rspec-support (~> 3.10.0)
24
26
  rspec-support (3.10.0)
27
+ rubocop (1.2.0)
28
+ parallel (~> 1.10)
29
+ parser (>= 2.7.1.5)
30
+ rainbow (>= 2.2.2, < 4.0)
31
+ regexp_parser (>= 1.8)
32
+ rexml
33
+ rubocop-ast (>= 1.0.1)
34
+ ruby-progressbar (~> 1.7)
35
+ unicode-display_width (>= 1.4.0, < 2.0)
36
+ rubocop-ast (1.1.1)
37
+ parser (>= 2.7.1.5)
38
+ ruby-progressbar (1.10.1)
25
39
  simplecov (0.19.0)
26
40
  docile (~> 1.1)
27
41
  simplecov-html (~> 0.11)
28
42
  simplecov-html (0.12.2)
43
+ unicode-display_width (1.7.0)
29
44
 
30
45
  PLATFORMS
31
46
  ruby
32
47
 
33
48
  DEPENDENCIES
34
- draco!
35
49
  rake (~> 12.0)
36
50
  rspec (~> 3.0)
51
+ rubocop
37
52
  simplecov
38
53
 
39
54
  BUNDLED WITH
data/README.md CHANGED
@@ -23,7 +23,7 @@ These can be shared across many different types of game objects.
23
23
  class Visible < Draco::Component; end
24
24
  ```
25
25
 
26
- `Visible` is an example of a flag component. An entity either has it, or it doesn't. We can also associate data with our
26
+ `Visible` is an example of a label component. An entity either has it, or it doesn't. We can also associate data with our
27
27
  components.
28
28
 
29
29
  ```ruby
@@ -95,17 +95,17 @@ class RenderSpriteSystem < Draco::System
95
95
  # You can also access the world that called the system.
96
96
  camera = world.filter([Camera]).first
97
97
 
98
- entities.each do |entity|
99
- next unless
100
-
101
- args.outputs.sprites << {
102
- x: entity.position.x,
103
- y: entity.position.y,
98
+ sprites = entities.select { |e| entity_in_camera?(e, camera) }.map do |entity|
99
+ {
100
+ x: entity.position.x - camera.position.x,
101
+ y: entity.position.y - camera.position.y,
104
102
  w: entity.sprite.w,
105
103
  h: entity.sprite.h,
106
104
  path: entity.sprite.path
107
105
  }
108
106
  end
107
+
108
+ args.outputs.sprites << sprites
109
109
  end
110
110
 
111
111
  def entity_in_camera?(entity, camera)
@@ -129,10 +129,19 @@ world.systems << RenderSpriteSystem
129
129
  world.tick(args)
130
130
  ```
131
131
 
132
+ ## Learn More
133
+
134
+ Here are some good resources to learn about Entity Component Systems
135
+
136
+ - [Evolve Your Heirarchy](https://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/)
137
+ - [Overwatch Gameplay Architecture and Netcode](https://www.youtube.com/watch?v=W3aieHjyNvw)
138
+
132
139
  ## Commercial License
133
140
 
134
141
  Draco is licensed under AGPL. You can purchase the right to use Draco under the [commercial license](https://github.com/guitsaru/draco/blob/master/COMM-LICENSE).
135
142
 
143
+ <a class="gumroad-button" href="https://guitsaru.itch.io/draco" target="_blank">Purchase Commercial License</a>
144
+
136
145
  ## Development
137
146
 
138
147
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rspec/core/rake_task"
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/draco"
4
+ require "benchmark"
5
+
6
+ puts "Defining Component"
7
+ class SampleComponent < Draco::Component
8
+ attribute :a, default: 0
9
+ attribute :b, default: 0
10
+ attribute :c, default: 0
11
+ attribute :d, default: 0
12
+ attribute :e, default: 0
13
+ attribute :f, default: 0
14
+ attribute :g, default: 0
15
+ end
16
+
17
+ puts "Defining System"
18
+ class SampleSystem < Draco::System
19
+ filter SampleComponent
20
+
21
+ def tick(_)
22
+ entities.each do |entity|
23
+ entity.sample_component.a
24
+ end
25
+ end
26
+ end
27
+
28
+ puts "Dynamic Components"
29
+ dynamic_components = (1..1000).map do |i|
30
+ name = "DynamicComponent#{i}"
31
+ klass = Class.new(SampleComponent)
32
+
33
+ Object.const_set(name, klass)
34
+ klass
35
+ end
36
+
37
+ puts "Defining Entity"
38
+ class SampleEntity < Draco::Entity
39
+ component SampleComponent
40
+ end
41
+
42
+ puts "Adding dynamic components to Entity"
43
+ dynamic_components.each do |dynamic_component|
44
+ SampleEntity.component(dynamic_component) if [true, false, false, false, false].sample
45
+ end
46
+
47
+ puts "Defining World"
48
+ world = Draco::World.new
49
+ world.systems << SampleSystem
50
+
51
+ Benchmark.bm do |bm|
52
+ bm.report("initialize entity") do
53
+ SampleEntity.new
54
+ end
55
+ end
56
+
57
+ puts "Generating 10_000 entities"
58
+ (1..10_000).each do |i|
59
+ print "#{i} " if (i % 100).zero?
60
+
61
+ entity = SampleEntity.new
62
+ world.entities << entity
63
+ end
64
+
65
+ puts
66
+ puts "Goal: #{1.0 / 60.0}"
67
+
68
+ Benchmark.bm do |bm|
69
+ bm.report("tick") do
70
+ world.tick(nil)
71
+ end
72
+
73
+ bm.report("filter") { world.filter(SampleComponent) }
74
+ end
75
+
76
+ # Initial Implementation
77
+ # user system total real
78
+ # tick 0.345672 0.006669 0.352341 ( 0.354792)
79
+ # filter 0.288709 0.000000 0.288709 ( 0.290659)
80
+
81
+ # After optimization
82
+ # user system total real
83
+ # tick 0.007575 0.000005 0.007580 ( 0.007589)
84
+ # filter 0.000013 0.000000 0.000013 ( 0.000012)
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require "draco"
@@ -1,4 +1,6 @@
1
- require_relative 'lib/draco'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/draco"
2
4
 
3
5
  Gem::Specification.new do |spec|
4
6
  spec.name = "draco"
@@ -6,17 +8,17 @@ Gem::Specification.new do |spec|
6
8
  spec.authors = ["Matt Pruitt"]
7
9
  spec.email = ["matt@guitsaru.com"]
8
10
 
9
- spec.summary = %q{An ECS library.}
10
- spec.description = %q{A library for Entities, Components, and Systems in games.}
11
- spec.homepage = "https://mattpruitt.com"
12
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
11
+ spec.summary = "An ECS library."
12
+ spec.description = "A library for Entities, Components, and Systems in games."
13
+ spec.homepage = "https://github.com/guitsaru/draco"
14
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
13
15
 
14
16
  spec.metadata["homepage_uri"] = spec.homepage
15
17
  spec.metadata["source_code_uri"] = "https://github.com/guitsaru/draco"
16
18
 
17
19
  # Specify which files should be added to the gem when it is released.
18
20
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
20
22
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
23
  end
22
24
  spec.bindir = "exe"
@@ -1,27 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Public: Draco is an Entity Component System for use in game engines like DragonRuby.
2
4
  #
3
- # An Entity Component System is an architectural pattern used in game development to decouple behaviour from game objects.
5
+ # An Entity Component System is an architectural pattern used in game development to decouple behavior from objects.
4
6
  module Draco
5
7
  # Public: The version of the library. Draco uses semver to version releases.
6
- VERSION = "0.1.0"
8
+ VERSION = "0.2.0"
7
9
 
8
10
  # Public: A general purpose game object that consists of a unique id and a collection of Components.
9
11
  class Entity
12
+ # rubocop:disable Style/ClassVars
10
13
  @default_components = {}
11
14
  @@next_id = 1
12
15
 
13
- # Public: Returns the Integer id of the Entity.
14
- attr_reader :id
15
-
16
- # Public: Returns the Array of the Entity's components
17
- attr_reader :components
18
-
19
16
  # Internal: Resets the default components for each class that inherites Entity.
20
17
  #
21
18
  # sub - The class that is inheriting Entity.
22
19
  #
23
20
  # Returns nothing.
24
21
  def self.inherited(sub)
22
+ super
25
23
  sub.instance_variable_set(:@default_components, {})
26
24
  end
27
25
 
@@ -42,10 +40,16 @@ module Draco
42
40
  end
43
41
 
44
42
  # Internal: Returns the default components for the class.
45
- def self.default_components
46
- @default_components
43
+ class << self
44
+ attr_reader :default_components
47
45
  end
48
46
 
47
+ # Public: Returns the Integer id of the Entity.
48
+ attr_reader :id
49
+
50
+ # Public: Returns the Array of the Entity's components
51
+ attr_reader :components
52
+
49
53
  # Public: Initialize a new Entity.
50
54
  #
51
55
  # args - A Hash of arguments to pass into the components.
@@ -60,22 +64,39 @@ module Draco
60
64
  def initialize(args = {})
61
65
  @id = args.fetch(:id, @@next_id)
62
66
  @@next_id = [@id + 1, @@next_id].max
63
- @components = []
67
+ @components = ComponentStore.new(self)
68
+ @subscriptions = []
64
69
 
65
70
  self.class.default_components.each do |component, default_args|
66
- arguments = default_args.merge(args[underscore(component.name.to_s).to_sym] || {})
71
+ arguments = default_args.merge(args[Draco.underscore(component.name.to_s).to_sym] || {})
67
72
  @components << component.new(arguments)
68
73
  end
69
74
  end
70
75
 
76
+ # Public: Subscribe to an Entity's Component updates.
77
+ #
78
+ # subscriber - The object to notify when Components change.
79
+ #
80
+ # Returns nothing.
81
+ def subscribe(subscriber)
82
+ @subscriptions << subscriber
83
+ end
84
+
85
+ # Internal: Notifies subscribers that components have been updated.
86
+ #
87
+ # Returns nothing.
88
+ def components_updated
89
+ @subscriptions.each { |sub| sub.entity_updated(self) }
90
+ end
91
+
71
92
  # Public: Serializes the Entity to save the current state.
72
93
  #
73
94
  # Returns a Hash representing the Entity.
74
95
  def serialize
75
- serialized = {id: id}
96
+ serialized = { id: id }
76
97
 
77
98
  components.each do |component|
78
- serialized[underscore(component.class.name.to_s).to_sym] = component.serialize
99
+ serialized[Draco.underscore(component.class.name.to_s).to_sym] = component.serialize
79
100
  end
80
101
 
81
102
  serialized
@@ -111,31 +132,95 @@ module Draco
111
132
  #
112
133
  # Returns the Component instance.
113
134
 
114
- def method_missing(m, *args, &block)
115
- component = components.find { |c| underscore(c.class.name.to_s) == m.to_s }
135
+ def method_missing(method, *args, &block)
136
+ component = components[method.to_sym]
116
137
  return component if component
117
138
 
118
139
  super
119
140
  end
120
141
 
121
- # Internal: Converts a camel cased string to an underscored string.
122
- #
123
- # Examples
124
- #
125
- # underscore("CamelCase")
126
- # # => "camel_case"
127
- #
128
- # Returns a String.
129
- def underscore(string)
130
- string.split("::").last.bytes.map.with_index do |byte, i|
131
- if byte > 64 && byte < 97
132
- downcased = byte + 32
133
- i == 0 ? downcased.chr : "_#{downcased.chr}"
134
- else
135
- byte.chr
136
- end
137
- end.join
142
+ def respond_to_missing?(method, _include_private = false)
143
+ !!components[method.to_sym] or super
138
144
  end
145
+
146
+ # Internal: An Array that notifies it's parent of updates.
147
+ class ComponentStore
148
+ include Enumerable
149
+
150
+ # Internal: Initializes a new ComponentStore
151
+ #
152
+ # parent - The object to notify about updates.
153
+ def initialize(parent)
154
+ @components = {}
155
+ @parent = parent
156
+ end
157
+
158
+ # Internal: Adds Components to the ComponentStore.
159
+ #
160
+ # Side Effects: Notifies the parent that the components were updated.
161
+ #
162
+ # components - The Component or Array list of Components to add to the ComponentStore.
163
+ #
164
+ # Returns the ComponentStore.
165
+ def <<(*components)
166
+ components.flatten.each { |component| add(component) }
167
+
168
+ @parent.components_updated
169
+ self
170
+ end
171
+
172
+ # Internal: Returns the Component with the underscored Component name.
173
+ #
174
+ # underscored_component - The String underscored version of the Component's class name.
175
+ #
176
+ # Returns the Component instance or nil.
177
+ def [](underscored_component)
178
+ @components[underscored_component]
179
+ end
180
+
181
+ # Internal: Adds a Component to the ComponentStore.
182
+ #
183
+ # Side Effects: Notifies the parent that the components were updated.
184
+ #
185
+ # components - The Component to add to the ComponentStore.
186
+ #
187
+ # Returns the ComponentStore.
188
+ def add(component)
189
+ name = Draco.underscore(component.class.name.to_s).to_sym
190
+ @components[name] = component
191
+
192
+ @parent.components_updated
193
+ self
194
+ end
195
+
196
+ # Internal: Removes a Component from the ComponentStore.
197
+ #
198
+ # Side Effects: Notifies the parent that the components were updated.
199
+ #
200
+ # components - The Component to remove from the ComponentStore.
201
+ #
202
+ # Returns the ComponentStore.
203
+ def delete(component)
204
+ name = Draco.underscore(component.class.name.to_s).to_sym
205
+ @components.delete(name)
206
+
207
+ @parent.components_updated
208
+ self
209
+ end
210
+
211
+ # Internal: Returns true if there are no entries in the Set.
212
+ #
213
+ # Returns a boolean.
214
+ def empty?
215
+ @components.empty?
216
+ end
217
+
218
+ # Internal: Returns an Enumerator for all of the Entities.
219
+ def each(&block)
220
+ @components.values.each(&block)
221
+ end
222
+ end
223
+ # rubocop:enable Style/ClassVars
139
224
  end
140
225
 
141
226
  # Public: The data to associate with an Entity.
@@ -148,6 +233,7 @@ module Draco
148
233
  #
149
234
  # Returns nothing.
150
235
  def self.inherited(sub)
236
+ super
151
237
  sub.instance_variable_set(:@attribute_options, {})
152
238
  end
153
239
 
@@ -160,12 +246,13 @@ module Draco
160
246
  # Returns nothing.
161
247
  def self.attribute(name, options = {})
162
248
  attr_accessor name
249
+
163
250
  @attribute_options[name] = options
164
251
  end
165
252
 
166
253
  # Internal: Returns the Hash attribute options for the current Class.
167
- def self.attribute_options
168
- @attribute_options
254
+ class << self
255
+ attr_reader :attribute_options
169
256
  end
170
257
 
171
258
  # Public: Initializes a new Component.
@@ -213,7 +300,8 @@ module Draco
213
300
  end
214
301
  end
215
302
 
216
- # Public: Systems contain the logic of the game. The System runs on each tick and manipulates the Entities in the World.
303
+ # Public: Systems contain the logic of the game.
304
+ # The System runs on each tick and manipulates the Entities in the World.
217
305
  class System
218
306
  @filter = []
219
307
 
@@ -240,6 +328,7 @@ module Draco
240
328
  #
241
329
  # Returns nothing.
242
330
  def self.inherited(sub)
331
+ super
243
332
  sub.instance_variable_set(:@filter, [])
244
333
  end
245
334
 
@@ -295,7 +384,7 @@ module Draco
295
384
  # entities - The Array of Entities for the World (default: []).
296
385
  # systems - The Array of System Classes for the World (default: []).
297
386
  def initialize(entities: [], systems: [])
298
- @entities = entities
387
+ @entities = EntityStore.new(entities)
299
388
  @systems = systems
300
389
  end
301
390
 
@@ -317,8 +406,8 @@ module Draco
317
406
  # components - An Array of Component classes to match.
318
407
  #
319
408
  # Returns an Array of matching Entities.
320
- def filter(components)
321
- entities.select { |e| (components - e.components.map(&:class)).empty? }
409
+ def filter(*components)
410
+ entities[components.flatten]
322
411
  end
323
412
 
324
413
  # Public: Serializes the World to save the current state.
@@ -340,5 +429,226 @@ module Draco
340
429
  def to_s
341
430
  serialize.to_s
342
431
  end
432
+
433
+ # Internal: Stores Entities with better performance than Array.
434
+ class EntityStore
435
+ include Enumerable
436
+
437
+ # Internal: Initializes a new EntityStore
438
+ #
439
+ # entities - The Entities to add to the EntityStore
440
+ def initialize(*entities)
441
+ @entity_to_components = Hash.new { |hash, key| hash[key] = Set.new }
442
+ @component_to_entities = Hash.new { |hash, key| hash[key] = Set.new }
443
+
444
+ self << entities
445
+ end
446
+
447
+ # Internal: Gets all Entities that implement all of the given Components
448
+ #
449
+ # components - The Component Classes to filter by
450
+ #
451
+ # Returns a Set list of Entities
452
+ def [](*components)
453
+ components
454
+ .flatten
455
+ .map { |component| @component_to_entities[component] }
456
+ .reduce { |acc, i| i & acc }
457
+ end
458
+
459
+ # Internal: Adds Entities to the EntityStore
460
+ #
461
+ # entities - The Entity or Array list of Entities to add to the EntityStore.
462
+ #
463
+ # Returns the EntityStore
464
+ def <<(entities)
465
+ Array(entities).flatten.each { |e| add(e) }
466
+ self
467
+ end
468
+
469
+ # Internal: Adds an Entity to the EntityStore.
470
+ #
471
+ # entity - The Entity to add to the EntityStore.
472
+ #
473
+ # Returns the EntityStore
474
+ def add(entity)
475
+ entity.subscribe(self)
476
+
477
+ components = entity.components.map(&:class)
478
+ @entity_to_components[entity].merge(components)
479
+
480
+ components.each do |component|
481
+ @component_to_entities[component].add(entity)
482
+ end
483
+
484
+ self
485
+ end
486
+
487
+ # Internal: Removes an Entity from the EntityStore.
488
+ #
489
+ # entity - The Entity to remove from the EntityStore.
490
+ #
491
+ # Returns the EntityStore
492
+ def delete(entity)
493
+ components = @entity_to_components.delete(entity)
494
+
495
+ components.map(&:class).each do |component|
496
+ @component_to_entities[component].delete(entity)
497
+ end
498
+ end
499
+
500
+ # Internal: Returns true if the EntityStore has no Entities.
501
+ def empty?
502
+ @entity_to_components.empty?
503
+ end
504
+
505
+ # Internal: Returns an Enumerator for all of the Entities.
506
+ def each(&block)
507
+ @entity_to_components.keys.each(&block)
508
+ end
509
+
510
+ # Internal: Updates the EntityStore when an Entity's Components are modified.
511
+ #
512
+ # entity - The Entity whose Components were updated.
513
+ #
514
+ # Returns nothing.
515
+ def entity_updated(entity)
516
+ old = @entity_to_components[entity].to_a
517
+ components = entity.components.map(&:class)
518
+ @entity_to_components[entity] = components
519
+
520
+ added = components - old
521
+ deleted = old - components
522
+
523
+ added.each { |component| @component_to_entities[component].add(entity) }
524
+ deleted.each { |component| @component_to_entities[component].delete(entity) }
525
+ end
526
+ end
527
+ end
528
+
529
+ # Internal: An implementation of Set.
530
+ class Set
531
+ include Enumerable
532
+
533
+ # Internal: Initializes a new Set.
534
+ #
535
+ # entries - The initial Array list of entries for the Set
536
+ def initialize(entries = [])
537
+ @hash = {}
538
+ merge(entries)
539
+ end
540
+
541
+ # Internal: Adds a new entry to the Set.
542
+ #
543
+ # entry - The object to add to the Set.
544
+ #
545
+ # Returns the Set.
546
+ def add(entry)
547
+ @hash[entry] = true
548
+ self
549
+ end
550
+
551
+ # Internal: Adds a new entry to the Set.
552
+ #
553
+ # entry - The object to add to the Set.
554
+ #
555
+ # Returns the Set.
556
+ def delete(entry)
557
+ @hash.delete(entry)
558
+ self
559
+ end
560
+
561
+ # Internal: Adds multiple objects to the Set.
562
+ #
563
+ # entry - The Array list of objects to add to the Set.
564
+ #
565
+ # Returns the Set.
566
+ def merge(entries)
567
+ Array(entries).each { |entry| add(entry) }
568
+ self
569
+ end
570
+
571
+ # Internal: Returns an Enumerator for all of the entries in the Set.
572
+ def each(&block)
573
+ @hash.keys.each(&block)
574
+ end
575
+
576
+ # Internal: Returns true if the object is in the Set.
577
+ #
578
+ # member - The object to search the Set for.
579
+ #
580
+ # Returns a boolean.
581
+ def member?(member)
582
+ @hash.key?(member)
583
+ end
584
+
585
+ # Internal: Returns true if there are no entries in the Set.
586
+ #
587
+ # Returns a boolean.
588
+ def empty?
589
+ @hash.empty?
590
+ end
591
+
592
+ # Internal: Returns the intersection of two Sets.
593
+ #
594
+ # other - The Set to intersect with
595
+ #
596
+ # Returns a new Set of all of the common entries.
597
+ def &(other)
598
+ response = Set.new
599
+ each do |key, _|
600
+ response.add(key) if other.member?(key)
601
+ end
602
+
603
+ response
604
+ end
605
+
606
+ def ==(other)
607
+ hash == other.hash
608
+ end
609
+
610
+ # Internal: Returns a unique hash value of the Set.
611
+ def hash
612
+ @hash.hash
613
+ end
614
+
615
+ # Internal: Returns an Array representation of the Set.
616
+ def to_a
617
+ @hash.keys
618
+ end
619
+
620
+ # Internal: Serializes the Set.
621
+ def serialize
622
+ to_a.inspect
623
+ end
624
+
625
+ # Internal: Inspects the Set.
626
+ def inspect
627
+ to_a.inspect
628
+ end
629
+
630
+ # Internal: Returns a String representation of the Set.
631
+ def to_s
632
+ to_a.to_s
633
+ end
634
+ end
635
+
636
+ # Internal: Converts a camel cased string to an underscored string.
637
+ #
638
+ # Examples
639
+ #
640
+ # underscore("CamelCase")
641
+ # # => "camel_case"
642
+ #
643
+ # Returns a String.
644
+ def self.underscore(string)
645
+ string.split("::").last.bytes.map.with_index do |byte, i|
646
+ if byte > 64 && byte < 97
647
+ downcased = byte + 32 # gemspec
648
+ i.zero? ? downcased.chr : "_#{downcased.chr}"
649
+ else
650
+ byte.chr
651
+ end
652
+ end.join
343
653
  end
344
654
  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.1.0
4
+ version: 0.2.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-11-04 00:00:00.000000000 Z
11
+ date: 2020-11-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A library for Entities, Components, and Systems in games.
14
14
  email:
@@ -21,7 +21,7 @@ files:
21
21
  - ".github/workflows/ruby.yml"
22
22
  - ".gitignore"
23
23
  - ".rspec"
24
- - ".travis.yml"
24
+ - ".rubocop.yml"
25
25
  - CODE_OF_CONDUCT.md
26
26
  - COMM-LICENSE
27
27
  - Gemfile
@@ -29,14 +29,15 @@ files:
29
29
  - LICENSE
30
30
  - README.md
31
31
  - Rakefile
32
+ - benchmark.rb
32
33
  - bin/console
33
34
  - bin/setup
34
35
  - draco.gemspec
35
36
  - lib/draco.rb
36
- homepage: https://mattpruitt.com
37
+ homepage: https://github.com/guitsaru/draco
37
38
  licenses: []
38
39
  metadata:
39
- homepage_uri: https://mattpruitt.com
40
+ homepage_uri: https://github.com/guitsaru/draco
40
41
  source_code_uri: https://github.com/guitsaru/draco
41
42
  post_install_message:
42
43
  rdoc_options: []
@@ -46,7 +47,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
46
47
  requirements:
47
48
  - - ">="
48
49
  - !ruby/object:Gem::Version
49
- version: 2.3.0
50
+ version: 2.4.0
50
51
  required_rubygems_version: !ruby/object:Gem::Requirement
51
52
  requirements:
52
53
  - - ">="
@@ -1,6 +0,0 @@
1
- ---
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.7.1
6
- before_install: gem install bundler -v 2.1.4