1
0
mirror of https://github.com/OpenTrespasser/JurassicParkTrespasser.git synced 2024-12-20 23:51:58 +00:00

Merge pull request from meekee7/ai-removesomeinfluences-refactor

Refactor CWorldView::RemoveSomeInfluences
This commit is contained in:
Ian Brun 2021-03-09 04:01:44 +01:00 committed by GitHub
commit 040c99d81b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 358 additions and 40 deletions

View File

@ -42,6 +42,7 @@ include(cmake/CMakeCommon.cmake)
add_subdirectory(cmake/AI)
add_subdirectory(cmake/AITest)
add_subdirectory(cmake/AIUnitTest)
add_subdirectory(cmake/Audio)
add_subdirectory(cmake/AudioTest)
add_subdirectory(cmake/Bugs)

View File

@ -136,7 +136,7 @@ public:
CInstance* const pinsTarget; // The object associated with this influence
// or 0 if none.
CSet<EInfluenceFlag>setFlags; // A bunch of flags for an influence.
mutable CSet<EInfluenceFlag> setFlags; // A bunch of flags for an influence.
// CFeeling feelDBGWeighted; // The emotional baggage associated with the influence,
@ -415,6 +415,10 @@ public:
// return (CInfluence)*this;
// }
static bool IsDiscardable(const CInfluence& influence)
{
return influence.setFlags[eifIS_DISCARDABLE];
}
};

View File

@ -367,56 +367,38 @@ void CWorldView::RemoveSomeInfluences()
// unimportant enough, go ahead.
// If we have a bunch of influences, remove one.
CInfluenceList::iterator pinf_lowest = inflInfluences.end();
CInfluenceList::iterator pinf = inflInfluences.begin();
size_t discardable = 0;
int i_discardable_count = 0;
std::list<const CInfluence*> lpinf_discard;
// Find the lamest influence and remove it.
// For now, we only remove one bad apple each cycle.
for (; pinf != inflInfluences.end(); pinf++)
CInstance* lowestInstance = nullptr;
std::list<CInstance*> todelete;
for (const auto& entry : inflInfluences)
{
TReal r_importance = (*pinf).rImportance;
if (gaiSystem.sNow - (*pinf).sLastSeen > paniOwner->pbrBrain->sForgetInfluence)
if (gaiSystem.sNow - entry.sLastSeen > paniOwner->pbrBrain->sForgetInfluence)
entry.setFlags[eifIS_DISCARDABLE] = true;
else if (entry.rImportance < r_lowest)
{
// It's been a while. Forget me, please.
((CInfluence*)&(*pinf))->setFlags[eifIS_DISCARDABLE] = true;
}
else if ( r_importance < r_lowest)
{
r_lowest = r_importance;
pinf_lowest = pinf;
r_lowest = entry.rImportance;
lowestInstance = entry.pinsTarget;
}
if ((*pinf).setFlags[eifIS_DISCARDABLE])
if (entry.setFlags[eifIS_DISCARDABLE])
{
// Add to discard list!
lpinf_discard.push_back(&(*pinf));
++i_discardable_count;
todelete.push_back(entry.pinsTarget);
discardable++;
}
}
if (pinf_lowest != inflInfluences.end())
{
// Add to discard list!
lpinf_discard.push_back(&(*pinf_lowest));
((CInfluence*)&(*pinf_lowest))->setFlags[eifIS_DISCARDABLE] = true;
}
if (i_discardable_count > 10)
if (lowestInstance)
{
std::list<const CInfluence*>::iterator itpinf;
for(itpinf = lpinf_discard.begin(); itpinf != lpinf_discard.end(); ++itpinf)
{
// Is it discardable?
Assert((*itpinf)->setFlags[eifIS_DISCARDABLE]);
// Discard it.
bRemoveInfluence((*itpinf)->pinsTarget);
}
//Influence set is based on instance pointers, find result is guaranteed to be the correct one
iterFindInfluence(lowestInstance)->setFlags[eifIS_DISCARDABLE] = true;
todelete.push_back(lowestInstance);
}
if (discardable > 10)
for (auto* entry : todelete)
bRemoveInfluence(entry);
}
//*********************************************************************************

