253 lines
12 KiB
C++
253 lines
12 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include "common/Capability.h"
|
|
#include "common/Snapshot.h"
|
|
#include "graphics/ArtifactProvider.h"
|
|
#include "map/ArtifactProvider.h"
|
|
#include "physics/ArtifactProvider.h"
|
|
#include "procgen/ArtifactPublish.h"
|
|
#include "procgen/GeneratedArtifact.h"
|
|
#include "procgen/Params.h"
|
|
#include "scene/ArtifactProvider.h"
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <span>
|
|
#include <string_view>
|
|
|
|
namespace {
|
|
|
|
eve::PersistentId persistentId(const char* text) {
|
|
const auto parsed = eve::PersistentId::parse(text);
|
|
REQUIRE(parsed.has_value());
|
|
return *parsed;
|
|
}
|
|
|
|
eve::procgen::ArtifactId artifactId(const char* text) {
|
|
const auto parsed = eve::procgen::ArtifactId::parse(text);
|
|
REQUIRE(parsed.has_value());
|
|
return *parsed;
|
|
}
|
|
|
|
eve::PersistentId persistentId(eve::procgen::ArtifactId id) { return eve::PersistentId::fromUuid(id); }
|
|
|
|
eve::procgen::Params hexParams() {
|
|
eve::procgen::Params params;
|
|
params.setSeed(424242u);
|
|
params.setInt("width", 8);
|
|
params.setInt("height", 7);
|
|
params.setFloat("radius", 1.f);
|
|
return params;
|
|
}
|
|
|
|
eve::SnapshotHashProvider testHash() {
|
|
return [](std::string_view input) -> eve::Result<eve::ContentId> {
|
|
std::uint64_t hash = 14695981039346656037ull;
|
|
for (const unsigned char byte : input) {
|
|
hash ^= byte;
|
|
hash *= 1099511628211ull;
|
|
}
|
|
std::array<std::uint8_t, 16> bytes{};
|
|
for (std::size_t i = 0; i < 8; ++i) {
|
|
bytes[i] = static_cast<std::uint8_t>((hash >> (i * 8u)) & 0xffu);
|
|
bytes[8 + i] = static_cast<std::uint8_t>(((hash ^ 0x9e3779b97f4a7c15ull) >> (i * 8u)) & 0xffu);
|
|
}
|
|
const auto id = eve::ContentId::fromBytes(std::span<const std::uint8_t>(bytes));
|
|
if (!id)
|
|
return eve::Result<eve::ContentId>::failure(
|
|
eve::Diagnostic::error(eve::DiagnosticCode::Failed, "test digest construction failed"));
|
|
return eve::Result<eve::ContentId>::success(*id);
|
|
};
|
|
}
|
|
|
|
void resetProviders() {
|
|
eve::cap::detail::clearAllRaw();
|
|
eve::scene::sceneArtifactProvider().clear();
|
|
eve::map::mapArtifactProvider().clear();
|
|
eve::graphics::graphicsArtifactProvider().clear();
|
|
eve::physics::physicsArtifactProvider().clear();
|
|
eve::scene::registerSceneArtifactProvider();
|
|
eve::map::registerMapArtifactProvider();
|
|
eve::graphics::registerGraphicsArtifactProvider();
|
|
eve::physics::registerPhysicsArtifactProvider();
|
|
}
|
|
|
|
eve::procgen::ArtifactPublishOptions allProviders() {
|
|
eve::procgen::ArtifactPublishOptions options;
|
|
options.scene = true;
|
|
options.graphics = true;
|
|
options.physics = true;
|
|
options.map = true;
|
|
return options;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("procgen.e2e.hexProvidersCommitQueryableObjects") {
|
|
resetProviders();
|
|
const auto id = artifactId("018f0b7e-6e50-7a10-8c22-2c8f8e3e0101");
|
|
eve::procgen::ArtifactStore store;
|
|
eve::procgen::ArtifactPublisher publisher(store);
|
|
auto generated = eve::procgen::generateHexTerrainArtifact(hexParams(), id);
|
|
REQUIRE(generated.ok());
|
|
REQUIRE(generated.value().metadata.contains("algorithm"));
|
|
REQUIRE(generated.value().metadata.contains("determinism"));
|
|
auto published = publisher.publish(std::move(generated).takeValue(), allProviders());
|
|
REQUIRE(published.ok());
|
|
std::move(published).takeValue();
|
|
|
|
auto& scene = eve::scene::sceneArtifactProvider();
|
|
auto& map = eve::map::mapArtifactProvider();
|
|
auto& graphics = eve::graphics::graphicsArtifactProvider();
|
|
auto& physics = eve::physics::physicsArtifactProvider();
|
|
CHECK_EQ(store.size(), std::size_t(1));
|
|
CHECK_EQ(store.partCount(), std::size_t(4));
|
|
CHECK_EQ(scene.size(), std::size_t(1));
|
|
CHECK_EQ(map.size(), std::size_t(1));
|
|
CHECK_EQ(graphics.size(), std::size_t(1));
|
|
CHECK_EQ(physics.size(), std::size_t(1));
|
|
REQUIRE(map.find(persistentId(id)) != nullptr);
|
|
CHECK_EQ(map.find(persistentId(id))->width, 8);
|
|
CHECK_EQ(map.find(persistentId(id))->height, 7);
|
|
REQUIRE(graphics.find(persistentId(id)) != nullptr);
|
|
CHECK_GT(graphics.find(persistentId(id))->positions.size(), std::size_t(0));
|
|
REQUIRE(physics.find(persistentId(id)) != nullptr);
|
|
const auto* collider = physics.find(persistentId(id));
|
|
REQUIRE_GE(collider->indices.size(), std::size_t(3));
|
|
const auto vertex = [collider](std::size_t corner, std::size_t axis) {
|
|
return collider->vertices[std::size_t(collider->indices[corner]) * 3u + axis];
|
|
};
|
|
const float rayX = (vertex(0, 0) + vertex(1, 0) + vertex(2, 0)) / 3.f;
|
|
const float rayZ = (vertex(0, 2) + vertex(1, 2) + vertex(2, 2)) / 3.f;
|
|
const auto bounds = collider->bounds;
|
|
const auto hit = physics.rayCast(persistentId(id), rayX, bounds.maxY + 10.f, rayZ, 0.f,
|
|
-((bounds.maxY - bounds.minY) + 20.f), 0.f);
|
|
CHECK(hit.hit);
|
|
CHECK(hit.fraction >= 0.f);
|
|
CHECK(hit.fraction <= 1.f);
|
|
}
|
|
|
|
TEST_CASE("procgen.e2e.providerFailureLeavesNoHalfState") {
|
|
for (int failure = 0; failure < 4; ++failure) {
|
|
resetProviders();
|
|
auto& scene = eve::scene::sceneArtifactProvider();
|
|
auto& map = eve::map::mapArtifactProvider();
|
|
auto& graphics = eve::graphics::graphicsArtifactProvider();
|
|
auto& physics = eve::physics::physicsArtifactProvider();
|
|
if (failure == 0) scene.setPrepareFailure(true);
|
|
if (failure == 1) map.setPrepareFailure(true);
|
|
if (failure == 2) graphics.setPrepareFailure(true);
|
|
if (failure == 3) physics.setPrepareFailure(true);
|
|
|
|
eve::procgen::ArtifactStore store;
|
|
eve::procgen::ArtifactPublisher publisher(store);
|
|
auto generated =
|
|
eve::procgen::generateHexTerrainArtifact(hexParams(), artifactId("018f0b7e-6e50-7a10-8c22-2c8f8e3e0102"));
|
|
REQUIRE(generated.ok());
|
|
auto result = publisher.publish(std::move(generated).takeValue(), allProviders());
|
|
CHECK(!result.ok());
|
|
CHECK_EQ(store.size(), std::size_t(0));
|
|
CHECK_EQ(store.partCount(), std::size_t(0));
|
|
CHECK_EQ(scene.size(), std::size_t(0));
|
|
CHECK_EQ(map.size(), std::size_t(0));
|
|
CHECK_EQ(graphics.size(), std::size_t(0));
|
|
CHECK_EQ(physics.size(), std::size_t(0));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("procgen.e2e.absentCapabilityIsExplicit") {
|
|
resetProviders();
|
|
eve::cap::revoke<eve::artifact::IPhysicsArtifactAdapter>(&eve::physics::physicsArtifactProvider());
|
|
eve::procgen::ArtifactStore store;
|
|
eve::procgen::ArtifactPublisher publisher(store);
|
|
auto generated =
|
|
eve::procgen::generateHexTerrainArtifact(hexParams(), artifactId("018f0b7e-6e50-7a10-8c22-2c8f8e3e0103"));
|
|
REQUIRE(generated.ok());
|
|
auto result = publisher.publish(std::move(generated).takeValue(), allProviders());
|
|
CHECK(!result.ok());
|
|
CHECK_EQ(static_cast<int>(result.code()), static_cast<int>(eve::StatusCode::Unsupported));
|
|
CHECK_EQ(store.size(), std::size_t(0));
|
|
CHECK_EQ(eve::physics::physicsArtifactProvider().size(), std::size_t(0));
|
|
}
|
|
|
|
TEST_CASE("procgen.e2e.snapshotRoundTripPreservesIdentityBuildKeyAndQueries") {
|
|
resetProviders();
|
|
const auto id = artifactId("018f0b7e-6e50-7a10-8c22-2c8f8e3e0104");
|
|
eve::procgen::ArtifactStore store;
|
|
eve::procgen::ArtifactPublisher publisher(store);
|
|
auto generated = eve::procgen::generateHexTerrainArtifact(hexParams(), id);
|
|
REQUIRE(generated.ok());
|
|
const auto expectedKey = generated.value().buildKey;
|
|
auto published = publisher.publish(std::move(generated).takeValue(), allProviders());
|
|
REQUIRE(published.ok());
|
|
std::move(published).takeValue();
|
|
const auto* beforeCollider = eve::physics::physicsArtifactProvider().find(persistentId(id));
|
|
REQUIRE(beforeCollider != nullptr);
|
|
const auto bounds = beforeCollider->bounds;
|
|
const float rayX = (bounds.minX + bounds.maxX) * .5f;
|
|
const float rayZ = (bounds.minZ + bounds.maxZ) * .5f;
|
|
const float rayOriginY = bounds.maxY + 10.f;
|
|
const float rayDirectionY = -((bounds.maxY - bounds.minY) + 20.f);
|
|
const auto beforeHit = eve::physics::physicsArtifactProvider().rayCast(persistentId(id), rayX, rayOriginY, rayZ,
|
|
0.f, rayDirectionY, 0.f);
|
|
const auto beforeChecksum = eve::graphics::graphicsArtifactProvider().checksum(persistentId(id));
|
|
const auto beforeCell = eve::map::mapArtifactProvider().cell(persistentId(id), 2, 3);
|
|
|
|
eve::procgen::ArtifactSnapshotContext context;
|
|
context.instanceId = persistentId("018f0b7e-6e50-7a10-8c22-2c8f8e3e0105");
|
|
context.revision = eve::Revision(9);
|
|
context.tick = eve::SimulationTick(17);
|
|
context.hashProvider = testHash();
|
|
auto snapshot = publisher.snapshot(context);
|
|
REQUIRE(snapshot.ok());
|
|
auto serialized = eve::serializeSnapshotEnvelope(snapshot.value());
|
|
REQUIRE(serialized.ok());
|
|
auto parsed = eve::parseSnapshotEnvelope(serialized.value(), context.hashProvider);
|
|
REQUIRE(parsed.ok());
|
|
|
|
store.clear();
|
|
eve::scene::sceneArtifactProvider().clear();
|
|
eve::map::mapArtifactProvider().clear();
|
|
eve::graphics::graphicsArtifactProvider().clear();
|
|
eve::physics::physicsArtifactProvider().clear();
|
|
auto restored = publisher.restore(std::move(parsed).takeValue(), context.hashProvider);
|
|
REQUIRE(restored.ok());
|
|
restored.value();
|
|
|
|
CHECK_EQ(store.size(), std::size_t(1));
|
|
CHECK_EQ(store.typeOf(id), std::optional<eve::procgen::ArtifactType>(eve::procgen::ArtifactType::Composite));
|
|
const auto matching = store.findByBuildKey(expectedKey);
|
|
REQUIRE_EQ(matching.size(), std::size_t(1));
|
|
CHECK_EQ(matching.front(), id);
|
|
CHECK_EQ(eve::scene::sceneArtifactProvider().size(), std::size_t(1));
|
|
CHECK_EQ(eve::map::mapArtifactProvider().cell(persistentId(id), 2, 3), beforeCell);
|
|
CHECK_EQ(eve::graphics::graphicsArtifactProvider().checksum(persistentId(id)), beforeChecksum);
|
|
const auto afterHit = eve::physics::physicsArtifactProvider().rayCast(persistentId(id), rayX, rayOriginY, rayZ, 0.f,
|
|
rayDirectionY, 0.f);
|
|
CHECK_EQ(afterHit.hit, beforeHit.hit);
|
|
CHECK_EQ(afterHit.fraction, beforeHit.fraction);
|
|
}
|
|
|
|
TEST_CASE("procgen.e2e.fixedSeedBuildKeyAndPayloadAreStable") {
|
|
const auto first =
|
|
eve::procgen::generateHexTerrainArtifact(hexParams(), artifactId("018f0b7e-6e50-7a10-8c22-2c8f8e3e0106"));
|
|
const auto second =
|
|
eve::procgen::generateHexTerrainArtifact(hexParams(), artifactId("018f0b7e-6e50-7a10-8c22-2c8f8e3e0107"));
|
|
REQUIRE(first.ok());
|
|
REQUIRE(second.ok());
|
|
CHECK_EQ(first.value().buildKey, second.value().buildKey);
|
|
REQUIRE(first.value().metadata.contains("determinism"));
|
|
const auto* determinism = first.value().metadata.at("determinism").getIf<std::string>();
|
|
REQUIRE(determinism != nullptr);
|
|
CHECK_EQ(*determinism, std::string("seeded_cpu"));
|
|
const auto& firstComposite = std::get<eve::procgen::CompositeArtifact>(first.value().payload);
|
|
const auto& secondComposite = std::get<eve::procgen::CompositeArtifact>(second.value().payload);
|
|
REQUIRE(firstComposite.find("mesh") != nullptr);
|
|
REQUIRE(secondComposite.find("mesh") != nullptr);
|
|
CHECK_EQ(std::get<eve::procgen::MeshData>(firstComposite.find("mesh")->payload).positions(),
|
|
std::get<eve::procgen::MeshData>(secondComposite.find("mesh")->payload).positions());
|
|
CHECK_EQ(std::get<eve::procgen::Grid2D>(firstComposite.find("topology")->payload).cells(),
|
|
std::get<eve::procgen::Grid2D>(secondComposite.find("topology")->payload).cells());
|
|
}
|