Index: ps/trunk/source/scriptinterface/ScriptRuntime.h
===================================================================
--- ps/trunk/source/scriptinterface/ScriptRuntime.h (revision 24180)
+++ ps/trunk/source/scriptinterface/ScriptRuntime.h (nonexistent)
@@ -1,102 +0,0 @@
-/* Copyright (C) 2020 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 .
- */
-
-#ifndef INCLUDED_SCRIPTRUNTIME
-#define INCLUDED_SCRIPTRUNTIME
-
-#include "ScriptTypes.h"
-#include "ScriptExtraHeaders.h"
-
-#include
-
-constexpr int STACK_CHUNK_SIZE = 8192;
-
-// Those are minimal defaults. The runtime for the main game is larger and GCs upon a larger growth.
-constexpr int DEFAULT_RUNTIME_SIZE = 16 * 1024 * 1024;
-constexpr int DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER = 2 * 1024 * 1024;
-
-/**
- * Abstraction around a SpiderMonkey JSRuntime/JSContext.
- *
- * A single ScriptRuntime, with the associated runtime and context,
- * should only be used on a single thread.
- *
- * (One means to share data between threads and runtimes is to create
- * a ScriptInterface::StructuredClone.)
- */
-
-class ScriptRuntime
-{
-public:
- ScriptRuntime(int runtimeSize, int heapGrowthBytesGCTrigger);
- ~ScriptRuntime();
-
- /**
- * Returns a runtime/context, in which any number of ScriptInterfaces compartments can live.
- * Each runtime should only ever be used on a single thread.
- * @param runtimeSize Maximum size in bytes of the new runtime
- * @param heapGrowthBytesGCTrigger Size in bytes of cumulated allocations after which a GC will be triggered
- */
- static shared_ptr CreateRuntime(
- int runtimeSize = DEFAULT_RUNTIME_SIZE,
- int heapGrowthBytesGCTrigger = DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER);
-
- /**
- * MaybeIncrementalRuntimeGC tries to determine whether a runtime-wide garbage collection would free up enough memory to
- * be worth the amount of time it would take. It does this with our own logic and NOT some predefined JSAPI logic because
- * such functionality currently isn't available out of the box.
- * It does incremental GC which means it will collect one slice each time it's called until the garbage collection is done.
- * This can and should be called quite regularly. The delay parameter allows you to specify a minimum time since the last GC
- * in seconds (the delay should be a fraction of a second in most cases though).
- * It will only start a new incremental GC or another GC slice if this time is exceeded. The user of this function is
- * responsible for ensuring that GC can run with a small enough delay to get done with the work.
- */
- void MaybeIncrementalGC(double delay);
- void ShrinkingGC();
-
- /**
- * This is used to keep track of compartments which should be prepared for a GC.
- */
- void RegisterCompartment(JSCompartment* cmpt);
- void UnRegisterCompartment(JSCompartment* cmpt);
-
- JSRuntime* GetJSRuntime() const { return m_rt; }
-
- /**
- * GetGeneralJSContext returns the context without starting a GC request and without
- * entering any compartment. It should only be used in specific situations, such as
- * creating a new compartment, or as an unsafe alternative to GetJSRuntime.
- * If you need the compartmented context of a ScriptInterface, you should create a
- * ScriptInterface::Request and use the context from that.
- */
- JSContext* GetGeneralJSContext() const { return m_cx; }
-
-private:
-
- JSRuntime* m_rt;
- JSContext* m_cx;
-
- void PrepareCompartmentsForIncrementalGC() const;
- std::list m_Compartments;
-
- int m_RuntimeSize;
- int m_HeapGrowthBytesGCTrigger;
- int m_LastGCBytes;
- double m_LastGCCheck;
-};
-
-#endif // INCLUDED_SCRIPTRUNTIME
Property changes on: ps/trunk/source/scriptinterface/ScriptRuntime.h
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/scriptinterface/ScriptRuntime.cpp
===================================================================
--- ps/trunk/source/scriptinterface/ScriptRuntime.cpp (revision 24180)
+++ ps/trunk/source/scriptinterface/ScriptRuntime.cpp (nonexistent)
@@ -1,324 +0,0 @@
-/* Copyright (C) 2020 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 .
- */
-
-#include "precompiled.h"
-
-#include "ScriptRuntime.h"
-
-#include "ps/GameSetup/Config.h"
-#include "ps/Profile.h"
-#include "scriptinterface/ScriptEngine.h"
-#include "scriptinterface/ScriptInterface.h"
-
-
-void GCSliceCallbackHook(JSRuntime* UNUSED(rt), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc))
-{
- /*
- * During non-incremental GC, the GC is bracketed by JSGC_CYCLE_BEGIN/END
- * callbacks. During an incremental GC, the sequence of callbacks is as
- * follows:
- * JSGC_CYCLE_BEGIN, JSGC_SLICE_END (first slice)
- * JSGC_SLICE_BEGIN, JSGC_SLICE_END (second slice)
- * ...
- * JSGC_SLICE_BEGIN, JSGC_CYCLE_END (last slice)
- */
-
-
- if (progress == JS::GC_SLICE_BEGIN)
- {
- if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread())
- g_Profiler.Start("GCSlice");
- g_Profiler2.RecordRegionEnter("GCSlice");
- }
- else if (progress == JS::GC_SLICE_END)
- {
- if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread())
- g_Profiler.Stop();
- g_Profiler2.RecordRegionLeave();
- }
- else if (progress == JS::GC_CYCLE_BEGIN)
- {
- if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread())
- g_Profiler.Start("GCSlice");
- g_Profiler2.RecordRegionEnter("GCSlice");
- }
- else if (progress == JS::GC_CYCLE_END)
- {
- if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread())
- g_Profiler.Stop();
- g_Profiler2.RecordRegionLeave();
- }
-
- // The following code can be used to print some information aobut garbage collection
- // Search for "Nonincremental reason" if there are problems running GC incrementally.
- #if 0
- if (progress == JS::GCProgress::GC_CYCLE_BEGIN)
- printf("starting cycle ===========================================\n");
-
- const char16_t* str = desc.formatMessage(rt);
- int len = 0;
-
- for(int i = 0; i < 10000; i++)
- {
- len++;
- if(!str[i])
- break;
- }
-
- wchar_t outstring[len];
-
- for(int i = 0; i < len; i++)
- {
- outstring[i] = (wchar_t)str[i];
- }
-
- printf("---------------------------------------\n: %ls \n---------------------------------------\n", outstring);
- #endif
-}
-
-
-namespace {
-
-void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)
-{
- ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface);
-
- std::stringstream msg;
- bool isWarning = JSREPORT_IS_WARNING(report->flags);
- msg << (isWarning ? "JavaScript warning: " : "JavaScript error: ");
- if (report->filename)
- {
- msg << report->filename;
- msg << " line " << report->lineno << "\n";
- }
-
- msg << message;
-
- // If there is an exception, then print its stack trace
- JS::RootedValue excn(rq.cx);
- if (JS_GetPendingException(rq.cx, &excn) && excn.isObject())
- {
- JS::RootedValue stackVal(rq.cx);
- JS::RootedObject excnObj(rq.cx, &excn.toObject());
- JS_GetProperty(rq.cx, excnObj, "stack", &stackVal);
-
- std::string stackText;
- ScriptInterface::FromJSVal(rq, stackVal, stackText);
-
- std::istringstream stream(stackText);
- for (std::string line; std::getline(stream, line);)
- msg << "\n " << line;
- }
-
- if (isWarning)
- LOGWARNING("%s", msg.str().c_str());
- else
- LOGERROR("%s", msg.str().c_str());
-
- // When running under Valgrind, print more information in the error message
- // VALGRIND_PRINTF_BACKTRACE("->");
-}
-
-} // anonymous namespace
-
-
-shared_ptr ScriptRuntime::CreateRuntime(int runtimeSize, int heapGrowthBytesGCTrigger)
-{
- return shared_ptr(new ScriptRuntime(runtimeSize, heapGrowthBytesGCTrigger));
-}
-
-ScriptRuntime::ScriptRuntime(int runtimeSize, int heapGrowthBytesGCTrigger):
- m_LastGCBytes(0),
- m_LastGCCheck(0.0f),
- m_HeapGrowthBytesGCTrigger(heapGrowthBytesGCTrigger),
- m_RuntimeSize(runtimeSize)
-{
- ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be initialized before constructing any ScriptRuntimes!");
-
- m_rt = JS_NewRuntime(runtimeSize, JS::DefaultNurseryBytes, nullptr);
- ENSURE(m_rt); // TODO: error handling
-
- JS::SetGCSliceCallback(m_rt, GCSliceCallbackHook);
-
- JS_SetGCParameter(m_rt, JSGC_MAX_MALLOC_BYTES, m_RuntimeSize);
- JS_SetGCParameter(m_rt, JSGC_MAX_BYTES, m_RuntimeSize);
- JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_INCREMENTAL);
-
- // The whole heap-growth mechanism seems to work only for non-incremental GCs.
- // We disable it to make it more clear if full GCs happen triggered by this JSAPI internal mechanism.
- JS_SetGCParameter(m_rt, JSGC_DYNAMIC_HEAP_GROWTH, false);
-
- ScriptEngine::GetSingleton().RegisterRuntime(m_rt);
-
-
- m_cx = JS_NewContext(m_rt, STACK_CHUNK_SIZE);
- ENSURE(m_cx); // TODO: error handling
-
- JS_SetOffthreadIonCompilationEnabled(m_rt, true);
-
- // For GC debugging:
- // JS_SetGCZeal(m_cx, 2, JS_DEFAULT_ZEAL_FREQ);
-
- JS_SetContextPrivate(m_cx, nullptr);
-
- JS_SetErrorReporter(m_rt, ErrorReporter);
-
- JS_SetGlobalJitCompilerOption(m_rt, JSJITCOMPILER_ION_ENABLE, 1);
- JS_SetGlobalJitCompilerOption(m_rt, JSJITCOMPILER_BASELINE_ENABLE, 1);
-
- JS::RuntimeOptionsRef(m_cx)
- .setExtraWarnings(true)
- .setWerror(false)
- .setStrictMode(true);
-}
-
-ScriptRuntime::~ScriptRuntime()
-{
- JS_DestroyContext(m_cx);
- JS_DestroyRuntime(m_rt);
-
- ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be active (initialized and not yet shut down) when destroying a ScriptRuntime!");
- ScriptEngine::GetSingleton().UnRegisterRuntime(m_rt);
-}
-
-void ScriptRuntime::RegisterCompartment(JSCompartment* cmpt)
-{
- ENSURE(cmpt);
- m_Compartments.push_back(cmpt);
-}
-
-void ScriptRuntime::UnRegisterCompartment(JSCompartment* cmpt)
-{
- m_Compartments.remove(cmpt);
-}
-
-#define GC_DEBUG_PRINT 0
-void ScriptRuntime::MaybeIncrementalGC(double delay)
-{
- PROFILE2("MaybeIncrementalGC");
-
- if (JS::IsIncrementalGCEnabled(m_rt))
- {
- // The idea is to get the heap size after a completed GC and trigger the next GC when the heap size has
- // reached m_LastGCBytes + X.
- // In practice it doesn't quite work like that. When the incremental marking is completed, the sweeping kicks in.
- // The sweeping actually frees memory and it does this in a background thread (if JS_USE_HELPER_THREADS is set).
- // While the sweeping is happening we already run scripts again and produce new garbage.
-
- const int GCSliceTimeBudget = 30; // Milliseconds an incremental slice is allowed to run
-
- // Have a minimum time in seconds to wait between GC slices and before starting a new GC to distribute the GC
- // load and to hopefully make it unnoticeable for the player. This value should be high enough to distribute
- // the load well enough and low enough to make sure we don't run out of memory before we can start with the
- // sweeping.
- if (timer_Time() - m_LastGCCheck < delay)
- return;
-
- m_LastGCCheck = timer_Time();
-
- int gcBytes = JS_GetGCParameter(m_rt, JSGC_BYTES);
-
-#if GC_DEBUG_PRINT
- std::cout << "gcBytes: " << gcBytes / 1024 << " KB" << std::endl;
-#endif
-
- if (m_LastGCBytes > gcBytes || m_LastGCBytes == 0)
- {
-#if GC_DEBUG_PRINT
- printf("Setting m_LastGCBytes: %d KB \n", gcBytes / 1024);
-#endif
- m_LastGCBytes = gcBytes;
- }
-
- // Run an additional incremental GC slice if the currently running incremental GC isn't over yet
- // ... or
- // start a new incremental GC if the JS heap size has grown enough for a GC to make sense
- if (JS::IsIncrementalGCInProgress(m_rt) || (gcBytes - m_LastGCBytes > m_HeapGrowthBytesGCTrigger))
- {
-#if GC_DEBUG_PRINT
- if (JS::IsIncrementalGCInProgress(m_rt))
- printf("An incremental GC cycle is in progress. \n");
- else
- printf("GC needed because JSGC_BYTES - m_LastGCBytes > m_HeapGrowthBytesGCTrigger \n"
- " JSGC_BYTES: %d KB \n m_LastGCBytes: %d KB \n m_HeapGrowthBytesGCTrigger: %d KB \n",
- gcBytes / 1024,
- m_LastGCBytes / 1024,
- m_HeapGrowthBytesGCTrigger / 1024);
-#endif
-
- // A hack to make sure we never exceed the runtime size because we can't collect the memory
- // fast enough.
- if (gcBytes > m_RuntimeSize / 2)
- {
- if (JS::IsIncrementalGCInProgress(m_rt))
- {
-#if GC_DEBUG_PRINT
- printf("Finishing incremental GC because gcBytes > m_RuntimeSize / 2. \n");
-#endif
- PrepareCompartmentsForIncrementalGC();
- JS::FinishIncrementalGC(m_rt, JS::gcreason::REFRESH_FRAME);
- }
- else
- {
- if (gcBytes > m_RuntimeSize * 0.75)
- {
- ShrinkingGC();
-#if GC_DEBUG_PRINT
- printf("Running shrinking GC because gcBytes > m_RuntimeSize * 0.75. \n");
-#endif
- }
- else
- {
-#if GC_DEBUG_PRINT
- printf("Running full GC because gcBytes > m_RuntimeSize / 2. \n");
-#endif
- JS_GC(m_rt);
- }
- }
- }
- else
- {
-#if GC_DEBUG_PRINT
- if (!JS::IsIncrementalGCInProgress(m_rt))
- printf("Starting incremental GC \n");
- else
- printf("Running incremental GC slice \n");
-#endif
- PrepareCompartmentsForIncrementalGC();
- if (!JS::IsIncrementalGCInProgress(m_rt))
- JS::StartIncrementalGC(m_rt, GC_NORMAL, JS::gcreason::REFRESH_FRAME, GCSliceTimeBudget);
- else
- JS::IncrementalGCSlice(m_rt, JS::gcreason::REFRESH_FRAME, GCSliceTimeBudget);
- }
- m_LastGCBytes = gcBytes;
- }
- }
-}
-
-void ScriptRuntime::ShrinkingGC()
-{
- JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_COMPARTMENT);
- JS::PrepareForFullGC(m_rt);
- JS::GCForReason(m_rt, GC_SHRINK, JS::gcreason::REFRESH_FRAME);
- JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_INCREMENTAL);
-}
-
-void ScriptRuntime::PrepareCompartmentsForIncrementalGC() const
-{
- for (JSCompartment* const& cmpt : m_Compartments)
- JS::PrepareZoneForGC(js::GetCompartmentZone(cmpt));
-}
Property changes on: ps/trunk/source/scriptinterface/ScriptRuntime.cpp
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h
===================================================================
--- ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h (revision 24180)
+++ ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h (revision 24181)
@@ -1,593 +1,593 @@
/* Copyright (C) 2020 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 .
*/
#include "simulation2/system/ComponentTest.h"
#include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpObstruction.h"
class MockObstruction : public ICmpObstruction
{
public:
DEFAULT_MOCK_COMPONENT()
ICmpObstructionManager::ObstructionSquare obstruction;
virtual ICmpObstructionManager::tag_t GetObstruction() const { return ICmpObstructionManager::tag_t(); }
virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) const { out = obstruction; return true; }
virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& UNUSED(out)) const { return true; }
virtual entity_pos_t GetSize() const { return entity_pos_t::Zero(); }
virtual CFixedVector2D GetStaticSize() const { return CFixedVector2D(); }
virtual EObstructionType GetObstructionType() const { return ICmpObstruction::STATIC; }
virtual void SetUnitClearance(const entity_pos_t& UNUSED(clearance)) { }
virtual bool IsControlPersistent() const { return true; }
virtual bool CheckShorePlacement() const { return true; }
virtual EFoundationCheck CheckFoundation(const std::string& UNUSED(className)) const { return EFoundationCheck(); }
virtual EFoundationCheck CheckFoundation(const std::string& UNUSED(className), bool UNUSED(onlyCenterPoint)) const { return EFoundationCheck(); }
virtual std::string CheckFoundation_wrapper(const std::string& UNUSED(className), bool UNUSED(onlyCenterPoint)) const { return std::string(); }
virtual bool CheckDuplicateFoundation() const { return true; }
virtual std::vector GetEntitiesByFlags(ICmpObstructionManager::flags_t UNUSED(flags)) const { return std::vector(); }
virtual std::vector GetEntitiesBlockingMovement() const { return std::vector(); }
virtual std::vector GetEntitiesBlockingConstruction() const { return std::vector(); }
virtual std::vector GetEntitiesDeletedUponConstruction() const { return std::vector(); }
virtual void ResolveFoundationCollisions() const { }
virtual void SetActive(bool UNUSED(active)) { }
virtual void SetMovingFlag(bool UNUSED(enabled)) { }
virtual void SetDisableBlockMovementPathfinding(bool UNUSED(movementDisabled), bool UNUSED(pathfindingDisabled), int32_t UNUSED(shape)) { }
virtual bool GetBlockMovementFlag() const { return true; }
virtual void SetControlGroup(entity_id_t UNUSED(group)) { }
virtual entity_id_t GetControlGroup() const { return INVALID_ENTITY; }
virtual void SetControlGroup2(entity_id_t UNUSED(group2)) { }
virtual entity_id_t GetControlGroup2() const { return INVALID_ENTITY; }
};
class TestCmpObstructionManager : public CxxTest::TestSuite
{
typedef ICmpObstructionManager::tag_t tag_t;
typedef ICmpObstructionManager::ObstructionSquare ObstructionSquare;
// some variables for setting up a scene with 3 shapes
entity_id_t ent1, ent2, ent3; // entity IDs
entity_angle_t ent1a; // angles
entity_pos_t ent1x, ent1z, ent1w, ent1h, // positions/dimensions
ent2x, ent2z, ent2c,
ent3x, ent3z, ent3c;
entity_id_t ent1g1, ent1g2, ent2g, ent3g; // control groups
tag_t shape1, shape2, shape3;
ICmpObstructionManager* cmp;
ComponentTestHelper* testHelper;
public:
void setUp()
{
CXeromyces::Startup();
CxxTest::setAbortTestOnFail(true);
// set up a simple scene with some predefined obstruction shapes
// (we can't position shapes on the origin because the world bounds must range
// from 0 to X, so instead we'll offset things by, say, 10).
ent1 = 1;
ent1a = fixed::Zero();
ent1w = fixed::FromFloat(4);
ent1h = fixed::FromFloat(2);
ent1x = fixed::FromInt(10);
ent1z = fixed::FromInt(10);
ent1g1 = ent1;
ent1g2 = INVALID_ENTITY;
ent2 = 2;
ent2c = fixed::FromFloat(1);
ent2x = ent1x;
ent2z = ent1z;
ent2g = ent1g1;
ent3 = 3;
ent3c = fixed::FromFloat(3);
ent3x = ent2x;
ent3z = ent2z + ent2c + ent3c; // ensure it just touches the border of ent2
ent3g = ent3;
- testHelper = new ComponentTestHelper(g_ScriptRuntime);
+ testHelper = new ComponentTestHelper(g_ScriptContext);
cmp = testHelper->Add(CID_ObstructionManager, "", SYSTEM_ENTITY);
cmp->SetBounds(fixed::FromInt(0), fixed::FromInt(0), fixed::FromInt(1000), fixed::FromInt(1000));
shape1 = cmp->AddStaticShape(ent1, ent1x, ent1z, ent1a, ent1w, ent1h,
ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION |
ICmpObstructionManager::FLAG_BLOCK_MOVEMENT |
ICmpObstructionManager::FLAG_MOVING, ent1g1, ent1g2);
shape2 = cmp->AddUnitShape(ent2, ent2x, ent2z, ent2c,
ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION |
ICmpObstructionManager::FLAG_BLOCK_FOUNDATION, ent2g);
shape3 = cmp->AddUnitShape(ent3, ent3x, ent3z, ent3c,
ICmpObstructionManager::FLAG_BLOCK_MOVEMENT |
ICmpObstructionManager::FLAG_BLOCK_FOUNDATION, ent3g);
}
void tearDown()
{
delete testHelper;
cmp = NULL; // not our responsibility to deallocate
CXeromyces::Terminate();
}
/**
* Verifies the collision testing procedure. Collision-tests some simple shapes against the shapes registered in
* the scene, and verifies the result of the test against the expected value.
*/
void test_simple_collisions()
{
std::vector out;
NullObstructionFilter nullFilter;
// Collision-test a simple shape nested inside shape3 against all shapes in the scene. Since the tested shape
// overlaps only with shape 3, we should find only shape 3 in the result.
cmp->TestUnitShape(nullFilter, ent3x, ent3z, fixed::FromInt(1), &out);
TS_ASSERT_EQUALS(1U, out.size());
TS_ASSERT_EQUALS(ent3, out[0]);
out.clear();
cmp->TestStaticShape(nullFilter, ent3x, ent3z, fixed::Zero(), fixed::FromInt(1), fixed::FromInt(1), &out);
TS_ASSERT_EQUALS(1U, out.size());
TS_ASSERT_EQUALS(ent3, out[0]);
out.clear();
// Similarly, collision-test a simple shape nested inside both shape1 and shape2. Since the tested shape overlaps
// only with shapes 1 and 2, those are the only ones we should find in the result.
cmp->TestUnitShape(nullFilter, ent2x, ent2z, ent2c/2, &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent1);
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
out.clear();
cmp->TestStaticShape(nullFilter, ent2x, ent2z, fixed::Zero(), ent2c, ent2c, &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent1);
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
out.clear();
}
/**
* Verifies the behaviour of the null obstruction filter. Tests with this filter will be performed against all
* registered shapes.
*/
void test_filter_null()
{
std::vector out;
// Collision test a scene-covering shape against all shapes in the scene. We should find all registered shapes
// in the result.
NullObstructionFilter nullFilter;
cmp->TestUnitShape(nullFilter, ent1x, ent1z, fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(3U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent1);
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
TS_ASSERT_VECTOR_CONTAINS(out, ent3);
out.clear();
cmp->TestStaticShape(nullFilter, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(3U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent1);
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
TS_ASSERT_VECTOR_CONTAINS(out, ent3);
out.clear();
}
/**
* Verifies the behaviour of the StationaryOnlyObstructionFilter. Tests with this filter will be performed only
* against non-moving (stationary) shapes.
*/
void test_filter_stationary_only()
{
std::vector out;
// Collision test a scene-covering shape against all shapes in the scene, but skipping shapes that are moving,
// i.e. shapes that have the MOVING flag. Since only shape 1 is flagged as moving, we should find
// shapes 2 and 3 in each case.
StationaryOnlyObstructionFilter ignoreMoving;
cmp->TestUnitShape(ignoreMoving, ent1x, ent1z, fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
TS_ASSERT_VECTOR_CONTAINS(out, ent3);
out.clear();
cmp->TestStaticShape(ignoreMoving, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
TS_ASSERT_VECTOR_CONTAINS(out, ent3);
out.clear();
}
/**
* Verifies the behaviour of the SkipTagObstructionFilter. Tests with this filter will be performed against
* all registered shapes that do not have the specified tag set.
*/
void test_filter_skip_tag()
{
std::vector out;
// Collision-test shape 2's obstruction shape against all shapes in the scene, but skipping tests against
// shape 2. Since shape 2 overlaps only with shape 1, we should find only shape 1's entity ID in the result.
SkipTagObstructionFilter ignoreShape2(shape2);
cmp->TestUnitShape(ignoreShape2, ent2x, ent2z, ent2c/2, &out);
TS_ASSERT_EQUALS(1U, out.size());
TS_ASSERT_EQUALS(ent1, out[0]);
out.clear();
cmp->TestStaticShape(ignoreShape2, ent2x, ent2z, fixed::Zero(), ent2c, ent2c, &out);
TS_ASSERT_EQUALS(1U, out.size());
TS_ASSERT_EQUALS(ent1, out[0]);
out.clear();
}
/**
* Verifies the behaviour of the SkipTagFlagsObstructionFilter. Tests with this filter will be performed against
* all registered shapes that do not have the specified tag set, and that have at least one of required flags set.
*/
void test_filter_skip_tag_require_flag()
{
std::vector out;
// Collision-test a scene-covering shape against all shapes in the scene, but skipping tests against shape 1
// and requiring the BLOCK_MOVEMENT flag. Since shape 1 is being ignored and shape 2 does not have the required
// flag, we should find only shape 3 in the results.
SkipTagRequireFlagsObstructionFilter skipShape1RequireBlockMovement(shape1, ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
cmp->TestUnitShape(skipShape1RequireBlockMovement, ent1x, ent1z, fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(1U, out.size());
TS_ASSERT_EQUALS(ent3, out[0]);
out.clear();
cmp->TestStaticShape(skipShape1RequireBlockMovement, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(1U, out.size());
TS_ASSERT_EQUALS(ent3, out[0]);
out.clear();
// If we now do the same test, but require at least one of the entire set of available filters, we should find
// all shapes that are not shape 1 and that have at least one flag set. Since all shapes in our testing scene
// have at least one flag set, we should find shape 2 and shape 3 in the results.
SkipTagRequireFlagsObstructionFilter skipShape1RequireAnyFlag(shape1, (ICmpObstructionManager::flags_t) -1);
cmp->TestUnitShape(skipShape1RequireAnyFlag, ent1x, ent1z, fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
TS_ASSERT_VECTOR_CONTAINS(out, ent3);
out.clear();
cmp->TestStaticShape(skipShape1RequireAnyFlag, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
TS_ASSERT_VECTOR_CONTAINS(out, ent3);
out.clear();
// And if we now do the same test yet again, but specify an empty set of flags, then it becomes impossible for
// any shape to have at least one of the required flags, and we should hence find no shapes in the result.
SkipTagRequireFlagsObstructionFilter skipShape1RejectAll(shape1, 0U);
cmp->TestUnitShape(skipShape1RejectAll, ent1x, ent1z, fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(0U, out.size());
out.clear();
cmp->TestStaticShape(skipShape1RejectAll, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(0U, out.size());
out.clear();
}
/**
* Verifies the behaviour of SkipControlGroupsRequireFlagObstructionFilter. Tests with this filter will be performed
* against all registered shapes that are members of neither specified control groups, and that have at least one of
* the specified flags set.
*/
void test_filter_skip_controlgroups_require_flag()
{
std::vector out;
// Collision-test a shape that overlaps the entire scene, but ignoring shapes from shape1's control group
// (which also includes shape 2), and requiring that either the BLOCK_FOUNDATION or the
// BLOCK_CONSTRUCTION flag is set, or both. Since shape 1 and shape 2 both belong to shape 1's control
// group, and shape 3 has the BLOCK_FOUNDATION flag (but not BLOCK_CONSTRUCTION), we should find only
// shape 3 in the result.
SkipControlGroupsRequireFlagObstructionFilter skipGroup1ReqFoundConstr(ent1g1, INVALID_ENTITY,
ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION);
cmp->TestUnitShape(skipGroup1ReqFoundConstr, ent1x, ent1z, fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(1U, out.size());
TS_ASSERT_EQUALS(ent3, out[0]);
out.clear();
cmp->TestStaticShape(skipGroup1ReqFoundConstr, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(1U, out.size());
TS_ASSERT_EQUALS(ent3, out[0]);
out.clear();
// Perform the same test, but now also exclude shape 3's control group (in addition to shape 1's control
// group). Despite shape 3 having at least one of the required flags set, it should now also be ignored,
// yielding an empty result set.
SkipControlGroupsRequireFlagObstructionFilter skipGroup1And3ReqFoundConstr(ent1g1, ent3g,
ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION);
cmp->TestUnitShape(skipGroup1And3ReqFoundConstr, ent1x, ent1z, fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(0U, out.size());
out.clear();
cmp->TestStaticShape(skipGroup1And3ReqFoundConstr, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(0U, out.size());
out.clear();
// Same test, but this time excluding only shape 3's control group, and requiring any of the available flags
// to be set. Since both shape 1 and shape 2 have at least one flag set and are both in a different control
// group, we should find them in the result.
SkipControlGroupsRequireFlagObstructionFilter skipGroup3RequireAnyFlag(ent3g, INVALID_ENTITY,
(ICmpObstructionManager::flags_t) -1);
cmp->TestUnitShape(skipGroup3RequireAnyFlag, ent1x, ent1z, fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent1);
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
out.clear();
cmp->TestStaticShape(skipGroup3RequireAnyFlag, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent1);
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
out.clear();
// Finally, the same test as the one directly above, now with an empty set of required flags. Since it now becomes
// impossible for shape 1 and shape 2 to have at least one of the required flags set, and shape 3 is excluded by
// virtue of the control group filtering, we should find an empty result.
SkipControlGroupsRequireFlagObstructionFilter skipGroup3RequireNoFlags(ent3g, INVALID_ENTITY, 0U);
cmp->TestUnitShape(skipGroup3RequireNoFlags, ent1x, ent1z, fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(0U, out.size());
out.clear();
cmp->TestStaticShape(skipGroup3RequireNoFlags, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(0U, out.size());
out.clear();
// ------------------------------------------------------------------------------------
// In the tests up until this point, the shapes have all been filtered out based on their primary control group.
// Now, to verify that shapes are also filtered out based on their secondary control groups, add a fourth shape
// with arbitrarily-chosen dual control groups, and also change shape 1's secondary control group to another
// arbitrarily-chosen control group. Then, do a scene-covering collision test while filtering out a combination
// of shape 1's secondary control group, and one of shape 4's control groups. We should find neither ent1 nor ent4
// in the result.
entity_id_t ent4 = 4,
ent4g1 = 17,
ent4g2 = 19,
ent1g2_new = 18; // new secondary control group for entity 1
entity_pos_t ent4x = fixed::FromInt(4),
ent4z = fixed::Zero(),
ent4w = fixed::FromInt(1),
ent4h = fixed::FromInt(1);
entity_angle_t ent4a = fixed::FromDouble(M_PI/3);
cmp->AddStaticShape(ent4, ent4x, ent4z, ent4a, ent4w, ent4h, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, ent4g1, ent4g2);
cmp->SetStaticControlGroup(shape1, ent1g1, ent1g2_new);
// Exclude shape 1's and shape 4's secondary control groups from testing, and require any available flag to be set.
// Since neither shape 2 nor shape 3 are part of those control groups and both have at least one available flag set,
// the results should only those two shapes' entities.
SkipControlGroupsRequireFlagObstructionFilter skipGroup1SecAnd4SecRequireAny(ent1g2_new, ent4g2,
(ICmpObstructionManager::flags_t) -1);
cmp->TestUnitShape(skipGroup1SecAnd4SecRequireAny, ent1x, ent1z, fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
TS_ASSERT_VECTOR_CONTAINS(out, ent3);
out.clear();
cmp->TestStaticShape(skipGroup1SecAnd4SecRequireAny, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
TS_ASSERT_VECTOR_CONTAINS(out, ent3);
out.clear();
// Same as the above, but now exclude shape 1's secondary and shape 4's primary control group, while still requiring
// any available flag to be set. (Note that the test above used shape 4's secondary control group). Results should
// remain the same.
SkipControlGroupsRequireFlagObstructionFilter skipGroup1SecAnd4PrimRequireAny(ent1g2_new, ent4g1,
(ICmpObstructionManager::flags_t) -1);
cmp->TestUnitShape(skipGroup1SecAnd4PrimRequireAny, ent1x, ent1z, fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
TS_ASSERT_VECTOR_CONTAINS(out, ent3);
out.clear();
cmp->TestStaticShape(skipGroup1SecAnd4PrimRequireAny, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
TS_ASSERT_EQUALS(2U, out.size());
TS_ASSERT_VECTOR_CONTAINS(out, ent2);
TS_ASSERT_VECTOR_CONTAINS(out, ent3);
out.clear();
cmp->SetStaticControlGroup(shape1, ent1g1, ent1g2); // restore shape 1's original secondary control group
}
void test_adjacent_shapes()
{
std::vector out;
NullObstructionFilter nullFilter;
SkipTagObstructionFilter ignoreShape1(shape1);
SkipTagObstructionFilter ignoreShape2(shape2);
SkipTagObstructionFilter ignoreShape3(shape3);
// Collision-test a shape that is perfectly adjacent to shape3. This should be counted as a hit according to
// the code at the time of writing.
entity_angle_t ent4a = fixed::FromDouble(M_PI); // rotated 180 degrees, should not affect collision test
entity_pos_t ent4w = fixed::FromInt(2),
ent4h = fixed::FromInt(1),
ent4x = ent3x + ent3c + ent4w/2, // make ent4 adjacent to ent3
ent4z = ent3z;
cmp->TestStaticShape(nullFilter, ent4x, ent4z, ent4a, ent4w, ent4h, &out);
TS_ASSERT_EQUALS(1U, out.size());
TS_ASSERT_EQUALS(ent3, out[0]);
out.clear();
cmp->TestUnitShape(nullFilter, ent4x, ent4z, ent4w/2, &out);
TS_ASSERT_EQUALS(1U, out.size());
TS_ASSERT_EQUALS(ent3, out[0]);
out.clear();
// now do the same tests, but move the shape a little bit to the right so that it doesn't touch anymore
cmp->TestStaticShape(nullFilter, ent4x + fixed::FromFloat(1e-5f), ent4z, ent4a, ent4w, ent4h, &out);
TS_ASSERT_EQUALS(0U, out.size());
out.clear();
cmp->TestUnitShape(nullFilter, ent4x + fixed::FromFloat(1e-5f), ent4z, ent4w/2, &out);
TS_ASSERT_EQUALS(0U, out.size());
out.clear();
}
/**
* Verifies that fetching the registered shapes from the obstruction manager yields the correct results.
*/
void test_get_obstruction()
{
ObstructionSquare obSquare1 = cmp->GetObstruction(shape1);
ObstructionSquare obSquare2 = cmp->GetObstruction(shape2);
ObstructionSquare obSquare3 = cmp->GetObstruction(shape3);
TS_ASSERT_EQUALS(obSquare1.hh, ent1h/2);
TS_ASSERT_EQUALS(obSquare1.hw, ent1w/2);
TS_ASSERT_EQUALS(obSquare1.x, ent1x);
TS_ASSERT_EQUALS(obSquare1.z, ent1z);
TS_ASSERT_EQUALS(obSquare1.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0)));
TS_ASSERT_EQUALS(obSquare1.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1)));
TS_ASSERT_EQUALS(obSquare2.hh, ent2c);
TS_ASSERT_EQUALS(obSquare2.hw, ent2c);
TS_ASSERT_EQUALS(obSquare2.x, ent2x);
TS_ASSERT_EQUALS(obSquare2.z, ent2z);
TS_ASSERT_EQUALS(obSquare2.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0)));
TS_ASSERT_EQUALS(obSquare2.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1)));
TS_ASSERT_EQUALS(obSquare3.hh, ent3c);
TS_ASSERT_EQUALS(obSquare3.hw, ent3c);
TS_ASSERT_EQUALS(obSquare3.x, ent3x);
TS_ASSERT_EQUALS(obSquare3.z, ent3z);
TS_ASSERT_EQUALS(obSquare3.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0)));
TS_ASSERT_EQUALS(obSquare3.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1)));
}
/**
* Verifies the calculations of distances between shapes.
*/
void test_distance_to()
{
// Create two more entities to have non-zero distances
entity_id_t ent4 = 4,
ent4g1 = ent4,
ent4g2 = INVALID_ENTITY,
ent5 = 5,
ent5g1 = ent5,
ent5g2 = INVALID_ENTITY;
entity_pos_t ent4a = fixed::Zero(),
ent4w = fixed::FromInt(6),
ent4h = fixed::Zero(),
ent4x = ent1x,
ent4z = fixed::FromInt(20),
ent5a = fixed::Zero(),
ent5w = fixed::FromInt(2),
ent5h = fixed::FromInt(4),
ent5x = fixed::FromInt(20),
ent5z = ent1z;
tag_t shape4 = cmp->AddStaticShape(ent4, ent4x, ent4z, ent4a, ent4w, ent4h,
ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION |
ICmpObstructionManager::FLAG_BLOCK_MOVEMENT |
ICmpObstructionManager::FLAG_MOVING, ent4g1, ent4g2);
tag_t shape5 = cmp->AddStaticShape(ent5, ent5x, ent5z, ent5a, ent5w, ent5h,
ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION |
ICmpObstructionManager::FLAG_BLOCK_MOVEMENT |
ICmpObstructionManager::FLAG_MOVING, ent5g1, ent5g2);
MockObstruction obstruction1, obstruction2, obstruction3, obstruction4, obstruction5;
testHelper->AddMock(ent1, IID_Obstruction, obstruction1);
testHelper->AddMock(ent2, IID_Obstruction, obstruction2);
testHelper->AddMock(ent3, IID_Obstruction, obstruction3);
testHelper->AddMock(ent4, IID_Obstruction, obstruction4);
testHelper->AddMock(ent5, IID_Obstruction, obstruction5);
obstruction1.obstruction = cmp->GetObstruction(shape1);
obstruction2.obstruction = cmp->GetObstruction(shape2);
obstruction3.obstruction = cmp->GetObstruction(shape3);
obstruction4.obstruction = cmp->GetObstruction(shape4);
obstruction5.obstruction = cmp->GetObstruction(shape5);
TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent1, ent2));
TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent2, ent1));
TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent2, ent3));
TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent3, ent2));
// Due to rounding errors we need to use some leeway
TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(80)), cmp->MaxDistanceToTarget(ent2, ent3), fixed::FromFloat(0.0001));
TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(80)), cmp->MaxDistanceToTarget(ent3, ent2), fixed::FromFloat(0.0001));
TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent1, ent3));
TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent3, ent1));
TS_ASSERT_EQUALS(fixed::FromInt(6), cmp->DistanceToTarget(ent1, ent4));
TS_ASSERT_EQUALS(fixed::FromInt(6), cmp->DistanceToTarget(ent4, ent1));
TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(125) + 3), cmp->MaxDistanceToTarget(ent1, ent4), fixed::FromFloat(0.0001));
TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(125) + 3), cmp->MaxDistanceToTarget(ent4, ent1), fixed::FromFloat(0.0001));
TS_ASSERT_EQUALS(fixed::FromInt(7), cmp->DistanceToTarget(ent1, ent5));
TS_ASSERT_EQUALS(fixed::FromInt(7), cmp->DistanceToTarget(ent5, ent1));
TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(178)), cmp->MaxDistanceToTarget(ent1, ent5), fixed::FromFloat(0.0001));
TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(178)), cmp->MaxDistanceToTarget(ent5, ent1), fixed::FromFloat(0.0001));
TS_ASSERT(cmp->IsInTargetRange(ent1, ent2, fixed::Zero(), fixed::FromInt(1), true));
TS_ASSERT(cmp->IsInTargetRange(ent1, ent2, fixed::Zero(), fixed::FromInt(1), false));
TS_ASSERT(cmp->IsInTargetRange(ent1, ent2, fixed::FromInt(1), fixed::FromInt(1), true));
TS_ASSERT(!cmp->IsInTargetRange(ent1, ent2, fixed::FromInt(1), fixed::FromInt(1), false));
TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::Zero(), fixed::FromInt(10), true));
TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::Zero(), fixed::FromInt(10), false));
TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(1), fixed::FromInt(10), true));
TS_ASSERT(!cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(1), fixed::FromInt(5), false));
TS_ASSERT(!cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(10), fixed::FromInt(10), false));
TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(10), fixed::FromInt(10), true));
}
};
Index: ps/trunk/source/simulation2/components/tests/test_RangeManager.h
===================================================================
--- ps/trunk/source/simulation2/components/tests/test_RangeManager.h (revision 24180)
+++ ps/trunk/source/simulation2/components/tests/test_RangeManager.h (revision 24181)
@@ -1,156 +1,156 @@
/* Copyright (C) 2019 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 .
*/
#include "simulation2/system/ComponentTest.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpVision.h"
#include
#include
class MockVision : public ICmpVision
{
public:
DEFAULT_MOCK_COMPONENT()
virtual entity_pos_t GetRange() const { return entity_pos_t::FromInt(66); }
virtual bool GetRevealShore() const { return false; }
};
class MockPosition : public ICmpPosition
{
public:
DEFAULT_MOCK_COMPONENT()
virtual void SetTurretParent(entity_id_t UNUSED(id), const CFixedVector3D& UNUSED(pos)) {}
virtual entity_id_t GetTurretParent() const {return INVALID_ENTITY;}
virtual void UpdateTurretPosition() {}
virtual std::set* GetTurrets() { return NULL; }
virtual bool IsInWorld() const { return true; }
virtual void MoveOutOfWorld() { }
virtual void MoveTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) { }
virtual void MoveAndTurnTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z), entity_angle_t UNUSED(a)) { }
virtual void JumpTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) { }
virtual void SetHeightOffset(entity_pos_t UNUSED(dy)) { }
virtual entity_pos_t GetHeightOffset() const { return entity_pos_t::Zero(); }
virtual void SetHeightFixed(entity_pos_t UNUSED(y)) { }
virtual entity_pos_t GetHeightFixed() const { return entity_pos_t::Zero(); }
virtual bool IsHeightRelative() const { return true; }
virtual void SetHeightRelative(bool UNUSED(relative)) { }
virtual bool CanFloat() const { return false; }
virtual void SetFloating(bool UNUSED(flag)) { }
virtual void SetActorFloating(bool UNUSED(flag)) { }
virtual void SetConstructionProgress(fixed UNUSED(progress)) { }
virtual CFixedVector3D GetPosition() const { return CFixedVector3D(); }
virtual CFixedVector2D GetPosition2D() const { return CFixedVector2D(); }
virtual CFixedVector3D GetPreviousPosition() const { return CFixedVector3D(); }
virtual CFixedVector2D GetPreviousPosition2D() const { return CFixedVector2D(); }
virtual void TurnTo(entity_angle_t UNUSED(y)) { }
virtual void SetYRotation(entity_angle_t UNUSED(y)) { }
virtual void SetXZRotation(entity_angle_t UNUSED(x), entity_angle_t UNUSED(z)) { }
virtual CFixedVector3D GetRotation() const { return CFixedVector3D(); }
virtual fixed GetDistanceTravelled() const { return fixed::Zero(); }
virtual void GetInterpolatedPosition2D(float UNUSED(frameOffset), float& x, float& z, float& rotY) const { x = z = rotY = 0; }
virtual CMatrix3D GetInterpolatedTransform(float UNUSED(frameOffset)) const { return CMatrix3D(); }
};
class TestCmpRangeManager : public CxxTest::TestSuite
{
public:
void setUp()
{
CXeromyces::Startup();
}
void tearDown()
{
CXeromyces::Terminate();
}
// 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_ScriptRuntime);
+ ComponentTestHelper test(g_ScriptContext);
ICmpRangeManager* cmp = test.Add(CID_RangeManager, "", SYSTEM_ENTITY);
MockVision vision;
test.AddMock(100, IID_Vision, vision);
MockPosition 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), 512/TERRAIN_TILE_SIZE + 1);
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(0.0, 512.0)(rng);
double z = boost::random::uniform_real_distribution(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;
}
}
}
};
Index: ps/trunk/source/simulation2/system/ComponentManager.h
===================================================================
--- ps/trunk/source/simulation2/system/ComponentManager.h (revision 24180)
+++ ps/trunk/source/simulation2/system/ComponentManager.h (revision 24181)
@@ -1,342 +1,342 @@
/* Copyright (C) 2020 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 .
*/
#ifndef INCLUDED_COMPONENTMANAGER
#define INCLUDED_COMPONENTMANAGER
#include "ps/Filesystem.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/helpers/Player.h"
#include "simulation2/system/Components.h"
#include "simulation2/system/Entity.h"
#include
#include