Files
UnrealEngine/Engine/Source/Programs/AutoRTFMTests/Private/TaskArrayTests.cpp
2025-05-18 13:04:45 +08:00

282 lines
5.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "TaskArray.h"
#include "Catch2Includes.h"
#include <memory>
#include <random>
#include <unordered_map>
TEST_CASE("TTaskArray")
{
std::mt19937 Rand(0x1234);
using TaskType = int;
using TaskArrayType = AutoRTFM::TTaskArray
<
/* TaskType */ int,
/* KeyType */ const void*
>;
using TaskPoolType = typename TaskArrayType::FEntryPool;
TaskPoolType Pool;
auto TaskArray = std::make_unique<TaskArrayType>(Pool);
struct FEntry
{
int Value = 0;
FEntry* Prev = nullptr;
FEntry* Next = nullptr;
};
std::vector<void*> Keys;
std::unordered_map<void*, std::vector<FEntry*>> EntriesByKey;
FEntry* Head = nullptr;
FEntry* Tail = nullptr;
auto DeleteEntry = [&](FEntry* Entry)
{
if (Head == Entry) { Head = Entry->Next; }
if (Tail == Entry) { Tail = Entry->Prev; }
if (Entry->Prev) { Entry->Prev->Next = Entry->Next; }
if (Entry->Next) { Entry->Next->Prev = Entry->Prev; }
Entry->Prev = nullptr;
Entry->Next = nullptr;
delete Entry;
};
struct FConfig
{
size_t NumKeys = 0;
size_t NumElements = 0;
};
auto Populate = [&](FConfig Config)
{
for (size_t I = 0; I < Config.NumKeys; I++)
{
void* Key = reinterpret_cast<void*>(static_cast<uintptr_t>(I + 1) << 20);
Keys.push_back(Key);
}
for (size_t I = 0; I < Config.NumElements; I++)
{
const int Value = static_cast<int>(Rand());
FEntry* const Entry = new FEntry{};
Entry->Value = Value;
if (!Head)
{
Head = Entry;
}
else
{
Tail->Next = Entry;
Entry->Prev = Tail;
}
Tail = Entry;
if (Config.NumKeys > 0 && Rand() & 1)
{
void* Key = Keys[Rand() % Keys.size()];
EntriesByKey[Key].push_back(Entry);
TaskArray->AddKeyed(Key, Value);
}
else
{
TaskArray->Add(Value);
}
}
};
// Checks the TaskArray is valid using a number of different approaches.
auto Check = [&]
{
if (Head)
{
const size_t ExpectedCount = TaskArray->Num();
size_t Count = 0;
SECTION("RemoveEachForward")
{
FEntry* Current = Head;
TaskArray->RemoveEachForward([&](int Got)
{
REQUIRE(Current);
REQUIRE(Current->Value == Got);
Current = Current->Next;
Count++;
});
REQUIRE(Count == ExpectedCount);
REQUIRE(!Current);
}
SECTION("RemoveEachBackward")
{
FEntry* Current = Tail;
TaskArray->RemoveEachBackward([&](int Got)
{
REQUIRE(Current);
REQUIRE(Current->Value == Got);
Current = Current->Prev;
Count++;
});
REQUIRE(Count == ExpectedCount);
REQUIRE(!Current);
}
SECTION("Reset")
{
TaskArray->Reset();
}
REQUIRE(TaskArray->Num() == 0);
REQUIRE(TaskArray->IsEmpty());
REQUIRE(Pool.GetNumInUse() == 0);
}
else
{
REQUIRE(TaskArray->Num() == 0);
REQUIRE(TaskArray->IsEmpty());
TaskArray->RemoveEachForward([&](int)
{
FAIL("Should not be called");
});
TaskArray->RemoveEachBackward([&](int)
{
FAIL("Should not be called");
});
}
};
auto CallWithConfigs = [&](auto&& Fn)
{
auto WithKeys = [&](size_t NumKeys)
{
SECTION("NumElements: 0")
{
Fn(FConfig{NumKeys, /* NumElements */ 0});
}
SECTION("NumElements: 10")
{
Fn(FConfig{NumKeys, /* NumElements */ 10});
}
SECTION("NumElements: 100")
{
Fn(FConfig{NumKeys, /* NumElements */ 100});
}
SECTION("NumElements: 1000")
{
Fn(FConfig{NumKeys, /* NumElements */ 1000});
}
SECTION("NumElements: 10000")
{
Fn(FConfig{NumKeys, /* NumElements */ 10000});
}
};
SECTION("NumKeys: 0")
{
WithKeys(/* NumKeys */ 0);
}
SECTION("NumKeys: 5")
{
WithKeys(/* NumKeys */ 5);
}
SECTION("NumKeys: 10")
{
WithKeys(/* NumKeys */ 10);
}
};
SECTION("Add")
{
CallWithConfigs([&](FConfig Config)
{
Populate(Config);
Check();
});
}
SECTION("DeleteKey")
{
CallWithConfigs([&](FConfig Config)
{
if (Config.NumKeys == 0)
{
return;
}
Populate(Config);
for (size_t I = 0; I < 10; I++)
{
void* Key = Keys[Rand() % Config.NumKeys];
bool HoldsKey = false;
if (std::vector<FEntry*>& Entries = EntriesByKey[Key]; Entries.size())
{
HoldsKey = true;
DeleteEntry(Entries.back());
Entries.pop_back();
}
REQUIRE(TaskArray->DeleteKey(Key) == HoldsKey);
}
Check();
});
}
SECTION("DeleteAllMatchingKeys")
{
CallWithConfigs([&](FConfig Config)
{
if (Config.NumKeys == 0)
{
return;
}
Populate(Config);
for (size_t I = 0; I < 4; I++)
{
void* Key = Keys[Rand() % Config.NumKeys];
std::vector<FEntry*>& Entries = EntriesByKey[Key];
bool HoldsKey = !Entries.empty();
while (Entries.size())
{
DeleteEntry(Entries.back());
Entries.pop_back();
}
REQUIRE(TaskArray->DeleteAllMatchingKeys(Key) == HoldsKey);
}
Check();
});
}
SECTION("AddAll")
{
CallWithConfigs([&](FConfig Config)
{
Populate(Config);
auto Second = std::make_unique<TaskArrayType>(Pool);
std::swap(TaskArray, Second);
Populate(Config);
std::swap(TaskArray, Second);
TaskArray->AddAll(std::move(*Second));
Check();
});
}
// Clean-up
while (Head)
{
DeleteEntry(Head);
}
}