chione 0.3.0 → 0.4.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 +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
|
|