darkholme 0.9.1 → 1.0.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: c96502aea889b8b96233989a44c91611918115f4
4
- data.tar.gz: 4bd198e683c64ad3b28a05536aa07034bbdb91eb
3
+ metadata.gz: e7887db34f894712a0b13a7b0efc0371af0983a4
4
+ data.tar.gz: 44a523114c61788c2c11d450df41e90179f3867d
5
5
  SHA512:
6
- metadata.gz: 490d144381019b4ca83651917fdc71d74410fe2238c252079eb42e3b673b6f8e2a7eeea3eb58b371c540ac522b03f6b648beb5b411c4b25f8416f13daca6b9f6
7
- data.tar.gz: e232f4dbde1ac5588e6e0e6ab9e8a3a8ec5d2fd9d34ac9db3eb5bf519a17c2c282df2ebee6ade2dec8dbd9fc5b85fd200fbd763bd00535e628235d67d181dc59
6
+ metadata.gz: ecdfaa52cc78ef4e681a84b4dc8a734a9a61c239171e75931fa7a5b756fa1a30626de6dc7274c94faba5215ad6836952ab2f9e87d05685ac01858b283b12ac3d
7
+ data.tar.gz: 4e83e7a54bdf7eeec2c35cc8d4c28d35087a26c6124899b509351c8db1bdb404f62a93954c11ea6537ec151b1fe977b16b163d8289525ab34fc45b7cc023b68e
data/README.md CHANGED
@@ -2,14 +2,86 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/massivedanger/darkholme.png?branch=master)](https://travis-ci.org/massivedanger/darkholme)
4
4
 
5
- > Who am I? That, my dear, is an excellent question. Though not one easily answered.
5
+ > Who am I? That, my dear, is an excellent question. Though not one easily answered.
6
6
 
7
- Darkholme is an [entity-component system](http://en.wikipedia.org/wiki/Entity_component_system)
7
+ Darkholme is an [entity-component system](http://en.wikipedia.org/wiki/Entity_component_system)
8
8
  written in Ruby. It's still early days for it, but I think it's ready for most basic use cases.
9
9
 
10
10
  ## Usage
11
11
 
12
- _COMING SOON!_
12
+ First, you need to create an **Engine** to hold all the other parts of Darkholme.
13
+
14
+ ```ruby
15
+ @engine = Darkholme::Engine.new
16
+ ```
17
+
18
+ All of your **Systems** and **Entities** will be added to your engine, which will update
19
+ them once per frame. Make sure you put an Engine#update inside of your game's update loop, like
20
+ so:
21
+
22
+ ```ruby
23
+ def update(delta)
24
+ @engine.update(delta)
25
+ end
26
+ ```
27
+
28
+ Now, you can define an Entity and add **Components** to it, which hold data for your systems
29
+ to use. In this case, let's assume you've made a component called `Spatial` that holds an entity's
30
+ position along with its velocity. Something like this:
31
+
32
+ ```ruby
33
+ class Spatial < Darkholme::Component
34
+ attr_accessor :position, :velocity
35
+
36
+ def initialize
37
+ @position = { x: 0, y: 0 }
38
+ @velocity = { x: 0, y: 0 }
39
+ end
40
+ end
41
+ ```
42
+
43
+ And adding it:
44
+
45
+ ```ruby
46
+ new_entity = Darkhole::Entity.new
47
+ new_entity.add_component Spatial.new
48
+ ```
49
+
50
+ Next is adding your entity to the engine and adding a new system that'll use the Spatial
51
+ component and move the entity according to its velocity.
52
+
53
+ Here's what the system could look like:
54
+
55
+ ```ruby
56
+ class PositionSystem < Darkholme::System
57
+ has_family Spatial
58
+
59
+ def update(delta)
60
+ entities.each do |entity|
61
+ spatial = entity.component_for Spatial
62
+
63
+ spatial.position.x += spatial.velocity.x * delta
64
+ spatial.position.y += spatial.velocity.y * delta
65
+ end
66
+ end
67
+ end
68
+ ```
69
+
70
+ And adding it and the entity from before:
71
+
72
+ ```ruby
73
+ engine.add_system PositionSystem.new
74
+ engine.add_entity new_entity
75
+ ```
76
+
77
+ Now, as another system modifies the entity's Spatial component velocity, the
78
+ PositionSystem will position it according to its velocity, but properly using
79
+ the delta between frames for smooth movement.
80
+
81
+ It's highly encouraged to [read through the documentation](http://rdoc.info/github/massivedanger/darkholme/master/frames)
82
+ for the gem, as it can answer most questions you might have about each individual method. Please
83
+ [file an issue](https://github.com/massivedanger/darkholme/issues) if you think our documentation is
84
+ lacking in some way. Thanks!
13
85
 
14
86
  ## Key concepts
15
87
 
@@ -40,7 +112,7 @@ means that systems won't loop through every single entity on update. They only l
40
112
  relevant entities. This is _awesome_. Maybe.
41
113
 
42
114
  ## Contributing to darkholme
43
-
115
+
44
116
  - Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
45
117
  - Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
46
118
  - Fork the project.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.1
1
+ 1.0.0
data/darkholme.gemspec CHANGED
@@ -2,15 +2,15 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: darkholme 0.9.1 ruby lib
5
+ # stub: darkholme 1.0.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "darkholme"
9
- s.version = "0.9.1"
9
+ s.version = "1.0.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.authors = ["Massive Danger"]
13
- s.date = "2014-02-23"
13
+ s.date = "2014-02-25"
14
14
  s.description = "An entity-component system in Ruby"
15
15
  s.email = "evan@massivedanger.com"
16
16
  s.extra_rdoc_files = [
data/lib/darkholme.rb CHANGED
@@ -6,6 +6,6 @@ require 'darkholme/iterating_system'
6
6
  require 'darkholme/component'
7
7
  require 'darkholme/family'
8
8
 
9
+ # Darkholme is an Entity-Component System by Massive Danger
9
10
  module Darkholme
10
-
11
11
  end
@@ -1,7 +1,17 @@
1
1
  module Darkholme
2
+ # A class for easier bitset management with handy bitwise methods
2
3
  class Bitset
3
- attr_reader :bits, :set_indexes
4
-
4
+ attr_reader :bits
5
+ attr_reader :set_indexes
6
+
7
+ # Create a new Bitset with optional initial bits
8
+ #
9
+ # @param initial_bits [Array<Fixnum>] The initial bit indexes to set
10
+ #
11
+ # @example Setting multiple bits initially
12
+ # Bitset.new 1, 2, 10, 30
13
+ #
14
+ # @return [Bitset] The new bitset
5
15
  def initialize(*initial_bits)
6
16
  @bits = initial_bits.map {|bit| convert_bit(bit) }.inject(:+) || 0
7
17
 
@@ -9,6 +19,13 @@ module Darkholme
9
19
  @set_indexes += initial_bits.uniq if initial_bits
10
20
  end
11
21
 
22
+ # Set the bit at an index to true or false.
23
+ # Sets the bit to 1, by default
24
+ #
25
+ # @param bit [Fixnum] Index of bitset to set
26
+ # @param value [Boolean] Whether to set the bit to 0 or 1
27
+ #
28
+ # @return [Fixnum] The bitset's integer representation after being modified
12
29
  def set(bit, value = true)
13
30
  if value
14
31
  @bits = union convert_bit(bit)
@@ -21,67 +38,117 @@ module Darkholme
21
38
  @bits
22
39
  end
23
40
 
41
+ # Set a bit at supplied index to 0
42
+ #
43
+ # @param bit [Fixnum] The index of the bit to modify
44
+ #
45
+ # @return [Fixnum] The bitset's integer representation after being modified
24
46
  def clear(bit)
25
- set(bit, false)
47
+ set(bit, false)
26
48
  end
27
49
 
50
+ # Checks if a bit at an index is set to 1
51
+ #
52
+ # @param bit [Fixnum] Index of bit to check
53
+ #
54
+ # @return [Boolean] Whether or not the bit is set to one
28
55
  def set?(bit)
29
- bit = convert_bit(bit)
56
+ bit = convert_bit(bit)
30
57
  intersect(bit) == bit
31
58
  end
32
59
 
60
+ # Checks if all of another bitset's flipped bits are flipped in this one
61
+ #
62
+ # @param other [Bitset] Bitset use for inclusion check
63
+ #
64
+ # @return [Boolean] Whether or not all bits are flipped in both
33
65
  def include?(other)
34
66
  set_indexes & other.set_indexes == other.set_indexes
35
67
  end
36
68
 
69
+ # Perform a bitwise AND
70
+ #
71
+ # @param other [Bitset or Fixnum] Other object to use for AND
72
+ #
73
+ # @return [Fixnum] Bits after operation
37
74
  def intersect(other)
38
- @bits & bits_from_object(other)
75
+ @bits & bits_from_object(other)
39
76
  end
40
77
  alias_method :&, :intersect
41
78
 
42
- def intersect?(other)
43
- if other_bits = bits_from_object(other) > 0
44
- intersect(other_bits) == other_bits
45
- else
46
- false
47
- end
48
- end
49
-
79
+ # Perform a bitwise NOT on the current bitset
80
+ #
81
+ # @return [Fixnum] Bits after operation
50
82
  def not
51
83
  ~@bits
52
84
  end
53
85
  alias_method :~, :not
54
86
 
87
+ # Perform a bitwise XOR
88
+ #
89
+ # @param other [Bitset or Fixnum] Other object to use for XOR
90
+ #
91
+ # @return [Fixnum] Bits after operation
55
92
  def xor(other)
56
- @bits ^ bits_from_object(other)
93
+ @bits ^ bits_from_object(other)
57
94
  end
58
95
  alias_method :^, :xor
59
96
 
97
+ # Perform a bitwise OR
98
+ #
99
+ # @param other [Bitset or Fixnum] Other object to use for OR
100
+ #
101
+ # @return [Fixnum] Bits after operation
60
102
  def union(other)
61
103
  @bits | bits_from_object(other)
62
104
  end
63
105
  alias_method :|, :union
64
106
 
107
+ # Return the binary representation of the bitset
108
+ #
109
+ # @return [String] Binary of current bits
65
110
  def to_s
66
- @bits.to_s(2)
111
+ @bits.to_s(2)
67
112
  end
68
113
 
114
+ # Return Integer representation of the bitset
115
+ #
116
+ # @return [Fixnum] Integer of current bits
69
117
  def to_i
70
118
  @bits.to_i
71
119
  end
72
120
 
121
+ # Return Float representation of the bitset
122
+ #
123
+ # @return [Float] Float of current bits
73
124
  def to_f
74
125
  @bits.to_f
75
126
  end
76
127
 
128
+ # Check for a bit at an index to be set to 1
129
+ #
130
+ # @param index [Fixnum] Index of bit to check
131
+ #
132
+ # @return [Boolean] Whether or not the bit is set to 1
77
133
  def [](index)
78
134
  set?(index)
79
135
  end
80
136
 
137
+ # Set a bit at an index to 0 or 1
138
+ #
139
+ # @param index [Fixnum] Index of bitset to set
140
+ # @param value [Boolean] Whether to set the bit to 0 or 1
141
+ #
142
+ # @return [Fixnum] The bitset's integer representation after being modified
81
143
  def []=(index, value)
82
144
  set(index, value)
83
145
  end
84
146
 
147
+ # Check if other bitset's bits match the current ones
148
+ #
149
+ # @param other [Bitset] Other bitset
150
+ #
151
+ # @return [Boolean] Whether or not the bits match
85
152
  def ==(other)
86
153
  @bits == other.bits
87
154
  end
@@ -1,14 +1,23 @@
1
1
  module Darkholme
2
+ # Top-level class for storing data on an Entity that a System uses
2
3
  class Component
3
4
  @next_bit = 0
4
5
  @bits = {}
5
6
 
7
+ # Get a unique bit for a Component class
8
+ #
9
+ # @param klass [Class] The class to generate (or look up) the bit for
10
+ #
11
+ # @return [Fixnum] The bit for that class
6
12
  def self.bit_for(klass)
7
13
  @bits[klass] ||= @next_bit += 1
8
14
  end
9
15
 
16
+ # Get the current instance's class' bit
17
+ #
18
+ # @return [Fixnum] The bit for the instance's class
10
19
  def bit
11
- Component.bit_for(self.class)
20
+ Component.bit_for(self.class)
12
21
  end
13
22
  end
14
23
  end
@@ -1,39 +1,87 @@
1
1
  module Darkholme
2
+ # An Engine contains all of a game's entities, components, and systems.
3
+ # Usually only one is needed for your entire game, although no limitation
4
+ # is hardcoded. Use at your discretion.
2
5
  class Engine
3
- attr_reader :systems, :entities
6
+ attr_reader :systems, :entities, :families
4
7
 
8
+ # Create a new engine with empty systems and entities
9
+ #
10
+ # @return [Engine] The new Engine
5
11
  def initialize
6
12
  @systems = {}
7
13
  @families = {}
8
14
  @entities = Set.new
9
15
  end
10
16
 
17
+ # Add an entity to the engine (with callbacks). Once added,
18
+ # the entity is available for use by systems during each
19
+ # update loop.
20
+ #
21
+ # @param entity [Entity] The entity to add
22
+ #
23
+ # @return Result of entity#added_to_engine
11
24
  def add_entity(entity)
12
25
  @entities << entity
13
26
  entity.added_to_engine self
14
27
  end
15
28
 
29
+ # Remove an entity from the engine (with callbacks). Once removed,
30
+ # the entity will no longer be updated during the update loop
31
+ #
32
+ # @param entity [Entity] The entity to remove
33
+ #
34
+ # @return Result of entity#removed_from_engine
16
35
  def remove_entity(entity)
17
36
  @entities.delete entity
18
37
  entity.removed_from_engine self
19
38
  end
20
39
 
40
+ # Add a system to be updated each frame
41
+ #
42
+ # @param system [System] The system to add
43
+ #
44
+ # @return Result of system#added_to_engine
21
45
  def add_system(system)
22
46
  @systems[system.class] = system
23
47
  system.added_to_engine self
24
48
  end
25
49
 
26
- def remove_system(systemClass)
27
- system = @systems.delete systemClass
50
+ # Remove a system from the engine. It will no longer be updated
51
+ # each frame
52
+ #
53
+ # @param system_class [Class] Class of System to remove
54
+ #
55
+ # @return Result of system#removed_from_engine
56
+ def remove_system(system_class)
57
+ system = @systems.delete system_class
28
58
  system.removed_from_engine(self) if system
29
59
  end
30
60
 
31
- def system(systemClass)
32
- @systems[systemClass]
61
+ # Retrieve a system added to the engine by its class
62
+ #
63
+ # @param system_class [Class] Class of the system to get
64
+ #
65
+ # @example Standard usage
66
+ # engine.system_for(MySystem)
67
+ #
68
+ # @return [System] A System instance, if found
69
+ def system_for(system_class)
70
+ @systems[system_class]
33
71
  end
34
72
 
73
+ # Retrieve all entities matching Family provided
74
+ #
75
+ # @param family [Family] Family to use when checking entities
76
+ #
77
+ # @example Standard usage
78
+ # engine.entities_for_family(
79
+ # Family.for(MyComponent, AnotherComponent)
80
+ # )
81
+ #
82
+ # @return [Array<Entity>] All entities matching provided Family
35
83
  def entities_for_family(family)
36
- @families[family.index] ||= begin
84
+ @families[family] ||= begin
37
85
  Set.new.tap do |entities|
38
86
  @entities.each do |entity|
39
87
  entities << entity if family.member?(entity)
@@ -42,9 +90,41 @@ module Darkholme
42
90
  end
43
91
  end
44
92
 
93
+ # Update all systems within engine. This should called once per
94
+ # frame in game.
95
+ #
96
+ # @param delta [Float] Time between last frame and this one
97
+ #
98
+ # @example Standard usage
99
+ # engine.update(0.0008)
45
100
  def update(delta)
46
101
  @systems.each {|klass, system| system.update(delta) }
47
102
  end
48
103
 
104
+ # A callback called every time an entity added to the engine
105
+ # has a component added. Updates all associated families
106
+ def component_added(entity, component)
107
+ @families.each do |family, entities|
108
+ unless entity.family_bits.set?(family.index)
109
+ if family.member?(entity)
110
+ entities << entity
111
+ entity.family_bits.set(family.index)
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ # A callback called every time an entity added to the engine
118
+ # has a component removed. Updates all associated families
119
+ def component_removed(entity, component)
120
+ @families.each do |family, entities|
121
+ if entity.family_bits.set?(family.index)
122
+ unless family.member?(entity)
123
+ entities.delete(entity)
124
+ entity.family_bits.clear(family.index)
125
+ end
126
+ end
127
+ end
128
+ end
49
129
  end
50
130
  end
@@ -1,41 +1,75 @@
1
1
  module Darkholme
2
+ # An Entity contains Components, which hold data which is manipulated
3
+ # by a System
2
4
  class Entity
3
- attr_accessor :engine, :components, :component_bits
5
+ attr_accessor :engine, :components, :component_bits, :family_bits
4
6
 
7
+ # Create a new Entity
8
+ #
9
+ # @return [Entity] The new Entity
5
10
  def initialize
6
11
  self.components = {}
7
12
  self.component_bits = Bitset.new
13
+ self.family_bits = Bitset.new
8
14
  self.engine = nil
9
15
  end
10
16
 
17
+ # Callback called after the entity has been added to an Engine
18
+ #
19
+ # @param engine [Engine] The engine the system was added to
11
20
  def added_to_engine(engine)
12
21
  self.engine = engine
13
22
  end
14
23
 
24
+ # Callback called after the engine has been removed from an Engine
25
+ #
26
+ # @param engine [Engine] The engine the entity was removed from
15
27
  def removed_from_engine(engine)
16
28
  self.engine = nil
17
29
  end
18
30
 
31
+ # Add a component to an entity
32
+ #
33
+ # @param component [Component] The component being added
34
+ #
35
+ # @return [Component] The component that was added
19
36
  def add_component(component)
20
37
  self.components[component.class] = component
21
38
  self.component_bits.set(component.bit)
39
+ self.engine.component_added(self, component) if self.engine
22
40
 
23
- component
41
+ component
24
42
  end
25
43
 
44
+ # Remove a component from an entity
45
+ #
46
+ # @param component_class [Class] The class of the component being removed
47
+ #
48
+ # @return [Component] The component that was removed
26
49
  def remove_component(component_class)
27
50
  if removed_component = component_for(component_class)
28
51
  self.component_bits.clear(removed_component.bit)
29
52
  self.components.delete(component_class)
53
+ self.engine.component_removed(self, removed_component) if self.engine
30
54
  end
31
55
 
32
56
  removed_component
33
57
  end
34
58
 
59
+ # Check to see if an entity contains a type of component
60
+ #
61
+ # @param component_class [Class] The class of the component to check for
62
+ #
63
+ # @return [Boolean] Whether or not the entity has the component
35
64
  def has_component?(component_class)
36
65
  self.components.include? component_class
37
66
  end
38
67
 
68
+ # Retrieve a component of a certain class from an entity
69
+ #
70
+ # @param component_class [Class] The class of the component to get
71
+ #
72
+ # @return [Component] The component, if present
39
73
  def component_for(component_class)
40
74
  self.components[component_class]
41
75
  end
@@ -1,5 +1,6 @@
1
1
  module Darkholme
2
- class Family
2
+ # Used by systems and engines to get related entities for processing
3
+ class Family
3
4
  @next_index = 0
4
5
  @families = {}
5
6
 
@@ -9,6 +10,13 @@ module Darkholme
9
10
 
10
11
  attr_reader :index, :bits
11
12
 
13
+ # Get a family for matching entities with a specific
14
+ # list of components present
15
+ #
16
+ # @param component_classes [Array<Class>] List of Component classes to
17
+ # check for
18
+ #
19
+ # @return [Family] The Family instance for that list of Components
12
20
  def self.for(*component_classes)
13
21
  bits = Bitset.new
14
22
  component_classes.each do |klass|
@@ -21,6 +29,11 @@ module Darkholme
21
29
  end
22
30
  end
23
31
 
32
+ # Check if an Entity has all the required Components for the Family
33
+ #
34
+ # @param entity [Entity] The entity to check for membership
35
+ #
36
+ # @return [Boolean] Whether or not the entity is a member of the Family
24
37
  def member?(entity)
25
38
  entity.component_bits.include?(bits)
26
39
  end
@@ -1,5 +1,15 @@
1
1
  module Darkholme
2
+ # A subclass of System, IteratingSystem automatically loops through entities
3
+ # belonging to it and calls #process on them, along with a before and after
4
+ # callback
2
5
  class IteratingSystem < System
6
+ # Called by the engine, this calls #before_processing, then #process on
7
+ # each entity, then #after_processing
8
+ #
9
+ # @param delta [Float] The difference in time between the last frame
10
+ # and this one
11
+ #
12
+ # @return The result of after_processing
3
13
  def update(delta)
4
14
  before_processing
5
15
  entities.each do |entity|
@@ -8,13 +18,22 @@ module Darkholme
8
18
  after_processing
9
19
  end
10
20
 
21
+ # Called on each entity in the collection. This where the various
22
+ # components' data will be manipulated. This must be overridden by
23
+ # the subclass.
24
+ #
25
+ # @param entity [Entity] The entity being manipulated
26
+ # @param delta [Float] The difference in time between the last frame
27
+ # and this one
11
28
  def process(entity, delta)
12
29
  raise NotImplementedError.new("You must override #process(entity, delta)")
13
30
  end
14
31
 
32
+ # Called once a frame before the entities are looped over and processed
15
33
  def before_processing
16
34
  end
17
35
 
36
+ # Called once a frame after the entities are looped over and processed
18
37
  def after_processing
19
38
  end
20
39
  end
@@ -1,31 +1,61 @@
1
1
  module Darkholme
2
+ # Updated every frame by the engine, a System manipulates and uses the
3
+ # data contained within a component
2
4
  class System
3
5
  class << self
4
6
  attr_reader :family
5
7
 
8
+ # Set the family for a System. Call this from the body of the
9
+ # class.
10
+ #
11
+ # @param component_classes [Array<Class>] List of component classes the system is
12
+ #
13
+ # @example Standard usage
14
+ # class MySystem < Darkholme::System
15
+ # has_family MyComponent, AnotherComponent, LastComponent
16
+ # end
17
+ #
18
+ # @return [Family] The family for the classes
6
19
  def has_family(*component_classes)
7
- @family = Family.for(*component_classes)
20
+ @family = Family.for(*component_classes)
8
21
  end
9
22
  end
10
23
 
24
+ # @!attribute engine [Engine] The engine this system belongs to
11
25
  attr_accessor :engine
12
26
 
27
+ # Called once per frame by the engine. This must be overriden
28
+ # by the subclass.
29
+ #
30
+ # @param delta [Float] Difference in time between the last frame and this one
13
31
  def update(delta)
14
32
  raise NotImplementedError.new("You must override #update(delta)")
15
33
  end
16
34
 
35
+ # Callback called after the system has been added to an Engine
36
+ #
37
+ # @param engine [Engine] The engine the system was added to
17
38
  def added_to_engine(engine)
18
39
  self.engine = engine
19
40
  end
20
41
 
42
+ # Callback called after the system has been removed from an Engine
43
+ #
44
+ # @param engine [Engine] The engine the system was removed from
21
45
  def removed_from_engine(engine)
22
46
  self.engine = nil if self.engine == engine
23
47
  end
24
48
 
49
+ # An Array of all the entities this system is interested in
50
+ #
51
+ # @return [Array<Entity>] All the entities with matching components
25
52
  def entities
26
53
  engine.entities_for_family(family)
27
54
  end
28
55
 
56
+ # Get the system's Family instance
57
+ #
58
+ # @return [Family] The system's family, as defined by #has_family
29
59
  def family
30
60
  self.class.family
31
61
  end
@@ -4,23 +4,23 @@ module Darkholme
4
4
  describe Engine do
5
5
  subject { Engine.new }
6
6
  let(:entity) { MockEntity.new }
7
- let(:system) { MockSystem.new }
7
+ let(:system) { MockSystem.new }
8
8
 
9
9
  describe "with entities" do
10
10
  it "can add them" do
11
11
  expect {
12
12
  subject.add_entity(entity)
13
- }.to change { subject.entities.count }.from(0).to(1)
13
+ }.to change { subject.entities.count }.from(0).to(1)
14
14
  end
15
15
 
16
16
  it "can remove them" do
17
17
  subject.add_entity(entity)
18
18
 
19
- expect {
19
+ expect {
20
20
  subject.remove_entity(entity)
21
21
  }.to change { subject.entities.count }.from(1).to(0)
22
22
  end
23
-
23
+
24
24
  it "can find them by family" do
25
25
  entity.add_component(MockComponent.new)
26
26
  subject.add_entity(entity)
@@ -53,6 +53,28 @@ module Darkholme
53
53
  subject.update(:delta)
54
54
  end
55
55
  end
56
+
57
+ describe "with callbacks" do
58
+ let(:family) { Family.for(MockComponent) }
59
+ it "updates families upon component addition" do
60
+ subject.families[family] = []
61
+ entity.engine = subject
62
+
63
+ expect(subject.families[family]).not_to include(entity)
64
+ entity.add_component MockComponent.new
65
+ expect(subject.families[family]).to include(entity)
66
+ end
67
+
68
+ it "updates families upon component removal" do
69
+ subject.families[family] = [entity]
70
+ entity.engine = subject
71
+ entity.add_component MockComponent.new
72
+
73
+ expect(subject.families[family]).to include(entity)
74
+ entity.remove_component MockComponent
75
+ expect(subject.families[family]).not_to include(entity)
76
+ end
77
+ end
56
78
  end
57
79
  end
58
80
 
@@ -41,8 +41,17 @@ module Darkholme
41
41
 
42
42
  subject.add_component(component)
43
43
  end
44
+
45
+ it "notifies the engine" do
46
+ subject.engine = Engine.new
47
+ expect(subject.engine).to receive(:component_added).with(
48
+ subject, component
49
+ )
50
+
51
+ subject.add_component(component)
52
+ end
44
53
  end
45
-
54
+
46
55
  describe "removing one" do
47
56
  let!(:added) { subject.add_component(component) }
48
57
 
@@ -62,8 +71,17 @@ module Darkholme
62
71
 
63
72
  subject.remove_component(component.class)
64
73
  end
74
+
75
+ it "notifies the engine" do
76
+ subject.engine = Engine.new
77
+ expect(subject.engine).to receive(:component_removed).with(
78
+ subject, component
79
+ )
80
+
81
+ subject.remove_component(component.class)
82
+ end
65
83
  end
66
-
84
+
67
85
  it "returns a component instance when asked for one" do
68
86
  subject.add_component(component)
69
87
  expect(subject.component_for(component.class)).to be_a MockComponent
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: darkholme
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Massive Danger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-23 00:00:00.000000000 Z
11
+ date: 2014-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry