baku 0.1.0 → 0.2.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
  SHA1:
3
- metadata.gz: 28ac783aa88b25f6c40da785a9455b8ceffc57ce
4
- data.tar.gz: e1352417ee1b095d41da14d033fa84bdadb57097
3
+ metadata.gz: 1653036472420b349cb3e013e0d55a589ebe29cb
4
+ data.tar.gz: 4ce15a89513b81e4803b800a3083b7e5d51ce45a
5
5
  SHA512:
6
- metadata.gz: 5c68d176ad7eda25d66ec1674c3cf5a739c7eea941a3edcbe0133c8aa14242af7fd7277e95e20198e8fc9fd6b06bbead8e04ff5210ac270bb40a0a7472e3255f
7
- data.tar.gz: 0fb615ce7c1b69ed0aad6147c96e9f02b5da16e62e8daf6c77dcd37194c0316ac5c75aef5c08135c3a06279f4249596500bcff3bca0b93d291af26d68249a234
6
+ metadata.gz: cc818103da055af5358e1dfeab535945c31be1b847f931b54febebbe407e6ab3a3427660de8abeb5063d24ed2bf01ad958ce32af3b89bed1338061dd71ef6b35
7
+ data.tar.gz: 497c1f10325f892d97dbe9a48ef30052b4d03edc26e6cf20453e9ca52b32f7cee10d3a7bf7c5215fb85f4f74f1f4d2cf86eafda98b346bd926801dcb1445b487
data/.gitignore CHANGED
@@ -10,3 +10,6 @@
10
10
 
11
11
  # rspec failure tracking
12
12
  .rspec_status
13
+ .byebug_history
14
+ .ruby-version
15
+ .ruby-gemset
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # 0.2.0
2
+
3
+ * Add ability to remove components from an entity.
4
+ * Extract component mask logic into its own class.
5
+ * Remove a few instances of unnecessary coupling.
6
+ * Write remaining tests.
7
+
8
+ # 0.1.0
9
+
10
+ * Initial release!
data/README.md CHANGED
@@ -58,7 +58,6 @@ class MovementSystem < Baku::System
58
58
  def process_entity(entity, transform, velocity)
59
59
  transform.x += velocity.x
60
60
  transform.y += velocity.y
61
- transform.z += velocity.z
62
61
  end
63
62
  end
