# Conflicts: # cmake/module_manifest.cmake # docs/usr/modules/procgen.md # src/engine/CMakeLists.txt # src/engine/cmdline/Run.cpp # src/engine/common/ScriptModule.h # src/modules/animation/Animation.cpp # src/modules/physics/World.cpp # src/modules/physics/World3D.cpp # src/modules/procgen/Procgen.cpp # src/modules/procgen/Procgen.h # src/modules/scene/SceneCapabilities.cpp # src/modules/schema/Schema.cpp # src/modules/schema/Schema.h
488 lines
19 KiB
C++
488 lines
19 KiB
C++
#include "procgen/RuntimeGeneration.h"
|
|
#include "procgen/Procgen.h"
|
|
|
|
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
using namespace eve::procgen;
|
|
|
|
namespace {
|
|
|
|
/** @brief Owns a module-backed runtime-generation handle for one test. */
|
|
class RuntimeLease {
|
|
public:
|
|
RuntimeLease() = default;
|
|
RuntimeLease(Procgen& owner, ProcgenRuntimeGenerationHandleRef handle)
|
|
: owner_(&owner), handle_(handle) {}
|
|
|
|
RuntimeLease(const RuntimeLease&) = delete;
|
|
RuntimeLease& operator=(const RuntimeLease&) = delete;
|
|
RuntimeLease(RuntimeLease&& other) noexcept
|
|
: owner_(other.owner_), handle_(other.handle_) {
|
|
other.owner_ = nullptr;
|
|
other.handle_ = {};
|
|
}
|
|
RuntimeLease& operator=(RuntimeLease&& other) noexcept {
|
|
if (this == &other) return *this;
|
|
reset();
|
|
owner_ = other.owner_;
|
|
handle_ = other.handle_;
|
|
other.owner_ = nullptr;
|
|
other.handle_ = {};
|
|
return *this;
|
|
}
|
|
~RuntimeLease() { reset(); }
|
|
|
|
/** @brief Releases the scheduler handle and explicitly observes the Result. */
|
|
void reset() noexcept {
|
|
if (!owner_ || !handle_.isValid()) return;
|
|
auto result = owner_->release(handle_);
|
|
result.ignore("test runtime-generation cleanup");
|
|
owner_ = nullptr;
|
|
handle_ = {};
|
|
}
|
|
|
|
/** @brief Provides a short-lived borrowed view of the scheduler. */
|
|
[[nodiscard]] eve::script::Borrowed<RuntimeGeneration> view() const noexcept {
|
|
return owner_ ? owner_->resolveRuntimeGeneration(handle_)
|
|
: eve::script::Borrowed<RuntimeGeneration>();
|
|
}
|
|
|
|
/** @brief Preserves the concise `runtime->operation()` test syntax. */
|
|
[[nodiscard]] eve::script::Borrowed<RuntimeGeneration> operator->() const noexcept {
|
|
return view();
|
|
}
|
|
|
|
private:
|
|
Procgen* owner_ = nullptr;
|
|
ProcgenRuntimeGenerationHandleRef handle_{};
|
|
};
|
|
|
|
/** @brief Converts a checked scheduler allocation Result into a test lease. */
|
|
RuntimeLease requireRuntime(Procgen& proc, uint32_t worldSeed) {
|
|
auto result = proc.newRuntimeGenerationHandle(worldSeed);
|
|
const bool ok = result.ok();
|
|
if (!ok) {
|
|
const eve::Diagnostic* diagnostic = result.error();
|
|
REQUIRE(diagnostic != nullptr);
|
|
}
|
|
REQUIRE(ok);
|
|
return RuntimeLease(proc, std::move(result).takeValue());
|
|
}
|
|
|
|
/** @brief Gives an owning lifetime to a request allocated by the scheduler API. */
|
|
using RequestLease = std::unique_ptr<ProcgenCellRequest>;
|
|
|
|
[[nodiscard]] RequestLease ownRequest(ProcgenCellRequest* request) noexcept {
|
|
return RequestLease(request);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.partitionsAndPublishesCells") {
|
|
Procgen proc;
|
|
auto runtime = requireRuntime(proc, 42);
|
|
const int nearLevel = runtime->addLevel(10.f, 8.f, 1.5f);
|
|
const int farLevel = runtime->addLevel(40.f, 30.f, 2.f);
|
|
CHECK_EQ(nearLevel, 0);
|
|
CHECK_EQ(farLevel, 1);
|
|
CHECK_EQ(runtime->getLevelCount(), 2);
|
|
CHECK_EQ(runtime->getLevelCleanupRadius(0), 12.f);
|
|
runtime->setMaxGenerating(1);
|
|
runtime->setFrameTimeBudget(2.f);
|
|
CHECK_EQ(runtime->getFrameTimeBudget(), 2.f);
|
|
runtime->beginFrame();
|
|
runtime->updateSource(5.f, 5.f, 1.f, 0.f);
|
|
CHECK(runtime->getPendingGenerateCount() > 0);
|
|
|
|
auto request = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(request));
|
|
CHECK_EQ(runtime->getGeneratingCount(), 1);
|
|
CHECK(!runtime->nextGenerate());
|
|
CHECK_NE(request->getSeed(), uint32_t(0));
|
|
CHECK(request->getMaxX() > request->getMinX());
|
|
|
|
const int level = request->getLevel();
|
|
const int x = request->getX();
|
|
const int z = request->getZ();
|
|
PointSet output;
|
|
output.add(request->getMinX(), 0.f, request->getMinZ());
|
|
CHECK(runtime->completeGeneration(request.get(), &output));
|
|
CHECK(runtime->hasCell(level, x, z));
|
|
CHECK_EQ(runtime->getCellRevision(level, x, z), uint64_t(1));
|
|
auto stored = std::unique_ptr<PointSet>(runtime->getCellOutput(level, x, z));
|
|
REQUIRE(bool(stored));
|
|
CHECK_EQ(stored->getCount(), 1);
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.cleansBeyondHysteresisRadius") {
|
|
Procgen proc;
|
|
auto runtime = requireRuntime(proc, 7);
|
|
runtime->addLevel(10.f, 6.f, 2.f);
|
|
runtime->updateSource(5.f, 5.f, 0.f, 1.f);
|
|
auto generated = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(generated));
|
|
const int cellX = generated->getX();
|
|
const int cellZ = generated->getZ();
|
|
PointSet points;
|
|
points.add(5.f, 0.f, 5.f);
|
|
CHECK(runtime->completeGeneration(generated.get(), &points));
|
|
|
|
runtime->updateSource(100.f, 100.f, 0.f, 1.f);
|
|
CHECK(runtime->getPendingCleanupCount() > 0);
|
|
bool cleanedTarget = false;
|
|
while (auto cleanup = ownRequest(runtime->nextCleanup())) {
|
|
if (cleanup->getX() == cellX && cleanup->getZ() == cellZ) cleanedTarget = true;
|
|
CHECK(runtime->completeCleanup(cleanup.get()));
|
|
}
|
|
CHECK(cleanedTarget);
|
|
CHECK(!runtime->hasCell(0, cellX, cellZ));
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.failedWorkCanRetryWithStableSeed") {
|
|
Procgen proc;
|
|
auto runtime = requireRuntime(proc, 99);
|
|
runtime->addLevel(8.f, 5.f, 1.25f);
|
|
runtime->updateSource(4.f, 4.f, 1.f, 0.f);
|
|
auto first = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(first));
|
|
const uint32_t seed = first->getSeed();
|
|
CHECK(runtime->failGeneration(first.get()));
|
|
|
|
auto retry = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(retry));
|
|
CHECK_EQ(retry->getSeed(), seed);
|
|
CHECK_EQ(retry->getLevel(), 0);
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.unionsNamedSourcesAndCleansAfterLastSource") {
|
|
Procgen proc;
|
|
auto runtime = requireRuntime(proc, 123);
|
|
runtime->addLevel(10.f, 16.f, 1.5f);
|
|
runtime->setMaxGenerating(100);
|
|
CHECK(runtime->setGenerationSource("player", 5.f, 5.f, 1.f, 0.f, 1.f));
|
|
CHECK(runtime->setGenerationSource("quest", 105.f, 5.f, 0.f, 0.f, 1.f));
|
|
CHECK_EQ(runtime->getGenerationSourceCount(), 2);
|
|
CHECK_EQ(runtime->getGenerationSourceId(0), std::string("player"));
|
|
CHECK_EQ(runtime->getGenerationSourceId(1), std::string("quest"));
|
|
|
|
bool generatedNearPlayer = false;
|
|
bool generatedNearQuest = false;
|
|
PointSet empty;
|
|
while (auto request = ownRequest(runtime->nextGenerate())) {
|
|
if (request->getX() >= -1 && request->getX() <= 1) generatedNearPlayer = true;
|
|
if (request->getX() >= 9 && request->getX() <= 11) generatedNearQuest = true;
|
|
CHECK(runtime->completeGeneration(request.get(), &empty));
|
|
}
|
|
CHECK(generatedNearPlayer);
|
|
CHECK(generatedNearQuest);
|
|
|
|
CHECK(runtime->removeGenerationSource("player"));
|
|
bool cleanupNearPlayer = false;
|
|
bool cleanupNearQuest = false;
|
|
while (auto request = ownRequest(runtime->nextCleanup())) {
|
|
if (request->getX() >= -1 && request->getX() <= 1) cleanupNearPlayer = true;
|
|
if (request->getX() >= 9 && request->getX() <= 11) cleanupNearQuest = true;
|
|
CHECK(runtime->completeCleanup(request.get()));
|
|
}
|
|
CHECK(cleanupNearPlayer);
|
|
CHECK(!cleanupNearQuest);
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.frustumCullsFarBehindButKeepsNearCells") {
|
|
Procgen proc;
|
|
auto runtime = requireRuntime(proc, 9);
|
|
runtime->addLevel(10.f, 35.f, 1.25f);
|
|
runtime->setMaxGenerating(100);
|
|
runtime->setFrustumCulling(true, 45.f, 11.f);
|
|
CHECK(runtime->isFrustumCullingEnabled());
|
|
CHECK_EQ(runtime->getFrustumHalfAngle(), 45.f);
|
|
CHECK_EQ(runtime->getFrustumBehindRadius(), 11.f);
|
|
CHECK(runtime->setGenerationSource("camera", 5.f, 5.f, 1.f, 0.f, 1.f));
|
|
|
|
bool foundFarBehind = false;
|
|
bool foundNearBehind = false;
|
|
bool foundForward = false;
|
|
PointSet empty;
|
|
while (auto request = ownRequest(runtime->nextGenerate())) {
|
|
const float centerX = (request->getMinX() + request->getMaxX()) * 0.5f;
|
|
if (centerX < -6.f) foundFarBehind = true;
|
|
if (centerX == -5.f) foundNearBehind = true;
|
|
if (centerX > 5.f) foundForward = true;
|
|
CHECK(runtime->completeGeneration(request.get(), &empty));
|
|
}
|
|
CHECK(!foundFarBehind);
|
|
CHECK(foundNearBehind);
|
|
CHECK(foundForward);
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.rejectsStaleAsyncGenerationTickets") {
|
|
Procgen proc;
|
|
auto runtime = requireRuntime(proc, 77);
|
|
runtime->addLevel(10.f, 6.f, 1.5f);
|
|
runtime->updateSource(5.f, 5.f, 1.f, 0.f);
|
|
auto stale = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(stale));
|
|
const uint64_t staleTicket = stale->getTicket();
|
|
|
|
runtime->updateSource(100.f, 100.f, 1.f, 0.f);
|
|
runtime->updateSource(5.f, 5.f, 1.f, 0.f);
|
|
auto current = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(current));
|
|
CHECK_NE(current->getTicket(), staleTicket);
|
|
PointSet output;
|
|
CHECK(!runtime->completeGeneration(stale.get(), &output));
|
|
CHECK(runtime->completeGeneration(current.get(), &output));
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.rejectsStaleAsyncCleanupTickets") {
|
|
Procgen proc;
|
|
auto runtime = requireRuntime(proc, 78);
|
|
runtime->addLevel(10.f, 6.f, 1.5f);
|
|
runtime->updateSource(5.f, 5.f, 1.f, 0.f);
|
|
auto generated = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(generated));
|
|
PointSet output;
|
|
CHECK(runtime->completeGeneration(generated.get(), &output));
|
|
|
|
runtime->updateSource(100.f, 100.f, 1.f, 0.f);
|
|
auto stale = ownRequest(runtime->nextCleanup());
|
|
REQUIRE(bool(stale));
|
|
runtime->updateSource(5.f, 5.f, 1.f, 0.f);
|
|
runtime->updateSource(100.f, 100.f, 1.f, 0.f);
|
|
CHECK(!runtime->completeCleanup(stale.get()));
|
|
auto current = ownRequest(runtime->nextCleanup());
|
|
REQUIRE(bool(current));
|
|
CHECK_NE(current->getTicket(), stale->getTicket());
|
|
CHECK(runtime->completeCleanup(current.get()));
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.enforcesResidentCellReservations") {
|
|
Procgen proc;
|
|
auto runtime = requireRuntime(proc, 91);
|
|
runtime->addLevel(10.f, 30.f, 1.5f);
|
|
runtime->setMaxGenerating(100);
|
|
runtime->setMaxActiveCells(2);
|
|
CHECK_EQ(runtime->getMaxActiveCells(), 2);
|
|
runtime->updateSource(5.f, 5.f, 1.f, 0.f);
|
|
|
|
auto first = ownRequest(runtime->nextGenerate());
|
|
auto second = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(first));
|
|
REQUIRE(bool(second));
|
|
CHECK(!runtime->nextGenerate());
|
|
CHECK_EQ(runtime->getGeneratingCount(), 2);
|
|
|
|
PointSet empty;
|
|
CHECK(runtime->completeGeneration(first.get(), &empty));
|
|
CHECK(runtime->completeGeneration(second.get(), &empty));
|
|
CHECK_EQ(runtime->getActiveCellCount(), 2);
|
|
CHECK(!runtime->nextGenerate());
|
|
CHECK(runtime->debugReport().find("maxActive=2") != std::string::npos);
|
|
|
|
runtime->setMaxActiveCells(0);
|
|
CHECK_EQ(runtime->getMaxActiveCells(), 0);
|
|
auto unlimited = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(unlimited));
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.enforcesPointMemoryBudgets") {
|
|
Procgen proc;
|
|
auto runtime = requireRuntime(proc, 92);
|
|
runtime->addLevel(10.f, 25.f, 1.5f);
|
|
runtime->setMaxGenerating(100);
|
|
runtime->setMaxPointsPerCell(2);
|
|
runtime->setMaxResidentPoints(2);
|
|
CHECK_EQ(runtime->getMaxPointsPerCell(), 2);
|
|
CHECK_EQ(runtime->getMaxResidentPoints(), 2);
|
|
runtime->updateSource(5.f, 5.f, 1.f, 0.f);
|
|
|
|
auto first = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(first));
|
|
PointSet oversized;
|
|
oversized.add(0.f, 0.f, 0.f);
|
|
oversized.add(1.f, 0.f, 0.f);
|
|
oversized.add(2.f, 0.f, 0.f);
|
|
CHECK(!runtime->completeGeneration(first.get(), &oversized));
|
|
CHECK_EQ(runtime->getRejectedOutputCount(), 1);
|
|
CHECK(runtime->failGeneration(first.get()));
|
|
|
|
auto retry = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(retry));
|
|
PointSet accepted;
|
|
accepted.add(0.f, 0.f, 0.f);
|
|
accepted.add(1.f, 0.f, 0.f);
|
|
CHECK(runtime->completeGeneration(retry.get(), &accepted));
|
|
CHECK_EQ(runtime->getResidentPointCount(), 2);
|
|
|
|
auto second = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(second));
|
|
PointSet one;
|
|
one.add(0.f, 0.f, 0.f);
|
|
CHECK(!runtime->completeGeneration(second.get(), &one));
|
|
CHECK_EQ(runtime->getRejectedOutputCount(), 2);
|
|
CHECK_EQ(runtime->getPendingCleanupCount(), 1);
|
|
runtime->refreshGenerationSources();
|
|
CHECK(!runtime->completeGeneration(second.get(), &one));
|
|
CHECK_EQ(runtime->getRejectedOutputCount(), 3);
|
|
CHECK_EQ(runtime->getPendingCleanupCount(), 1);
|
|
auto cleanup = ownRequest(runtime->nextCleanup());
|
|
REQUIRE(bool(cleanup));
|
|
CHECK(runtime->completeCleanup(cleanup.get()));
|
|
CHECK_EQ(runtime->getResidentPointCount(), 0);
|
|
CHECK(runtime->completeGeneration(second.get(), &one));
|
|
CHECK_EQ(runtime->getResidentPointCount(), 1);
|
|
CHECK(runtime->debugReport().find("residentPoints=1") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.breaksEqualPriorityTiesDeterministically") {
|
|
Procgen proc;
|
|
auto first = requireRuntime(proc, 17);
|
|
auto second = requireRuntime(proc, 17);
|
|
for (RuntimeLease* runtime : {&first, &second}) {
|
|
(*runtime)->addLevel(10.f, 25.f, 1.5f);
|
|
(*runtime)->setMaxGenerating(100);
|
|
(*runtime)->updateSource(5.f, 5.f, 0.f, 0.f);
|
|
}
|
|
for (int index = 0; index < 12; ++index) {
|
|
auto a = ownRequest(first->nextGenerate());
|
|
auto b = ownRequest(second->nextGenerate());
|
|
REQUIRE(bool(a));
|
|
REQUIRE(bool(b));
|
|
CHECK_EQ(a->getLevel(), b->getLevel());
|
|
CHECK_EQ(a->getX(), b->getX());
|
|
CHECK_EQ(a->getZ(), b->getZ());
|
|
}
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.trimsLowestPriorityCellsDeterministically") {
|
|
Procgen proc;
|
|
auto runtime = requireRuntime(proc, 18);
|
|
runtime->addLevel(10.f, 25.f, 1.5f);
|
|
runtime->setMaxGenerating(100);
|
|
runtime->updateSource(5.f, 5.f, 0.f, 0.f);
|
|
int farX = 0;
|
|
int farZ = 0;
|
|
PointSet one;
|
|
one.add(0.f, 0.f, 0.f);
|
|
for (int index = 0; index < 3; ++index) {
|
|
auto request = ownRequest(runtime->nextGenerate());
|
|
REQUIRE(bool(request));
|
|
farX = request->getX();
|
|
farZ = request->getZ();
|
|
CHECK(runtime->completeGeneration(request.get(), &one));
|
|
}
|
|
CHECK_EQ(runtime->getResidentPointCount(), 3);
|
|
const int trimmedCells = runtime->trimToResidentPoints(2);
|
|
CHECK_EQ(trimmedCells, 1);
|
|
CHECK_EQ(runtime->getResidentPointCount(), 3);
|
|
auto cleanup = ownRequest(runtime->nextCleanup());
|
|
REQUIRE(bool(cleanup));
|
|
CHECK_EQ(cleanup->getX(), farX);
|
|
CHECK_EQ(cleanup->getZ(), farZ);
|
|
CHECK(runtime->completeCleanup(cleanup.get()));
|
|
CHECK_EQ(runtime->getResidentPointCount(), 2);
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.boundsRetriesAndExplicitlyRecoversFailures") {
|
|
RuntimeGeneration runtime(29);
|
|
runtime.addLevel(10.f, 6.f, 1.5f);
|
|
runtime.setMaxGenerationRetries(1);
|
|
CHECK_EQ(runtime.getMaxGenerationRetries(), 1);
|
|
runtime.updateSource(5.f, 5.f, 1.f, 0.f);
|
|
|
|
ProcgenCellRequest* initial = runtime.nextGenerate();
|
|
REQUIRE(bool(initial));
|
|
CHECK(runtime.failGeneration(initial));
|
|
delete initial;
|
|
CHECK_EQ(runtime.getFailedCellCount(), 0);
|
|
|
|
ProcgenCellRequest* retry = runtime.nextGenerate();
|
|
REQUIRE(bool(retry));
|
|
CHECK(runtime.failGeneration(retry));
|
|
delete retry;
|
|
CHECK_EQ(runtime.getFailedCellCount(), 1);
|
|
CHECK(runtime.debugReport().find("failed=1") != std::string::npos);
|
|
|
|
const int retriedCells = runtime.retryFailedCells();
|
|
CHECK_EQ(retriedCells, 1);
|
|
CHECK_EQ(runtime.getFailedCellCount(), 0);
|
|
ProcgenCellRequest* recovered = runtime.nextGenerate();
|
|
REQUIRE(bool(recovered));
|
|
PointSet empty;
|
|
CHECK(runtime.completeGeneration(recovered, &empty));
|
|
CHECK_EQ(runtime.getActiveCellCount(), 1);
|
|
delete recovered;
|
|
}
|
|
|
|
TEST_CASE("procgen.runtimeGeneration.persistsAttributedCellCachesAtomically") {
|
|
RuntimeGeneration source(404);
|
|
source.addLevel(10.f, 6.f, 1.5f);
|
|
source.updateSource(5.f, 5.f, 1.f, 0.f);
|
|
ProcgenCellRequest* generated = source.nextGenerate();
|
|
REQUIRE(bool(generated));
|
|
const int level = generated->getLevel();
|
|
const int x = generated->getX();
|
|
const int z = generated->getZ();
|
|
PointSet points;
|
|
const int point = points.add(1.25f, 2.5f, 3.75f);
|
|
points.setNormal(point, 0.f, 0.f, 1.f);
|
|
points.setYaw(point, 45.f);
|
|
points.setScale(point, 2.f, 3.f, 4.f);
|
|
points.setDensity(point, 0.75f);
|
|
points.setPointSeed(point, 1234);
|
|
points.setFloatAttribute(point, "slope", 12.5f);
|
|
points.setFloatAttribute(point, "roughness", 0.4f);
|
|
points.setStringAttribute(point, "asset", "oak \"hero\"");
|
|
points.setStringAttribute(point, "biome", "forest");
|
|
points.add(9.f, 8.f, 7.f);
|
|
CHECK(source.completeGeneration(generated, &points));
|
|
delete generated;
|
|
|
|
const std::string persisted = source.serializeCell(level, x, z);
|
|
CHECK(persisted.find("EVPCG_CELL 1 404") == 0);
|
|
RuntimeGeneration equivalent(404);
|
|
equivalent.addLevel(10.f, 6.f, 1.5f);
|
|
equivalent.updateSource(5.f, 5.f, 1.f, 0.f);
|
|
ProcgenCellRequest* equivalentRequest = equivalent.nextGenerate();
|
|
REQUIRE(bool(equivalentRequest));
|
|
PointSet equivalentPoints = points;
|
|
equivalentPoints.points()[0].floatAttributes.clear();
|
|
equivalentPoints.points()[0].stringAttributes.clear();
|
|
equivalentPoints.setFloatAttribute(0, "roughness", 0.4f);
|
|
equivalentPoints.setFloatAttribute(0, "slope", 12.5f);
|
|
equivalentPoints.setStringAttribute(0, "biome", "forest");
|
|
equivalentPoints.setStringAttribute(0, "asset", "oak \"hero\"");
|
|
CHECK(equivalent.completeGeneration(equivalentRequest, &equivalentPoints));
|
|
delete equivalentRequest;
|
|
CHECK_EQ(equivalent.serializeCell(level, x, z), persisted);
|
|
RuntimeGeneration restored(404);
|
|
restored.addLevel(10.f, 6.f, 1.5f);
|
|
CHECK(restored.deserializeCell(persisted));
|
|
CHECK(restored.hasCell(level, x, z));
|
|
CHECK_EQ(restored.getCellRevision(level, x, z), uint64_t(1));
|
|
PointSet* loaded = restored.getCellOutput(level, x, z);
|
|
REQUIRE(bool(loaded));
|
|
CHECK_EQ(loaded->getCount(), 2);
|
|
CHECK_EQ(loaded->getX(0), 1.25f);
|
|
CHECK_EQ(loaded->getNormalZ(0), 1.f);
|
|
CHECK_EQ(loaded->getScaleY(0), 3.f);
|
|
CHECK_EQ(loaded->getFloatAttribute(0, "slope", -1.f), 12.5f);
|
|
CHECK_EQ(loaded->getStringAttribute(0, "asset", ""), std::string("oak \"hero\""));
|
|
delete loaded;
|
|
|
|
CHECK(!restored.deserializeCell(persisted + "TRAILING"));
|
|
CHECK(restored.hasCell(level, x, z));
|
|
RuntimeGeneration wrongWorld(405);
|
|
wrongWorld.addLevel(10.f, 6.f, 1.5f);
|
|
CHECK(!wrongWorld.deserializeCell(persisted));
|
|
RuntimeGeneration constrained(404);
|
|
constrained.addLevel(10.f, 6.f, 1.5f);
|
|
constrained.setMaxPointsPerCell(1);
|
|
CHECK(!constrained.deserializeCell(persisted));
|
|
CHECK_EQ(constrained.getRejectedOutputCount(), 1);
|
|
}
|