chione 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +16 -2
- data/History.md +31 -0
- data/Manifest.txt +4 -0
- data/README.md +18 -11
- data/Rakefile +2 -0
- data/lib/chione.rb +7 -4
- data/lib/chione/archetype.rb +49 -3
- data/lib/chione/aspect.rb +52 -10
- data/lib/chione/component.rb +43 -8
- data/lib/chione/entity.rb +38 -28
- data/lib/chione/fixtures.rb +18 -0
- data/lib/chione/fixtures/entities.rb +32 -0
- data/lib/chione/iterating_system.rb +35 -0
- data/lib/chione/manager.rb +15 -0
- data/lib/chione/mixins.rb +22 -0
- data/lib/chione/system.rb +137 -12
- data/lib/chione/world.rb +155 -38
- data/spec/chione/archetype_spec.rb +14 -0
- data/spec/chione/aspect_spec.rb +90 -0
- data/spec/chione/component_spec.rb +37 -0
- data/spec/chione/entity_spec.rb +17 -24
- data/spec/chione/iterating_system_spec.rb +135 -0
- data/spec/chione/system_spec.rb +224 -27
- data/spec/chione/world_spec.rb +122 -59
- data/spec/spec_helper.rb +3 -0
- metadata +46 -14
- metadata.gz.sig +0 -0
@@ -3,6 +3,7 @@
|
|
3
3
|
require_relative '../spec_helper'
|
4
4
|
|
5
5
|
require 'chione/archetype'
|
6
|
+
require 'chione/aspect'
|
6
7
|
require 'chione/component'
|
7
8
|
require 'chione/entity'
|
8
9
|
require 'chione/world'
|
@@ -57,6 +58,19 @@ describe Chione::Archetype do
|
|
57
58
|
end
|
58
59
|
|
59
60
|
|
61
|
+
it "can be constructed from the Components specified in an Aspect" do
|
62
|
+
aspect = Chione::Aspect.with_all_of( location_component, tags_component )
|
63
|
+
archetype = described_class.from_aspect( aspect )
|
64
|
+
|
65
|
+
expect( archetype ).to be_a( Module )
|
66
|
+
expect( archetype.from_aspect ).to eq( aspect )
|
67
|
+
|
68
|
+
entity = archetype.construct_for( world )
|
69
|
+
|
70
|
+
expect( aspect ).to match( entity )
|
71
|
+
end
|
72
|
+
|
73
|
+
|
60
74
|
it "is still loadable as an `Assemblage`" do
|
61
75
|
expect {
|
62
76
|
expect( Chione::Assemblage ).to equal( described_class )
|
data/spec/chione/aspect_spec.rb
CHANGED
@@ -4,20 +4,28 @@ require_relative '../spec_helper'
|
|
4
4
|
|
5
5
|
require 'chione/aspect'
|
6
6
|
require 'chione/component'
|
7
|
+
require 'chione/fixtures'
|
7
8
|
|
8
9
|
|
9
10
|
describe Chione::Aspect do
|
10
11
|
|
12
|
+
before( :all ) do
|
13
|
+
Chione::Fixtures.load( :entities )
|
14
|
+
end
|
15
|
+
|
16
|
+
|
11
17
|
let( :location ) do
|
12
18
|
Class.new( Chione::Component ) do
|
13
19
|
field :x, default: 0
|
14
20
|
field :y, default: 0
|
21
|
+
def self::name; "Location"; end
|
15
22
|
end
|
16
23
|
end
|
17
24
|
|
18
25
|
let( :tags ) do
|
19
26
|
Class.new( Chione::Component ) do
|
20
27
|
field :tags, default: []
|
28
|
+
def self::name; "Tags"; end
|
21
29
|
end
|
22
30
|
end
|
23
31
|
|
@@ -27,9 +35,29 @@ describe Chione::Aspect do
|
|
27
35
|
field :shade, default: 0
|
28
36
|
field :value, default: 0
|
29
37
|
field :opacity, default: 0
|
38
|
+
def self::name; "Color"; end
|
30
39
|
end
|
31
40
|
end
|
32
41
|
|
42
|
+
let( :world ) { Chione::World.new }
|
43
|
+
|
44
|
+
let( :entity ) { Chione::Fixtures.entity(world) }
|
45
|
+
let( :entity1 ) do
|
46
|
+
entity.with_components( location ).instance
|
47
|
+
end
|
48
|
+
let( :entity2 ) do
|
49
|
+
entity.with_components( location, color ).instance
|
50
|
+
end
|
51
|
+
let( :entity3 ) do
|
52
|
+
entity.with_components( location, color, tags ).instance
|
53
|
+
end
|
54
|
+
let( :entity4 ) do
|
55
|
+
entity.with_components( color, tags ).instance
|
56
|
+
end
|
57
|
+
let( :entity5 ) do
|
58
|
+
entity.with_components( tags ).instance
|
59
|
+
end
|
60
|
+
|
33
61
|
|
34
62
|
it "doesn't have any default criteria" do
|
35
63
|
aspect = described_class.new
|
@@ -139,5 +167,67 @@ describe Chione::Aspect do
|
|
139
167
|
expect( aspect.none_of ).to include( tags, location )
|
140
168
|
end
|
141
169
|
|
170
|
+
|
171
|
+
it "can be inverted"
|
172
|
+
|
173
|
+
|
174
|
+
describe "entity-matching" do
|
175
|
+
|
176
|
+
let( :all_entities ) {[ entity1, entity2, entity3, entity4, entity5 ]}
|
177
|
+
|
178
|
+
|
179
|
+
it "can find the matching subset of values given a Hash keyed by Components" do
|
180
|
+
entities = all_entities()
|
181
|
+
|
182
|
+
aspect = described_class.with_all_of( color, location ).and_none_of( tags )
|
183
|
+
result = aspect.matching_entities( world.entities_by_component )
|
184
|
+
|
185
|
+
expect( result ).to contain_exactly( entity2.id )
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
it "always matches an individual entity if it's empty" do
|
190
|
+
aspect = described_class.new
|
191
|
+
|
192
|
+
expect( aspect ).to match( entity1 )
|
193
|
+
expect( aspect ).to match( entity2 )
|
194
|
+
expect( aspect ).to match( entity3 )
|
195
|
+
expect( aspect ).to match( entity4 )
|
196
|
+
expect( aspect ).to match( entity5 )
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
it "matches an individual entity if its components meet the criteria" do
|
201
|
+
aspect = described_class.with_all_of( color, location ).and_none_of( tags )
|
202
|
+
|
203
|
+
expect( aspect ).to_not match( entity1 )
|
204
|
+
expect( aspect ).to match( entity2 )
|
205
|
+
expect( aspect ).to_not match( entity3 )
|
206
|
+
expect( aspect ).to_not match( entity4 )
|
207
|
+
expect( aspect ).to_not match( entity5 )
|
208
|
+
|
209
|
+
aspect = described_class.with_one_of( tags, color )
|
210
|
+
|
211
|
+
expect( aspect ).to_not match( entity1 )
|
212
|
+
expect( aspect ).to match( entity2 )
|
213
|
+
expect( aspect ).to match( entity3 )
|
214
|
+
expect( aspect ).to match( entity4 )
|
215
|
+
expect( aspect ).to match( entity5 )
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
it "matches a component hash if it meets the criteria" do
|
220
|
+
aspect = described_class.with_all_of( color, location ).and_none_of( tags )
|
221
|
+
|
222
|
+
expect( aspect ).to_not match( entity1.components )
|
223
|
+
expect( aspect ).to match( entity2.components )
|
224
|
+
expect( aspect ).to_not match( entity3.components )
|
225
|
+
expect( aspect ).to_not match( entity4.components )
|
226
|
+
expect( aspect ).to_not match( entity5.components )
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
|
142
232
|
end
|
143
233
|
|
@@ -12,6 +12,7 @@ describe Chione::Component do
|
|
12
12
|
let( :component_subclass ) do
|
13
13
|
Class.new( described_class )
|
14
14
|
end
|
15
|
+
let( :entity_id ) { '73BA6CB5-50CB-4EA4-9941-5EBF17D5D379' }
|
15
16
|
|
16
17
|
|
17
18
|
it "can declare fields" do
|
@@ -27,6 +28,19 @@ describe Chione::Component do
|
|
27
28
|
end
|
28
29
|
|
29
30
|
|
31
|
+
it "includes a description of its fields in its inspection output" do
|
32
|
+
component_subclass.field( :name )
|
33
|
+
component_subclass.field( :x )
|
34
|
+
component_subclass.field( :y )
|
35
|
+
|
36
|
+
instance = component_subclass.new
|
37
|
+
|
38
|
+
expect( instance.inspect ).to match( /\bname:/ )
|
39
|
+
expect( instance.inspect ).to match( /\bx:/ )
|
40
|
+
expect( instance.inspect ).to match( /\by:/ )
|
41
|
+
end
|
42
|
+
|
43
|
+
|
30
44
|
it "can declare fields with default values" do
|
31
45
|
component_subclass.field( :x, default: 0 )
|
32
46
|
component_subclass.field( :y, default: 18 )
|
@@ -37,6 +51,17 @@ describe Chione::Component do
|
|
37
51
|
end
|
38
52
|
|
39
53
|
|
54
|
+
it "can declare fields with a block for validation or pre-processing" do
|
55
|
+
component_subclass.field( :coordinates, default: [0, 0] ) do |vals|
|
56
|
+
{ x: Integer(vals[0]), y: Integer(vals[1]) }
|
57
|
+
end
|
58
|
+
|
59
|
+
instance = component_subclass.new( coordinates: [88, 19] )
|
60
|
+
expect( instance.coordinates[:x] ).to eq( 88 )
|
61
|
+
expect( instance.coordinates[:y] ).to eq( 19 )
|
62
|
+
end
|
63
|
+
|
64
|
+
|
40
65
|
it "uses a dup of the default if it's not an immediate object" do
|
41
66
|
component_subclass.field( :things, default: [] )
|
42
67
|
|
@@ -59,6 +84,18 @@ describe Chione::Component do
|
|
59
84
|
expect( instance2.oid ).to eq( 121212 )
|
60
85
|
end
|
61
86
|
|
87
|
+
|
88
|
+
it "can be created with an entity ID" do
|
89
|
+
component_subclass.field( :x )
|
90
|
+
component_subclass.field( :y )
|
91
|
+
|
92
|
+
instance = component_subclass.new( entity_id, x: 1, y: 18 )
|
93
|
+
|
94
|
+
expect( instance.entity_id ).to eq( entity_id )
|
95
|
+
expect( instance.x ).to eq( 1 )
|
96
|
+
expect( instance.y ).to eq( 18 )
|
97
|
+
end
|
98
|
+
|
62
99
|
end
|
63
100
|
|
64
101
|
end
|
data/spec/chione/entity_spec.rb
CHANGED
@@ -98,51 +98,44 @@ describe Chione::Entity do
|
|
98
98
|
end
|
99
99
|
|
100
100
|
|
101
|
-
it "
|
102
|
-
entity.add_component( location_component )
|
103
|
-
entity.add_component( tags_component )
|
101
|
+
it "can have components with init arguments added to it" do
|
102
|
+
entity.add_component( location_component, x: 18, y: 51 )
|
103
|
+
entity.add_component( tags_component, tags: ['trace', 'volatile'] )
|
104
104
|
|
105
|
-
expect(
|
106
|
-
|
107
|
-
).to
|
105
|
+
expect( entity.get_component(location_component).x ).to eq( 18 )
|
106
|
+
expect( entity.get_component(location_component).y ).to eq( 51 )
|
107
|
+
expect( entity.get_component(tags_component).tags ).to contain_exactly('trace', 'volatile')
|
108
108
|
end
|
109
109
|
|
110
110
|
|
111
|
-
it "
|
111
|
+
it "lets components be fetched for it" do
|
112
112
|
entity.add_component( location_component )
|
113
113
|
entity.add_component( tags_component )
|
114
114
|
|
115
115
|
expect(
|
116
116
|
entity.get_component( location_component )
|
117
|
-
).to
|
117
|
+
).to be_an_instance_of( location_component )
|
118
118
|
end
|
119
119
|
|
120
120
|
|
121
|
-
it "
|
121
|
+
it "can have components removed from it" do
|
122
122
|
entity.add_component( location_component )
|
123
123
|
entity.add_component( tags_component )
|
124
124
|
|
125
|
-
|
126
|
-
entity.find_component( bounding_box_component, location_component )
|
127
|
-
).to eq( entity.components[location_component] )
|
128
|
-
end
|
129
|
-
|
125
|
+
entity.remove_component( tags_component )
|
130
126
|
|
131
|
-
|
132
|
-
entity.
|
133
|
-
|
134
|
-
expect {
|
135
|
-
entity.find_component( location_component )
|
136
|
-
}.to raise_error( KeyError, /#{entity.id} doesn't have/i )
|
127
|
+
expect( entity ).to have_component( location_component )
|
128
|
+
expect( entity ).to_not have_component( tags_component )
|
137
129
|
end
|
138
130
|
|
139
131
|
|
140
|
-
it "
|
132
|
+
it "returns nil when fetching a component the entity doesn't have" do
|
133
|
+
entity.add_component( location_component )
|
141
134
|
entity.add_component( tags_component )
|
142
135
|
|
143
|
-
expect
|
144
|
-
entity.
|
145
|
-
|
136
|
+
expect(
|
137
|
+
entity.get_component( bounding_box_component )
|
138
|
+
).to be_nil
|
146
139
|
end
|
147
140
|
|
148
141
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'chione/iterating_system'
|
6
|
+
require 'chione/fixtures'
|
7
|
+
|
8
|
+
|
9
|
+
describe Chione::IteratingSystem do
|
10
|
+
|
11
|
+
before( :all ) do
|
12
|
+
Chione::Fixtures.load( :entities )
|
13
|
+
@component_classes = Chione::Component.derivatives.dup
|
14
|
+
end
|
15
|
+
before( :each ) do
|
16
|
+
Chione::Component.derivatives.clear
|
17
|
+
end
|
18
|
+
after( :all ) do
|
19
|
+
Chione::Component.derivatives.replace( @component_classes )
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
let( :subclass ) { Class.new(described_class) }
|
24
|
+
let( :world ) { Chione::World.new }
|
25
|
+
let( :location_component ) do
|
26
|
+
klass = Class.new( Chione::Component ) do
|
27
|
+
def self::name; "Location"; end
|
28
|
+
field :x, default: 0
|
29
|
+
field :y, default: 0
|
30
|
+
end
|
31
|
+
Chione::Component.derivatives['location'] = klass
|
32
|
+
klass
|
33
|
+
end
|
34
|
+
|
35
|
+
let( :tags_component ) do
|
36
|
+
klass = Class.new( Chione::Component ) do
|
37
|
+
def self::name; "Tags"; end
|
38
|
+
field :tags, default: []
|
39
|
+
end
|
40
|
+
Chione::Component.derivatives['tags'] = klass
|
41
|
+
klass
|
42
|
+
end
|
43
|
+
|
44
|
+
let( :bounding_box_component ) do
|
45
|
+
klass = Class.new( Chione::Component ) do
|
46
|
+
def self::name; "BoundingBox"; end
|
47
|
+
field :width, default: 1
|
48
|
+
field :height, default: 1
|
49
|
+
field :depth, default: 1
|
50
|
+
end
|
51
|
+
Chione::Component.derivatives['bounding_box'] = klass
|
52
|
+
klass
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
describe "instances" do
|
57
|
+
|
58
|
+
let( :instance ) { subclass.new(world) }
|
59
|
+
|
60
|
+
|
61
|
+
it "requires an override for the #process method" do
|
62
|
+
expect( instance ).to respond_to( :process )
|
63
|
+
expect {
|
64
|
+
instance.process( :default, 'entity-id', {} )
|
65
|
+
}.to raise_error( NotImplementedError, /#process/i )
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
describe "with a process method" do
|
70
|
+
|
71
|
+
let( :entity ) { Chione::Fixtures.entity(world) }
|
72
|
+
|
73
|
+
|
74
|
+
let( :subclass ) do
|
75
|
+
cls = super()
|
76
|
+
cls.class_exec(location_component, bounding_box_component) do |location, bbox|
|
77
|
+
aspect :default, all_of: [location, bbox]
|
78
|
+
aspect :emphemeral, all_of: [location], none_of: bbox
|
79
|
+
|
80
|
+
def initialize( * )
|
81
|
+
super
|
82
|
+
@calls = []
|
83
|
+
end
|
84
|
+
|
85
|
+
attr_reader :calls
|
86
|
+
|
87
|
+
def process( aspect_name, entity_id, components )
|
88
|
+
@calls << [ aspect_name, entity_id, components ]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
cls
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
it "gets called for each entity which matches an aspect on each tick" do
|
97
|
+
entities_with_location = entity.with_components( location_component ).
|
98
|
+
generator.take( 5 ) # :ephemeral
|
99
|
+
entities_with_bb = entity.with_components( bounding_box_component ).
|
100
|
+
generator.take( 5 ) # -
|
101
|
+
entities_with_both = entity.
|
102
|
+
with_components( location_component, bounding_box_component ).
|
103
|
+
generator.take( 5 ) # :default
|
104
|
+
|
105
|
+
sys = world.add_system( subclass )
|
106
|
+
sys.start
|
107
|
+
|
108
|
+
world.publish( 'timing', 0.5, 1 )
|
109
|
+
world.publish_deferred_events
|
110
|
+
|
111
|
+
both_components = a_hash_including( location_component, bounding_box_component )
|
112
|
+
only_location_component = a_hash_including( location_component )
|
113
|
+
|
114
|
+
expect( sys.calls.length ).to eq( 10 )
|
115
|
+
expect( sys.calls ).to contain_exactly(
|
116
|
+
[:default, entities_with_both[0].id, both_components ],
|
117
|
+
[:default, entities_with_both[1].id, both_components ],
|
118
|
+
[:default, entities_with_both[2].id, both_components ],
|
119
|
+
[:default, entities_with_both[3].id, both_components ],
|
120
|
+
[:default, entities_with_both[4].id, both_components ],
|
121
|
+
[:emphemeral, entities_with_location[0].id, only_location_component ],
|
122
|
+
[:emphemeral, entities_with_location[1].id, only_location_component ],
|
123
|
+
[:emphemeral, entities_with_location[2].id, only_location_component ],
|
124
|
+
[:emphemeral, entities_with_location[3].id, only_location_component ],
|
125
|
+
[:emphemeral, entities_with_location[4].id, only_location_component ]
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
end
|
135
|
+
|
data/spec/chione/system_spec.rb
CHANGED
@@ -3,10 +3,16 @@
|
|
3
3
|
require_relative '../spec_helper'
|
4
4
|
|
5
5
|
require 'chione/system'
|
6
|
+
require 'chione/fixtures'
|
6
7
|
|
7
8
|
|
8
9
|
describe Chione::System do
|
9
10
|
|
11
|
+
before( :all ) do
|
12
|
+
Chione::Fixtures.load( :entities )
|
13
|
+
end
|
14
|
+
|
15
|
+
|
10
16
|
let( :location_component ) do
|
11
17
|
Class.new( Chione::Component ) do
|
12
18
|
field :x, default: 0
|
@@ -30,40 +36,218 @@ describe Chione::System do
|
|
30
36
|
describe "subclass" do
|
31
37
|
|
32
38
|
let( :subclass ) do
|
33
|
-
Class.new(described_class)
|
39
|
+
Class.new( described_class ) do
|
40
|
+
def initialize( * )
|
41
|
+
super
|
42
|
+
@calls = []
|
43
|
+
end
|
44
|
+
attr_reader :calls
|
45
|
+
|
46
|
+
def inserted( *args )
|
47
|
+
self.calls << [ __method__, args ]
|
48
|
+
super
|
49
|
+
end
|
50
|
+
def removed( *args )
|
51
|
+
self.calls << [ __method__, args ]
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
34
55
|
end
|
35
56
|
let( :world ) { Chione::World.new }
|
36
57
|
|
37
58
|
|
38
|
-
|
39
|
-
|
40
|
-
|
59
|
+
describe "aspects" do
|
60
|
+
|
61
|
+
it "has a default Aspect which matches all entities" do
|
62
|
+
expect( subclass.aspects ).to be_empty
|
63
|
+
expect( subclass.aspects[:default] ).to be_empty
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
it "can declare components for its default aspect" do
|
68
|
+
subclass.aspect :default,
|
69
|
+
all_of: volition_component,
|
70
|
+
one_of: [ tags_component, location_component ]
|
71
|
+
|
72
|
+
expect( subclass.aspects.keys ).to contain_exactly( :default )
|
73
|
+
expect( subclass.aspects[:default].all_of ).to include( volition_component )
|
74
|
+
expect( subclass.aspects[:default].one_of ).
|
75
|
+
to include( tags_component, location_component )
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
it "can declare a named aspect" do
|
80
|
+
subclass.aspect( :with_location, all_of: location_component )
|
81
|
+
|
82
|
+
expect( subclass.aspects.keys ).to contain_exactly( :with_location )
|
83
|
+
expect( subclass.aspects[:with_location].all_of ).
|
84
|
+
to contain_exactly( location_component )
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
it "can declare a named aspect using simplified syntax" do
|
89
|
+
subclass.aspect( :with_location, location_component )
|
90
|
+
|
91
|
+
expect( subclass.aspects.keys ).to contain_exactly( :with_location )
|
92
|
+
expect( subclass.aspects[:with_location].all_of ).
|
93
|
+
to contain_exactly( location_component )
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
it "can declare more than one named aspect" do
|
98
|
+
subclass.aspect( :with_location, all_of: location_component )
|
99
|
+
subclass.aspect( :self_movable, all_of: [location_component, volition_component] )
|
100
|
+
|
101
|
+
expect( subclass.aspects.keys ).to contain_exactly( :with_location, :self_movable )
|
102
|
+
expect( subclass.aspects[:with_location].all_of ).to contain_exactly( location_component )
|
103
|
+
expect( subclass.aspects[:self_movable].all_of ).
|
104
|
+
to contain_exactly( location_component, volition_component )
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
it "keeps an explicitly-defined default aspect event if other named ones are added" do
|
109
|
+
subclass.aspect( :default, all_of: location_component )
|
110
|
+
subclass.aspect( :self_movable, all_of: [location_component, volition_component] )
|
111
|
+
|
112
|
+
expect( subclass.aspects.keys ).to contain_exactly( :default, :self_movable )
|
113
|
+
expect( subclass.aspects[:default].all_of ).to contain_exactly( location_component )
|
114
|
+
expect( subclass.aspects[:self_movable].all_of ).
|
115
|
+
to contain_exactly( location_component, volition_component )
|
116
|
+
end
|
117
|
+
|
41
118
|
|
119
|
+
it "is notified when adding a component causes an entity to start matching one of its aspects" do
|
120
|
+
subclass.aspect( :with_location, all_of: location_component )
|
121
|
+
subclass.aspect( :self_movable, all_of: [location_component, volition_component] )
|
122
|
+
|
123
|
+
entity = world.create_entity
|
124
|
+
|
125
|
+
sys = world.add_system( subclass )
|
126
|
+
sys.start
|
127
|
+
|
128
|
+
world.add_component_to( entity, location_component )
|
129
|
+
world.publish_deferred_events
|
130
|
+
|
131
|
+
expect( sys.calls.length ).to eq( 1 )
|
132
|
+
expect( sys.calls ).to include([
|
133
|
+
:inserted, [
|
134
|
+
:with_location,
|
135
|
+
entity.id,
|
136
|
+
{location_component => an_instance_of(location_component)}
|
137
|
+
]
|
138
|
+
])
|
139
|
+
|
140
|
+
world.add_component_to( entity, volition_component )
|
141
|
+
world.publish_deferred_events
|
142
|
+
|
143
|
+
expect( sys.calls.length ).to eq( 2 )
|
144
|
+
expect( sys.calls ).to include([
|
145
|
+
:inserted, [
|
146
|
+
:self_movable,
|
147
|
+
entity.id,
|
148
|
+
{
|
149
|
+
location_component => an_instance_of(location_component),
|
150
|
+
volition_component => an_instance_of(volition_component)
|
151
|
+
}
|
152
|
+
]
|
153
|
+
])
|
154
|
+
end
|
42
155
|
|
43
|
-
it "can declare components for its aspect" do
|
44
|
-
subclass.aspect all_of: volition_component,
|
45
|
-
one_of: [ tags_component, location_component ]
|
46
156
|
|
47
|
-
|
48
|
-
|
49
|
-
|
157
|
+
it "is notified when removing a component causes an entity to no longer match one of its aspects" do
|
158
|
+
subclass.aspect( :with_location, all_of: location_component )
|
159
|
+
subclass.aspect( :self_movable, all_of: [location_component, volition_component] )
|
160
|
+
|
161
|
+
entity = world.create_entity
|
162
|
+
world.add_component_to( entity, location_component )
|
163
|
+
world.add_component_to( entity, volition_component )
|
164
|
+
world.publish_deferred_events
|
165
|
+
|
166
|
+
sys = world.add_system( subclass )
|
167
|
+
sys.start
|
168
|
+
|
169
|
+
world.remove_component_from( entity, volition_component )
|
170
|
+
world.publish_deferred_events
|
171
|
+
|
172
|
+
expect( sys.calls.length ).to eq( 1 )
|
173
|
+
expect( sys.calls ).to include([
|
174
|
+
:removed, [
|
175
|
+
:self_movable,
|
176
|
+
entity.id,
|
177
|
+
{location_component => an_instance_of(location_component)}
|
178
|
+
]
|
179
|
+
])
|
180
|
+
|
181
|
+
world.remove_component_from( entity, location_component )
|
182
|
+
world.publish_deferred_events
|
183
|
+
|
184
|
+
expect( sys.calls.length ).to eq( 2 )
|
185
|
+
expect( sys.calls ).to include([
|
186
|
+
:removed, [
|
187
|
+
:with_location,
|
188
|
+
entity.id,
|
189
|
+
{}
|
190
|
+
]
|
191
|
+
])
|
192
|
+
end
|
193
|
+
|
50
194
|
end
|
51
195
|
|
52
196
|
|
53
|
-
|
54
|
-
|
197
|
+
describe "event handlers" do
|
198
|
+
|
199
|
+
it "has no event handlers by default" do
|
200
|
+
expect( subclass.event_handlers ).to be_empty
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
it "can register a handler method for an event" do
|
205
|
+
subclass.on( 'entity/created' ) do |*|
|
206
|
+
# no-op
|
207
|
+
end
|
208
|
+
|
209
|
+
expect( subclass.event_handlers ).
|
210
|
+
to include( ['entity/created', 'on_entity_created_event'] )
|
211
|
+
expect( subclass.instance_methods ).to include( :on_entity_created_event )
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
it "provides a convenience declaration for the timing event" do
|
216
|
+
subclass.every_tick do |*|
|
217
|
+
# no-op
|
218
|
+
end
|
219
|
+
|
220
|
+
expect( subclass.event_handlers ).
|
221
|
+
to include( ['timing', 'on_timing_event'] )
|
222
|
+
expect( subclass.instance_methods ).to include( :on_timing_event )
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
it "unwraps arguments to `every_tick` callbacks" do
|
227
|
+
received_delta = received_tick = nil
|
228
|
+
subclass.every_tick do |delta, tick|
|
229
|
+
received_delta = delta
|
230
|
+
received_tick = tick
|
231
|
+
end
|
232
|
+
|
233
|
+
instance = subclass.new( world )
|
234
|
+
instance.on_timing_event( 'timing', [0.016666666666666666, 0] )
|
235
|
+
|
236
|
+
expect( received_delta ).to be_within( 0.00001 ).of( 0.016666666666666666 )
|
237
|
+
expect( received_tick ).to eq( 0 )
|
238
|
+
end
|
55
239
|
|
56
|
-
expect( subclass.aspect ).to_not be_empty
|
57
|
-
expect( subclass.aspect.all_of ).to include( volition_component )
|
58
240
|
end
|
59
241
|
|
60
242
|
|
61
243
|
describe "instance" do
|
62
244
|
|
63
245
|
let( :subclass ) do
|
64
|
-
subclass =
|
65
|
-
subclass.aspect
|
66
|
-
|
246
|
+
subclass = Class.new( described_class )
|
247
|
+
subclass.aspect( :default,
|
248
|
+
all_of: volition_component,
|
249
|
+
one_of: [tags_component, location_component] )
|
250
|
+
subclass.aspect( :tagged, all_of: tags_component )
|
67
251
|
subclass
|
68
252
|
end
|
69
253
|
|
@@ -72,20 +256,33 @@ describe Chione::System do
|
|
72
256
|
end
|
73
257
|
|
74
258
|
|
75
|
-
|
76
|
-
ent1 = world.create_entity
|
77
|
-
ent1.add_component( volition_component )
|
78
|
-
ent1.add_component( tags_component )
|
259
|
+
before( :each ) do
|
260
|
+
@ent1 = world.create_entity
|
261
|
+
@ent1.add_component( volition_component )
|
262
|
+
@ent1.add_component( tags_component )
|
79
263
|
|
80
|
-
ent2 = world.create_entity
|
81
|
-
ent2.add_component( volition_component )
|
82
|
-
ent2.add_component( location_component )
|
264
|
+
@ent2 = world.create_entity
|
265
|
+
@ent2.add_component( volition_component )
|
266
|
+
@ent2.add_component( location_component )
|
83
267
|
|
84
|
-
ent3 = world.create_entity
|
85
|
-
ent3.add_component( volition_component )
|
268
|
+
@ent3 = world.create_entity
|
269
|
+
@ent3.add_component( volition_component )
|
86
270
|
|
271
|
+
@ent4 = world.create_entity
|
272
|
+
@ent4.add_component( location_component )
|
273
|
+
@ent4.add_component( tags_component )
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
it "can enumerate the entities from the world that match its default aspect" do
|
87
278
|
expect( instance.entities ).to be_a( Enumerator )
|
88
|
-
expect( instance.entities ).to contain_exactly( ent1, ent2 )
|
279
|
+
expect( instance.entities ).to contain_exactly( @ent1.id, @ent2.id )
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
it "can enumerate the entities from the world that match one of its named aspects" do
|
284
|
+
expect( instance.entities(:tagged) ).to be_a( Enumerator )
|
285
|
+
expect( instance.entities(:tagged) ).to contain_exactly( @ent1.id, @ent4.id )
|
89
286
|
end
|
90
287
|
|
91
288
|
|