64
63
  ```
data/baku.gemspec CHANGED
@@ -24,4 +24,5 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency "bundler", "~> 1.15"
25
25
  spec.add_development_dependency "rake", "~> 10.0"
26
26
  spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.add_development_dependency "pry-byebug", "~> 3.5.0"
27
28
  end
@@ -1,9 +1,7 @@
1
1
  module Baku
2
2
  class Component
3
- attr_reader :id
4
-
5
3
  def initialize
6
- @id = SecureRandom.uuid
4
+
7
5
  end
8
6
  end
9
7
  end
@@ -0,0 +1,51 @@
1
+ module Baku
2
+ class ComponentMask
3
+ attr_reader :value
4
+
5
+ @@component_set = Set.new
6
+
7
+ class << self
8
+ def record_components(components)
9
+ components.each do |component|
10
+ @@component_set << component
11
+ end
12
+ end
13
+
14
+ def from_components(components)
15
+ record_components(components)
16
+
17
+ mask_value = 0
18
+
19
+ @@component_set.each_with_index do |component, index|
20
+ mask_value |= (1 << index) if components.include?(component)
21
+ end
22
+
23
+ ComponentMask.new(mask_value)
24
+ end
25
+ end
26
+
27
+ def initialize(value)
28
+ @value = value
29
+ end
30
+
31
+ def add_component(component_class)
32
+
33
+ end
34
+
35
+ def matches?(other_mask)
36
+ @value & other_mask.value == @value
37
+ end
38
+
39
+ def ==(other)
40
+ @value == other.value
41
+ end
42
+
43
+ def eql?(other)
44
+ self == other
45
+ end
46
+
47
+ def hash
48
+ value
49
+ end
50
+ end
51
+ end
data/lib/baku/entity.rb CHANGED
@@ -1,12 +1,11 @@
1
1
  module Baku
2
2
  class Entity
3
- attr_reader :id, :components, :tags
3
+ include Baku::EventDispatcher
4
4
 
5
- def initialize(world, tags = [])
6
- @world = world
5
+ attr_reader :components, :tags
6
+
7
+ def initialize(tags = [])
7
8
  @tags = tags
8
-
9
- @id = SecureRandom.uuid
10
9
  @components = {}
11
10
  end
12
11
 
@@ -16,9 +15,11 @@ module Baku
16
15
  new("Entity already has component: #{component.class}")
17
16
  end
18
17
 
19
- # TODO: this is some pretty ugly coupling, figure out if there's a cleaner
20
- # way to do this. Callbacks or something.
21
- @world.entity_manager.entity_add_component(self, component)
18
+ components[component.class] = component
19
+
20
+ update_component_mask
21
+
22
+ dispatch_event(:component_added, self, component)
22
23
  end
23
24
 
24
25
  def remove_component(component_class)
@@ -27,13 +28,25 @@ module Baku
27
28
  new("Entity does not have component: #{component_class}")
28
29
  end
29
30
 
30
- # TODO: this is some pretty ugly coupling, figure out if there's a cleaner
31
- # way to do this. Callbacks or something.
32
- @world.entity_manager.entity_remove_component(self, get_component(component_class))
31
+ @components.delete(component_class)
32
+
33
+ update_component_mask
34
+
35
+ dispatch_event(:component_removed, self, @components[component_class])
33
36
  end
34
37
 
35
38
  def get_component(component_class)
36
39
  @components[component_class]
37
40
  end
41
+
42
+ def component_mask
43
+ @component_mask ||= ComponentMask.from_components(@components)
44
+ end
45
+
46
+ private
47
+
48
+ def update_component_mask
49
+ @component_mask = ComponentMask.from_components(@components)
50
+ end
38
51
  end
39
52
  end
@@ -1,54 +1,51 @@
1
1
  module Baku
2
+ # The EntityManager stores entities in such a way that they can be retrieved
3
+ # efficiently by either ComponentMask or tag. Before storing any entities, the
4
+ # ComponentMasks that we wish to match on should be registered. This step is
5
+ # performed transparently by the World whenever a System is added.
2
6
  class EntityManager
3
7
  def initialize
4
- @entities_by_system_mask = {}
8
+ @entities_by_component_mask = {}
5
9
  @entities_by_tag = {}
6
-
7
- @component_set = Set.new
8
- @system_mask_cache = {}
9
10
  end
10
11
 
11
- def register_entity(entity)
12
+ def register_component_mask(component_mask)
13
+ @entities_by_component_mask[component_mask] = []
14
+ end
15
+
16
+ def add_entity(entity)
17
+ add_entity_to_matching_component_lists(entity)
18
+
19
+ entity.add_event_listener(:component_added,
20
+ method(:on_entity_component_added))
21
+ entity.add_event_listener(:component_removed,
22
+ method(:on_entity_component_removed))
23
+
12
24
  entity.tags.each do |tag|
13
25
  @entities_by_tag[tag] ||= []
14
26
  @entities_by_tag[tag] << entity
15
27
  end
16
28
  end
17
29
 
18
- def register_system(system)
19
- system.components.each do |component|
20
- @component_set << component
30
+ def remove_entity(entity)
31
+ entity.tags.each do |tag|
32
+ @entities_by_tag[tag].delete(entity)
21
33
  end
22
-
23
- system_component_mask =
24
- get_component_mask(system.components)
25
-
26
- @system_mask_cache[system] = system_component_mask
27
34
 
28
- @entities_by_system_mask[system_component_mask] = []
29
- end
30
-
31
- # TODO: there may be a more efficent way to do this, like by comparing
32
- # the diff of the mask before and after, but this works for now.
33
- def entity_add_component(entity, component)
34
- old_mask = get_component_mask(entity.components)
35
- entity.components[component.class] = component
36
- new_mask = get_component_mask(entity.components)
37
-
38
- update_system_membership(entity, old_mask, new_mask)
39
- end
35
+ entity.remove_event_listener(:component_added,
36
+ method(:on_entity_component_added))
37
+ entity.remove_event_listener(:component_removed,
38
+ method(:on_entity_component_removed))
40
39
 
41
- def entity_remove_component(entity, component)
42
- old_mask = get_component_mask(entity.components)
43
- entity.components.delete(component.class)
44
- new_mask = get_component_mask(entity.components)
45
-
46
- update_system_membership(entity, old_mask, new_mask)
40
+ @entities_by_component_mask.each do |component_mask, entities|
41
+ if component_mask.matches?(entity.component_mask)
42
+ entities.delete(entity)
43
+ end
44
+ end
47
45
  end
48
46
 
49
- def get_entities_for_system(system)
50
- system_mask = @system_mask_cache[system]
51
- @entities_by_system_mask[system_mask]
47
+ def get_entities(component_mask)
48
+ @entities_by_component_mask[component_mask]
52
49
  end
53
50
 
54
51
  def get_entities_by_tag(tag)
@@ -57,27 +54,30 @@ module Baku
57
54
 
58
55
  private
59
56
 
60
- def get_component_mask(components)
61
- mask = 0
62
-
63
- @component_set.each_with_index do |c, i|
64
- mask |= (1 << i) if components.include?(c)
57
+ def add_entity_to_matching_component_lists(entity)
58
+ @entities_by_component_mask.each do |component_mask, entities|
59
+ if component_mask.matches?(entity.component_mask)
60
+ entities << entity
61
+ end
65
62
  end
63
+ end
66
64
 
67
- mask
65
+ def on_entity_component_added(entity, component)
66
+ add_entity_to_matching_component_lists(entity)
68
67
  end
69
68
 
70
- def update_system_membership(entity, old_mask, new_mask)
71
- @entities_by_system_mask.each do |system_mask, entities|
72
- old_match = (system_mask & old_mask == system_mask)
73
- new_match = (system_mask & new_mask == system_mask)
69
+ def on_entity_component_removed(entity, component)
70
+ old_mask = ComponentMask.from_components(entity.components + component)
71
+ new_mask = entity.component_mask
72
+
73
+ @entities_by_component_mask.each do |component_mask, entities|
74
+ old_match = component_mask.match?(old_mask)
75
+ new_match = component_mask.match?(new_mask)
74
76
 
75
77
  if old_match && !new_match
76
78
  entities.delete(entity)
77
- elsif !old_match && new_match
78
- entities << entity
79
79
  end
80
- end
80
+ end
81
81
  end
82
82
  end
83
83
  end
@@ -0,0 +1,19 @@
1
+ module Baku
2
+ module EventDispatcher
3
+ def add_event_listener(event_name, method)
4
+ @event_listeners ||= {}
5
+ @event_listeners[event_name] ||= []
6
+
7
+ @event_listeners[event_name] << method
8
+ end
9
+
10
+ def remove_event_listener(event_name, method)
11
+ @event_listeners[event_name].delete(method)
12
+ end
13
+
14
+ def dispatch_event(event_name, *args)
15
+ return unless @event_listeners && @event_listeners.has_key?(event_name)
16
+ @event_listeners[event_name].each { |f| f.call(*args) }
17
+ end
18
+ end
19
+ end
data/lib/baku/system.rb CHANGED
@@ -1,25 +1,30 @@
1
1
  module Baku
2
2
  class System
3
3
  attr_reader :components, :game_loop_step
4
- attr_accessor :world
4
+ attr_writer :entity_manager
5
5
 
6
6
  def initialize(components, game_loop_step)
7
7
  @components = components
8
8
  @game_loop_step = game_loop_step
9
9
  end
10
10
 
11
- def execute
12
- entities =
13
- @world.entity_manager.get_entities_for_system(self)
14
-
15
- entities.each do |entity|
11
+ def process_entities
12
+ if @entity_manager.nil?
13
+ raise StandardError.new("Must set :entity_manager property of System.")
14
+ end
15
+
16
+ @entity_manager.get_entities(component_mask).each do |entity|
16
17
  entity_components = @components.map { |c| entity.get_component(c) }
17
18
  process_entity(entity, *entity_components)
18
19
  end
19
20
  end
20
-
21
+
21
22
  def process_entity(entity)
22
23
  raise NotImplementedError
23
24
  end
25
+
26
+ def component_mask
27
+ @component_mask ||= ComponentMask.from_components(@components)
28
+ end
24
29
  end
25
30
  end
data/lib/baku/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Baku
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/baku/world.rb CHANGED
@@ -26,28 +26,34 @@ module Baku
26
26
  end
27
27
 
28
28
  system_list << system
29
- system.world = self
29
+
30
+ @entity_manager.register_component_mask(system.component_mask)
30
31
 
31
- @entity_manager.register_system(system)
32
+ system.entity_manager = @entity_manager
32
33
  end
33
34
 
34
35
  def create_entity(tags = [])
35
- entity = Entity.new(self, tags)
36
- @entity_manager.register_entity(entity)
36
+ entity = Entity.new(tags)
37
+ @entity_manager.add_entity(entity)
37
38
  entity
38
39
  end
39
40
 
40
- def get_entities_by_tag(tag)
41
- @entity_manager.get_entities_by_tag(tag)
41
+ def destroy_entity(entity)
42
+ @entity_manager.remove_entity(entity)
42
43
  end
43
44
 
44
45
  def update(delta_ms)
45
46
  @delta_ms = delta_ms
46
- @update_systems.each(&:execute)
47
+
48
+ @update_systems.each do |system|
49
+ system.process_entities
50
+ end
47
51
  end
48
52
 
49
53
  def draw
50
- @draw_systems.each(&:execute)
54
+ @draw_systems.each do |system|
55
+ system.process_entities
56
+ end
51
57
  end
52
58
  end
53
59
  end
data/lib/baku.rb CHANGED
@@ -1,8 +1,14 @@
1
1
  require "baku/version"
2
2
 
3
3
  module Baku
4
- require 'securerandom'
4
+ begin
5
+ require 'pry-byebug'
6
+ rescue LoadError
7
+ # development dependencies
8
+ end
9
+
5
10
  require 'set'
6
-
11
+
12
+ require_relative "baku/event_dispatcher.rb"
7
13
  Gem.find_files("baku/**/*.rb").each { |path| require path }
8
14
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: baku
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
  - John Tuttle
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-09-12 00:00:00.000000000 Z
11
+ date: 2017-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.5.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.5.0
55
69
  description: An Entity Component System framework for Ruby
56
70
  email:
57
71
  - jtuttle.develops@gmail.com
@@ -62,6 +76,7 @@ files:
62
76
  - ".gitignore"
63
77
  - ".rspec"
64
78
  - ".travis.yml"
79
+ - CHANGELOG.md
65
80
  - Gemfile
66
81
  - LICENSE.txt
67
82
  - README.md
@@ -71,8 +86,10 @@ files:
71
86
  - bin/setup
72
87
  - lib/baku.rb
73
88
  - lib/baku/component.rb
89
+ - lib/baku/component_mask.rb
74
90
  - lib/baku/entity.rb
75
91
  - lib/baku/entity_manager.rb
92
+ - lib/baku/event_dispatcher.rb
76
93
  - lib/baku/system.rb
77
94
  - lib/baku/version.rb
78
95
  - lib/baku/world.rb