darkholme 0.9.1 → 1.0.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
  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