View File

@ -0,0 +1,38 @@
#include "common.hpp"
#include "Game/AI/Influence.hpp"
#include "Lib/EntityDBase/Instance.hpp"
#include "gtest/gtest.h"
TEST(CInfluence, ComparisonSetFlagsNoInfluence)
{
//Comparison is based on the pins_target pointer value
//Use specific pointers
CInfluence a(reinterpret_cast<CInstance*>(60));
CInfluence b(reinterpret_cast<CInstance*>(80));
const std::less<> less;
ASSERT_TRUE(less(a, b));
ASSERT_FALSE(less(b, a));
//Test that the discardable flag has no influence on comparison
for (int i=0; i < 4; i++)
{
a.setFlags[eifIS_DISCARDABLE] = static_cast<bool>(i & (1 << 0)); //First bit
b.setFlags[eifIS_DISCARDABLE] = static_cast<bool>(i & (1 << 1)); //Second bit
EXPECT_TRUE(less(a, b));
EXPECT_FALSE(less(b, a));
}
}
TEST(CInfluence, SetFlagOnConst)
{
const CInfluence influence(nullptr);
ASSERT_FALSE(influence.setFlags[eifIS_DISCARDABLE]);
//Test that we can set the flag despite constness
influence.setFlags[eifIS_DISCARDABLE] = true;
EXPECT_TRUE(influence.setFlags[eifIS_DISCARDABLE]);
}

View File

