File: test_RangeManager.h

package info (click to toggle)
0ad 0.27.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 173,296 kB
  • sloc: cpp: 194,003; javascript: 19,098; ansic: 15,066; python: 6,328; sh: 1,699; perl: 1,575; java: 533; xml: 482; php: 192; makefile: 99
file content (316 lines) | stat: -rw-r--r-- 14,842 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
/* Copyright (C) 2024 Wildfire Games.
 * This file is part of 0 A.D.
 *
 * 0 A.D. is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 0 A.D. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "maths/Matrix3D.h"
#include "simulation2/system/ComponentTest.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpObstruction.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpVision.h"

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>

#include <optional>

class MockVisionRgm : public ICmpVision
{
public:
	DEFAULT_MOCK_COMPONENT()

	entity_pos_t GetRange() const override { return entity_pos_t::FromInt(66); }
	bool GetRevealShore() const override { return false; }
};

class MockPositionRgm : public ICmpPosition
{
public:
	DEFAULT_MOCK_COMPONENT()

	void SetTurretParent(entity_id_t UNUSED(id), const CFixedVector3D& UNUSED(pos)) override {}
	entity_id_t GetTurretParent() const override {return INVALID_ENTITY;}
	void UpdateTurretPosition() override {}
	std::set<entity_id_t>* GetTurrets() override { return nullptr; }
	bool IsInWorld() const override { return true; }
	void MoveOutOfWorld() override { }
	void MoveTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) override { }
	void MoveAndTurnTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z), entity_angle_t UNUSED(a)) override { }
	void JumpTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) override { }
	void SetHeightOffset(entity_pos_t UNUSED(dy)) override { }
	entity_pos_t GetHeightOffset() const override { return entity_pos_t::Zero(); }
	void SetHeightFixed(entity_pos_t UNUSED(y)) override { }
	entity_pos_t GetHeightFixed() const override { return entity_pos_t::Zero(); }
	entity_pos_t GetHeightAtFixed(entity_pos_t, entity_pos_t) const override { return entity_pos_t::Zero(); }
	bool IsHeightRelative() const override { return true; }
	void SetHeightRelative(bool UNUSED(relative)) override { }
	bool CanFloat() const override { return false; }
	void SetFloating(bool UNUSED(flag)) override { }
	void SetActorFloating(bool UNUSED(flag)) override { }
	void SetConstructionProgress(fixed UNUSED(progress)) override { }
	CFixedVector3D GetPosition() const override { return m_Pos; }
	CFixedVector2D GetPosition2D() const override { return CFixedVector2D(m_Pos.X, m_Pos.Z); }
	CFixedVector3D GetPreviousPosition() const override { return CFixedVector3D(); }
	CFixedVector2D GetPreviousPosition2D() const override { return CFixedVector2D(); }
	fixed GetTurnRate() const override { return fixed::Zero(); }
	void TurnTo(entity_angle_t UNUSED(y)) override { }
	void SetYRotation(entity_angle_t UNUSED(y)) override { }
	void SetXZRotation(entity_angle_t UNUSED(x), entity_angle_t UNUSED(z)) override { }
	CFixedVector3D GetRotation() const override { return CFixedVector3D(); }
	fixed GetDistanceTravelled() const override { return fixed::Zero(); }
	void GetInterpolatedPosition2D(float UNUSED(frameOffset), float& x, float& z, float& rotY) const override { x = z = rotY = 0; }
	CMatrix3D GetInterpolatedTransform(float UNUSED(frameOffset)) const override { return CMatrix3D(); }

	CFixedVector3D m_Pos;
};

class MockObstructionRgm : public ICmpObstruction
{
public:
	DEFAULT_MOCK_COMPONENT();

	MockObstructionRgm(entity_pos_t s) : m_Size(s) {};

	ICmpObstructionManager::tag_t GetObstruction() const override { return {}; };
	bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare&) const override { return false; };
	bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare&) const override { return false; };
	entity_pos_t GetSize() const override { return m_Size; };
	CFixedVector2D GetStaticSize() const override { return {}; };
	EObstructionType GetObstructionType() const override { return {}; };
	void SetUnitClearance(const entity_pos_t&) override {};
	bool IsControlPersistent() const override { return {}; };
	bool CheckShorePlacement() const override { return {}; };
	EFoundationCheck CheckFoundation(const std::string&) const override { return {}; };
	EFoundationCheck CheckFoundation(const std::string& , bool) const override { return {}; };
	std::string CheckFoundation_wrapper(const std::string&, bool) const override { return {}; };
	bool CheckDuplicateFoundation() const override { return {}; };
	std::vector<entity_id_t> GetEntitiesByFlags(ICmpObstructionManager::flags_t) const override { return {}; };
	std::vector<entity_id_t> GetEntitiesBlockingMovement() const override { return {}; };
	std::vector<entity_id_t> GetEntitiesBlockingConstruction() const override { return {}; };
	std::vector<entity_id_t> GetEntitiesDeletedUponConstruction() const override { return {}; };
	void ResolveFoundationCollisions() const override {};
	void SetActive(bool) override {};
	void SetMovingFlag(bool) override {};
	void SetDisableBlockMovementPathfinding(bool, bool, int32_t) override {};
	bool GetBlockMovementFlag(bool) const override { return {}; };
	void SetControlGroup(entity_id_t) override {};
	entity_id_t GetControlGroup() const override { return {}; };
	void SetControlGroup2(entity_id_t) override {};
	entity_id_t GetControlGroup2() const override { return {}; };
private:
	entity_pos_t m_Size;
};

class TestCmpRangeManager : public CxxTest::TestSuite
{
	std::optional<CXeromycesEngine> xeromycesEngine;
public:
	void setUp()
	{
		xeromycesEngine.emplace();
	}

	void tearDown()
	{
		xeromycesEngine.reset();
	}

	// TODO It would be nice to call Verify() with the shore revealing system
	// but that means testing on an actual map, with water and land.

	void test_basic()
	{
		ComponentTestHelper test(*g_ScriptContext);

		ICmpRangeManager* cmp = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);

		MockVisionRgm vision;
		test.AddMock(100, IID_Vision, vision);

		MockPositionRgm position;
		test.AddMock(100, IID_Position, position);

		// This tests that the incremental computation produces the correct result
		// in various edge cases

		cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512));
		cmp->Verify();
		{ CMessageCreate msg(100); cmp->HandleMessage(msg, false); }
		cmp->Verify();
		{ CMessageOwnershipChanged msg(100, -1, 1); cmp->HandleMessage(msg, false); }
		cmp->Verify();
		{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(247), entity_pos_t::FromDouble(257.95), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
		cmp->Verify();
		{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(247), entity_pos_t::FromInt(253), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
		cmp->Verify();

		{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
		cmp->Verify();

		{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256)+entity_pos_t::Epsilon(), entity_pos_t::FromInt(256), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
		cmp->Verify();
		{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256)-entity_pos_t::Epsilon(), entity_pos_t::FromInt(256), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
		cmp->Verify();
		{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256)+entity_pos_t::Epsilon(), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
		cmp->Verify();
		{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256)-entity_pos_t::Epsilon(), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
		cmp->Verify();

		{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(383), entity_pos_t::FromInt(84), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
		cmp->Verify();
		{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(348), entity_pos_t::FromInt(83), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
		cmp->Verify();

		boost::mt19937 rng;
		for (size_t i = 0; i < 1024; ++i)
		{
			double x = boost::random::uniform_real_distribution<double>(0.0, 512.0)(rng);
			double z = boost::random::uniform_real_distribution<double>(0.0, 512.0)(rng);
			{ CMessagePositionChanged msg(100, true, entity_pos_t::FromDouble(x), entity_pos_t::FromDouble(z), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
			cmp->Verify();
		}

		// Test OwnershipChange, GetEntitiesByPlayer, GetNonGaiaEntities
		{
			player_id_t previousOwner = -1;
			for (player_id_t newOwner = 0; newOwner < 8; ++newOwner)
			{
				CMessageOwnershipChanged msg(100, previousOwner, newOwner);
				cmp->HandleMessage(msg, false);

				for (player_id_t i = 0; i < 8; ++i)
					TS_ASSERT_EQUALS(cmp->GetEntitiesByPlayer(i).size(), i == newOwner ? 1 : 0);

				TS_ASSERT_EQUALS(cmp->GetNonGaiaEntities().size(), newOwner > 0 ? 1 : 0);
				previousOwner = newOwner;
			}
		}
	}

	void test_queries()
	{
		ComponentTestHelper test(*g_ScriptContext);

		ICmpRangeManager* cmp = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);

		MockVisionRgm vision, vision2;
		MockPositionRgm position, position2;
		MockObstructionRgm obs(fixed::FromInt(2)), obs2(fixed::Zero());
		test.AddMock(100, IID_Vision, vision);
		test.AddMock(100, IID_Position, position);
		test.AddMock(100, IID_Obstruction, obs);

		test.AddMock(101, IID_Vision, vision2);
		test.AddMock(101, IID_Position, position2);
		test.AddMock(101, IID_Obstruction, obs2);

		cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512));
		cmp->Verify();
		{ CMessageCreate msg(100); cmp->HandleMessage(msg, false); }
		{ CMessageCreate msg(101); cmp->HandleMessage(msg, false); }

		{ CMessageOwnershipChanged msg(100, -1, 1); cmp->HandleMessage(msg, false); }
		{ CMessageOwnershipChanged msg(101, -1, 1); cmp->HandleMessage(msg, false); }

		auto move = [&cmp](entity_id_t ent, MockPositionRgm& pos, fixed x, fixed z) {
			pos.m_Pos = CFixedVector3D(x, fixed::Zero(), z);
			{ CMessagePositionChanged msg(ent, true, x, z, entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
		};

		move(100, position, fixed::FromInt(10), fixed::FromInt(10));
		move(101, position2, fixed::FromInt(10), fixed::FromInt(20));

		std::vector<entity_id_t> nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{});
		nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});

		move(101, position2, fixed::FromInt(10), fixed::FromInt(10));
		nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
		nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{});

		move(101, position2, fixed::FromInt(10), fixed::FromInt(13));
		nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
		nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{});

		move(101, position2, fixed::FromInt(10), fixed::FromInt(15));
		// In range thanks to self obstruction size.
		nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
		// In range thanks to target obstruction size.
		nearby = cmp->ExecuteQuery(101, fixed::FromInt(0), fixed::FromInt(4), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{100});

		// Trickier: min-range is closest-to-closest, but rotation may change the real distance.
		nearby = cmp->ExecuteQuery(100, fixed::FromInt(2), fixed::FromInt(50), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
		nearby = cmp->ExecuteQuery(100, fixed::FromInt(5), fixed::FromInt(50), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
		nearby = cmp->ExecuteQuery(100, fixed::FromInt(6), fixed::FromInt(50), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{});
		nearby = cmp->ExecuteQuery(101, fixed::FromInt(5), fixed::FromInt(50), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{100});
		nearby = cmp->ExecuteQuery(101, fixed::FromInt(6), fixed::FromInt(50), {1}, 0, true);
		TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{});

	}

	void test_IsInTargetParabolicRange()
	{
		ComponentTestHelper test(*g_ScriptContext);
		ICmpRangeManager* cmp = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);
		const entity_id_t source = 200;
		const entity_id_t target = 201;
		entity_pos_t range = fixed::FromInt(-3);
		entity_pos_t yOrigin = fixed::FromInt(-20);

		// Invalid range.
		TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), range);

		// No source ICmpPosition.
		range = fixed::FromInt(10);
		TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), NEVER_IN_RANGE);

		// No target ICmpPosition.
		MockPositionRgm cmpSourcePosition;
		test.AddMock(source, IID_Position, cmpSourcePosition);
		TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), NEVER_IN_RANGE);

		// Too much height difference.
		MockPositionRgm cmpTargetPosition;
		test.AddMock(target, IID_Position, cmpTargetPosition);
		TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), NEVER_IN_RANGE);

		// If no offset we get the range.
		range = fixed::FromInt(20);
		yOrigin = fixed::Zero();
		TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), range);
		TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, fixed::Zero(), yOrigin), fixed::Zero());

		// Normal case.
		yOrigin = fixed::FromInt(5);
		range = fixed::FromInt(10);
		TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), fixed::FromFloat(14.142136f));

		// Big range.
		range = fixed::FromInt(260);
		TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), fixed::FromFloat(264.952820f));
	}
};