@ -0,0 +1,246 @@
#include "common.hpp"
#include "Game/AI/WorldView.hpp"
#include "Game/AI/AIMain.hpp"
#include "Game/AI/AIInfo.hpp"
#include "Lib/EntityDBase/Instance.hpp"
#include "Lib/EntityDBase/Animal.hpp"
//#include "Test/AI/TestAnimal.hpp"
#include "gtest/gtest.h"
class WorldViewTest : public ::testing::Test
{
protected:
virtual void SetUp() override
{
aisystem = std::make_unique<CAISystem>(nullptr);
aisystem->sNow = 100.0f;
gpaiSystem = aisystem.get();
owner = std::make_unique<CAnimal>();
view = std::make_unique<CWorldView>(owner.get());
}
virtual void TearDown() override
{
aisystem = nullptr;
gpaiSystem = nullptr;
view = nullptr;
owner = nullptr;
}
void InsertNInfluences(size_t n, TSec lastSeen, TReal prio, bool markDiscarded = false)
{
for (size_t i = 0; i < n; i++)
InsertInfluence(lastSeen, prio, markDiscarded);
}
void InsertInfluence(TSec lastSeen, TReal prio, bool markDiscarded = false)
{
//Instance is deleted in CInfluence destructor
CInfluence influence(new CInstance());
influence.sLastSeen = lastSeen;
influence.rImportance = prio;
influence.setFlags[eifIS_DISCARDABLE] = markDiscarded;
view->inflInfluences.insert(influence);
}
CInfluenceList& GetList()
{
return view->inflInfluences;
}
static bool IsMarkedDiscardable(const CInfluence& influence)
{
return influence.setFlags[eifIS_DISCARDABLE];
}
std::unique_ptr<CAISystem> aisystem;
std::unique_ptr<CAnimal> owner;
std::unique_ptr<CWorldView> view;
};
//Nothing weird happens with an empty list
TEST_F(WorldViewTest, RemoveOnEmpty)
{
ASSERT_TRUE(GetList().empty());
view->RemoveSomeInfluences();
EXPECT_TRUE(GetList().empty());
}
//Few old influences, marked but not discarded
TEST_F(WorldViewTest, MarkOldsAsDiscardable)
{
constexpr size_t elems = 5;
InsertNInfluences(elems, aisystem->sNow - 2.5f, 10.0f);
ASSERT_EQ(GetList().size(), elems);
ASSERT_TRUE(std::none_of(GetList().begin(), GetList().end(), &IsMarkedDiscardable));
view->RemoveSomeInfluences();
EXPECT_EQ(GetList().size(), elems);
EXPECT_TRUE(std::all_of(GetList().begin(), GetList().end(), &IsMarkedDiscardable));
}
//Few new influences, not marked and not removed
TEST_F(WorldViewTest, NoMarkCurrentAsDiscardable)
{
constexpr size_t elems = 5;
InsertNInfluences(elems, aisystem->sNow - 0.5f, 10.0f);
ASSERT_EQ(GetList().size(), elems);
ASSERT_TRUE(std::none_of(GetList().begin(), GetList().end(), &CInfluence::IsDiscardable));
view->RemoveSomeInfluences();
EXPECT_EQ(GetList().size(), elems);
EXPECT_TRUE(std::none_of(GetList().begin(), GetList().end(), &CInfluence::IsDiscardable));
}
//One zero prio influence, marked but not removed
TEST_F(WorldViewTest, MarkZeroPrioAsDiscardable)
{
InsertInfluence(aisystem->sNow - 0.5f, 0.0f);
ASSERT_FALSE(GetList().empty());
ASSERT_FALSE(GetList().begin()->setFlags[eifIS_DISCARDABLE]);
view->RemoveSomeInfluences();
EXPECT_FALSE(GetList().empty());
EXPECT_TRUE(GetList().begin()->setFlags[eifIS_DISCARDABLE]);
}
//Many current influences, lowest prio one is removed
TEST_F(WorldViewTest, MarkLowestOfFew)
{
constexpr size_t elems = 5;
InsertNInfluences(elems, aisystem->sNow - 0.5f, 0.05f);
InsertInfluence(aisystem->sNow - 0.5f, 0.0f);
ASSERT_EQ(GetList().size(), elems + 1);
ASSERT_TRUE(std::none_of(GetList().begin(), GetList().end(), &CInfluence::IsDiscardable));
view->RemoveSomeInfluences();
EXPECT_EQ(GetList().size(), elems + 1);
EXPECT_EQ(std::count_if(GetList().begin(), GetList().end(), &CInfluence::IsDiscardable), 1);
EXPECT_EQ(std::find_if(GetList().begin(), GetList().end(), &CInfluence::IsDiscardable)->rImportance, 0.0f);
}
TEST_F(WorldViewTest, NoDiscardUnmarked)
{
constexpr size_t elems = 20;
InsertNInfluences(elems, aisystem->sNow - 0.5f, 10.0f, false);
view->RemoveSomeInfluences();
EXPECT_EQ(GetList().size(), elems);
}
TEST_F(WorldViewTest, DiscardMarkedAll)
{
constexpr size_t elems = 20;
InsertNInfluences(elems, aisystem->sNow - 0.5f, 10.0f, true);
view->RemoveSomeInfluences();
EXPECT_TRUE(GetList().empty());
}
TEST_F(WorldViewTest, DiscardMarkedMixed)
{
constexpr size_t keep = 12;
constexpr size_t discard = 12;
InsertNInfluences(keep, aisystem->sNow - 0.5f, 10.0f, false);
InsertNInfluences(discard, aisystem->sNow - 0.5f, 10.0f, true);
view->RemoveSomeInfluences();
EXPECT_EQ(GetList().size(), keep);
EXPECT_TRUE(std::none_of(GetList().begin(), GetList().end(), &CInfluence::IsDiscardable));
}
//Many current influences, none discarded
TEST_F(WorldViewTest, NoDiscardCurrent)
{
constexpr size_t tocreate = 20;
InsertNInfluences(tocreate, aisystem->sNow - 0.5f, 10.0f);
ASSERT_EQ(GetList().size(), tocreate);
view->RemoveSomeInfluences();
EXPECT_EQ(GetList().size(), tocreate);
}
//Many old influences, will be discarded
TEST_F(WorldViewTest, DiscardOldsMany)
{
constexpr size_t olds = 20;
constexpr size_t news = 5;
InsertNInfluences(news, aisystem->sNow - 0.5f, 10.0f);
InsertNInfluences(olds, aisystem->sNow - 2.5f, 10.0f);
ASSERT_EQ(GetList().size(), olds + news);
view->RemoveSomeInfluences();
EXPECT_EQ(GetList().size(), news);
}
//Many old influences and a current low-prio one, will be discarded
TEST_F(WorldViewTest, DiscardOldsAndLowestPrioCurrent)
{
constexpr size_t olds = 20;
constexpr size_t news = 5;
InsertNInfluences(news, aisystem->sNow - 0.5f, 10.0f);
InsertNInfluences(olds, aisystem->sNow - 2.5f, 10.0f);
InsertInfluence(aisystem->sNow - 0.5f, 0.0f);
ASSERT_EQ(GetList().size(), olds + news + 1);
view->RemoveSomeInfluences();
EXPECT_EQ(GetList().size(), news);
}
//Few influences, one with zero prio is to be removed
TEST_F(WorldViewTest, DiscardFewZeroPrio)
{
InsertNInfluences(15, aisystem->sNow - 2.5f, 10.0f);
InsertInfluence(aisystem->sNow - 0.5f, 0.0f);
view->RemoveSomeInfluences();
EXPECT_TRUE(GetList().empty());
}
//Some old influences to be removed, one with some prio is not to be removed
TEST_F(WorldViewTest, NoDiscardFewWithPrio)
{
InsertNInfluences(15, aisystem->sNow - 2.5f, 10.0f);
InsertInfluence(aisystem->sNow - 0.5f, 1.0f);
view->RemoveSomeInfluences();
EXPECT_EQ(GetList().size(), 1);
}
//Many old influences to be removed, new influence but with lowest prio is also removed
TEST_F(WorldViewTest, DiscardManyWithPrio)
{
InsertNInfluences(40, aisystem->sNow - 2.5f, 10.0f);
InsertInfluence(aisystem->sNow - 0.5f, 1.0f);
view->RemoveSomeInfluences();
EXPECT_TRUE(GetList().empty());
}
//Very few influences, none are removed, including one with zero prio
TEST_F(WorldViewTest, NoDiscardVeryFewZeroPrio)
{
constexpr size_t elems = 5;
InsertNInfluences(elems, aisystem->sNow - 2.5f, 10.0f);
InsertInfluence(aisystem->sNow - 0.5f, 0.0f);
view->RemoveSomeInfluences();
EXPECT_EQ(GetList().size(), elems + 1);
}

View File

@ -0,0 +1,47 @@
project(AIUnitTest)
list(APPEND AIUnitTest_Src
${CMAKE_SOURCE_DIR}/Source/Test/Novel/AIUnitTest/WorldViewTest.cpp
${CMAKE_SOURCE_DIR}/Source/Test/Novel/AIUnitTest/InfluenceTest.cpp
)
add_common_options()
add_executable(${PROJECT_NAME} ${AIUnitTest_Src})
set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Tests/Novel)
include_directories(
${CMAKE_SOURCE_DIR}/Source
${CMAKE_SOURCE_DIR}/Source/gblinc
${CMAKE_SOURCE_DIR}/gtest/googletest/include
)
target_link_libraries( ${PROJECT_NAME}
AI
Audio
EntityDBase
File
Game
GeomDBase
Loader
Math
Physics
Render3D
ScreenRenderDWI
Std
System
View
lz32
winmm
dxguid
dsound
ddraw
GTestLibGlobals
gtest
gtest_main
)