573 lines
28 KiB
C++
573 lines
28 KiB
C++
#include "common/Capability.h"
|
|
#include "common/ProcgenSceneSink.h"
|
|
#include "procgen/Procgen.h"
|
|
|
|
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using namespace eve::procgen;
|
|
|
|
namespace {
|
|
|
|
class MockProcgenSceneSink final : public eve::IProcgenSceneSink {
|
|
public:
|
|
bool applyBatch(const std::string& batchId, const std::vector<eve::ProcgenInstanceDesc>& instances) override {
|
|
const auto previous = batches.find(batchId);
|
|
std::unordered_map<std::string, bool> oldIds;
|
|
if (previous != batches.end())
|
|
for (const auto& instance : previous->second) oldIds[instance.id] = true;
|
|
Stats stats;
|
|
for (const auto& instance : instances) {
|
|
if (oldIds.erase(instance.id) != 0)
|
|
++stats.reused;
|
|
else
|
|
++stats.created;
|
|
}
|
|
stats.removed = int(oldIds.size());
|
|
batches[batchId] = instances;
|
|
revisions[batchId] = revisions[batchId] + 1;
|
|
latest[batchId] = stats;
|
|
return true;
|
|
}
|
|
eve::Result<uint64_t> replaceBatch(const std::string& batchId, uint64_t targetRevision,
|
|
const std::vector<eve::ProcgenInstanceDesc>& instances) override {
|
|
if (targetRevision == 0 || batchRevision(batchId) >= targetRevision)
|
|
return eve::Result<uint64_t>::failure(
|
|
eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "mock snapshot revision is stale"));
|
|
if (!applyBatch(batchId, instances))
|
|
return eve::Result<uint64_t>::failure(
|
|
eve::Diagnostic::error(eve::DiagnosticCode::Failed, "mock snapshot commit failed"));
|
|
++replaceCalls;
|
|
revisions[batchId] = targetRevision;
|
|
return eve::Result<uint64_t>::success(targetRevision);
|
|
}
|
|
eve::Result<uint64_t> replaceBatches(const std::vector<eve::ProcgenBatchSnapshot>& snapshots) override {
|
|
if (failBatchTransaction)
|
|
return eve::Result<uint64_t>::failure(
|
|
eve::Diagnostic::error(eve::DiagnosticCode::Failed, "injected batch transaction failure"));
|
|
auto nextBatches = batches;
|
|
auto nextRevisions = revisions;
|
|
for (const auto& snapshot : snapshots) {
|
|
if (snapshot.batchId.empty() || snapshot.targetRevision == 0 ||
|
|
nextRevisions[snapshot.batchId] >= snapshot.targetRevision)
|
|
return eve::Result<uint64_t>::failure(
|
|
eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "mock batch transaction is stale"));
|
|
nextBatches[snapshot.batchId] = snapshot.instances;
|
|
nextRevisions[snapshot.batchId] = snapshot.targetRevision;
|
|
}
|
|
batches.swap(nextBatches);
|
|
revisions.swap(nextRevisions);
|
|
return eve::Result<uint64_t>::success(static_cast<uint64_t>(snapshots.size()));
|
|
}
|
|
eve::Result<uint64_t> applyDelta(const std::string& batchId, const eve::ProcgenInstanceDelta& delta) override {
|
|
const auto current = revisions.find(batchId);
|
|
if (current == revisions.end() || current->second != delta.baseRevision)
|
|
return eve::Result<uint64_t>::failure(
|
|
eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "mock delta revision is stale"));
|
|
lastDeltaBatch = batchId;
|
|
lastDelta = delta;
|
|
++deltaCalls;
|
|
revisions[batchId] = delta.targetRevision;
|
|
return eve::Result<uint64_t>::success(delta.targetRevision);
|
|
}
|
|
uint64_t batchRevision(const std::string& batchId) const override {
|
|
const auto found = revisions.find(batchId);
|
|
return found == revisions.end() ? 0 : found->second;
|
|
}
|
|
bool removeBatch(const std::string& batchId) override {
|
|
const auto found = batches.find(batchId);
|
|
if (found == batches.end()) return false;
|
|
latest[batchId] = {0, 0, int(found->second.size())};
|
|
batches.erase(found);
|
|
revisions.erase(batchId);
|
|
return true;
|
|
}
|
|
eve::Result<uint64_t> removeBatches(const std::vector<std::string>& batchIds) override {
|
|
return removeBatchesCoordinated(batchIds, [] { return eve::Result<void>::success(); });
|
|
}
|
|
eve::Result<uint64_t> removeBatchesCoordinated(
|
|
const std::vector<std::string>& batchIds,
|
|
const std::function<eve::Result<void>()>& commitPreparedOwner) override {
|
|
if (failBatchTransaction)
|
|
return eve::Result<uint64_t>::failure(
|
|
eve::Diagnostic::error(eve::DiagnosticCode::Failed, "injected batch transaction failure"));
|
|
auto nextBatches = batches;
|
|
auto nextRevisions = revisions;
|
|
for (const auto& batchId : batchIds) {
|
|
if (nextBatches.erase(batchId) != 1)
|
|
return eve::Result<uint64_t>::failure(
|
|
eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "mock batch is unpublished"));
|
|
nextRevisions.erase(batchId);
|
|
}
|
|
if (!commitPreparedOwner)
|
|
return eve::Result<uint64_t>::failure(
|
|
eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "mock commit is missing"));
|
|
auto ownerCommit = commitPreparedOwner();
|
|
if (!ownerCommit.ok()) return eve::Result<uint64_t>::failure(ownerCommit.status());
|
|
batches.swap(nextBatches);
|
|
revisions.swap(nextRevisions);
|
|
return eve::Result<uint64_t>::success(static_cast<uint64_t>(batchIds.size()));
|
|
}
|
|
int instanceCount(const std::string& batchId) const override {
|
|
const auto found = batches.find(batchId);
|
|
return found == batches.end() ? 0 : int(found->second.size());
|
|
}
|
|
int lastCreatedCount(const std::string& batchId) const override {
|
|
const auto found = latest.find(batchId);
|
|
return found == latest.end() ? 0 : found->second.created;
|
|
}
|
|
int lastReusedCount(const std::string& batchId) const override {
|
|
const auto found = latest.find(batchId);
|
|
return found == latest.end() ? 0 : found->second.reused;
|
|
}
|
|
int lastRemovedCount(const std::string& batchId) const override {
|
|
const auto found = latest.find(batchId);
|
|
return found == latest.end() ? 0 : found->second.removed;
|
|
}
|
|
|
|
struct Stats {
|
|
int created = 0;
|
|
int reused = 0;
|
|
int removed = 0;
|
|
};
|
|
std::unordered_map<std::string, std::vector<eve::ProcgenInstanceDesc>> batches;
|
|
std::unordered_map<std::string, uint64_t> revisions;
|
|
std::unordered_map<std::string, Stats> latest;
|
|
std::string lastDeltaBatch;
|
|
eve::ProcgenInstanceDelta lastDelta;
|
|
int replaceCalls = 0;
|
|
int deltaCalls = 0;
|
|
bool failBatchTransaction = false;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("procgen.sceneSink.publishesStableAttributedInstances") {
|
|
eve::cap::detail::clearAllRaw();
|
|
MockProcgenSceneSink sink;
|
|
eve::cap::provide<eve::IProcgenSceneSink>(&sink);
|
|
|
|
Procgen proc;
|
|
auto pointsResult = proc.newPointSetHandle();
|
|
REQUIRE(pointsResult.ok());
|
|
const auto pointsHandle = std::move(pointsResult).takeValue();
|
|
auto pointsView = proc.resolvePointSet(pointsHandle);
|
|
REQUIRE(pointsView.isBound());
|
|
const int tree = pointsView->add(1.f, 2.f, 3.f);
|
|
REQUIRE(pointsView->trySetPointId(tree, 1001).ok());
|
|
pointsView->setPointSeed(tree, 77);
|
|
pointsView->setYaw(tree, 30.f);
|
|
pointsView->setScale(tree, 2.f, 3.f, 4.f);
|
|
pointsView->setStringAttribute(tree, "asset", "oak");
|
|
const int rock = pointsView->add(8.f, 0.f, 9.f);
|
|
REQUIRE(pointsView->trySetPointId(rock, 1002).ok());
|
|
pointsView->setPointSeed(rock, 88);
|
|
|
|
auto publishedResult = proc.publishInstances("forest/main", pointsHandle, "asset", "granite");
|
|
REQUIRE(publishedResult.ok());
|
|
CHECK_EQ(proc.getPublishedInstanceCount("forest/main"), 2);
|
|
CHECK_EQ(proc.getPublishedCreatedCount("forest/main"), 2);
|
|
CHECK_EQ(proc.getPublishedReusedCount("forest/main"), 0);
|
|
CHECK_EQ(proc.getPublishedRemovedCount("forest/main"), 0);
|
|
const auto& published = sink.batches.at("forest/main");
|
|
CHECK_EQ(published[0].id, std::string("pcg-id-1001"));
|
|
CHECK_EQ(published[0].sourcePointId, uint64_t(1001));
|
|
CHECK_EQ(published[0].asset, std::string("oak"));
|
|
CHECK_EQ(published[0].x, 1.f);
|
|
CHECK_EQ(published[0].yaw, 30.f);
|
|
CHECK_EQ(published[0].scaleZ, 4.f);
|
|
CHECK_EQ(published[1].asset, std::string("granite"));
|
|
CHECK_EQ(published[1].id, std::string("pcg-id-1002"));
|
|
CHECK_EQ(published[1].sourcePointId, uint64_t(1002));
|
|
|
|
auto reorderedResult = proc.newPointSetHandle();
|
|
REQUIRE(reorderedResult.ok());
|
|
const auto reorderedHandle = std::move(reorderedResult).takeValue();
|
|
auto reorderedView = proc.resolvePointSet(reorderedHandle);
|
|
REQUIRE(reorderedView.isBound());
|
|
reorderedView->add(-1.f, 0.f, 0.f);
|
|
reorderedView->setPointSeed(0, 99);
|
|
std::move(reorderedView->appendPointFrom(*pointsView, 0)).expect("scene sink test point copy");
|
|
std::move(reorderedView->appendPointFrom(*pointsView, 1)).expect("scene sink test point copy");
|
|
auto reorderedPublishResult = proc.publishInstances("forest/main", reorderedHandle, "asset", "granite");
|
|
REQUIRE(reorderedPublishResult.ok());
|
|
CHECK_EQ(proc.getPublishedCreatedCount("forest/main"), 1);
|
|
CHECK_EQ(proc.getPublishedReusedCount("forest/main"), 2);
|
|
CHECK_EQ(proc.getPublishedRemovedCount("forest/main"), 0);
|
|
const auto& reconciled = sink.batches.at("forest/main");
|
|
CHECK_EQ(reconciled[1].id, std::string("pcg-id-1001"));
|
|
CHECK_EQ(reconciled[2].id, std::string("pcg-id-1002"));
|
|
|
|
reorderedView->setStringAttribute(0, "instanceId", "hero-tree");
|
|
reorderedView->setStringAttribute(1, "instanceId", "hero-tree");
|
|
auto duplicateResult = proc.publishInstances("forest/main", reorderedHandle, "asset", "granite");
|
|
REQUIRE(!duplicateResult.ok());
|
|
CHECK(duplicateResult.status().describe().find("duplicate instance id") != std::string::npos);
|
|
CHECK_EQ(proc.getPublishedInstanceCount("forest/main"), 3);
|
|
|
|
auto removeResult = proc.removeInstances("forest/main");
|
|
REQUIRE(removeResult.ok());
|
|
CHECK_EQ(proc.getPublishedInstanceCount("forest/main"), 0);
|
|
CHECK_EQ(proc.getPublishedRemovedCount("forest/main"), 3);
|
|
REQUIRE(proc.releasePointSet(reorderedHandle).ok());
|
|
REQUIRE(proc.releasePointSet(pointsHandle).ok());
|
|
eve::cap::revoke<eve::IProcgenSceneSink>(&sink);
|
|
}
|
|
|
|
TEST_CASE("procgen.sceneSink.projectsRuntimePointDeltaByStableIdentity") {
|
|
eve::cap::detail::clearAllRaw();
|
|
MockProcgenSceneSink sink;
|
|
eve::cap::provide<eve::IProcgenSceneSink>(&sink);
|
|
|
|
Procgen proc;
|
|
auto pointsResult = proc.newPointSetHandle();
|
|
REQUIRE(pointsResult.ok());
|
|
const auto pointsHandle = std::move(pointsResult).takeValue();
|
|
auto pointsView = proc.resolvePointSet(pointsHandle);
|
|
REQUIRE(pointsView.isBound());
|
|
const int tree = pointsView->add(1.f, 0.f, 2.f);
|
|
REQUIRE(pointsView->trySetPointId(tree, 2001).ok());
|
|
pointsView->setStringAttribute(tree, "instanceId", "tree-custom");
|
|
pointsView->setStringAttribute(tree, "asset", "oak");
|
|
const int rock = pointsView->add(3.f, 0.f, 4.f);
|
|
REQUIRE(pointsView->trySetPointId(rock, 2002).ok());
|
|
|
|
RuntimeGeneration runtime(77);
|
|
REQUIRE(runtime.addLevel(10.f, 6.f, 2.f) >= 0);
|
|
runtime.updateSource(1.f, 1.f, 1.f, 0.f);
|
|
std::unique_ptr<ProcgenCellRequest> request(runtime.nextGenerate());
|
|
REQUIRE(bool(request));
|
|
auto initialPublish = proc.publishCellInstances("world", *request, pointsHandle, "asset", "stone");
|
|
REQUIRE(initialPublish.ok());
|
|
|
|
PointSet target;
|
|
const int targetTree = target.appendPointFrom(*pointsView, std::size_t(tree)).expect("scene delta target tree");
|
|
target.setPosition(targetTree, 9.f, 1.f, 2.f);
|
|
target.setStringAttribute(targetTree, "instanceId", "tree-renamed");
|
|
ProcgenPoint flower;
|
|
flower.id = 2003;
|
|
flower.seed = 33;
|
|
const int flowerIndex = target.appendPoint(flower);
|
|
target.setStringAttribute(flowerIndex, "asset", "lily");
|
|
auto delta = diffPointSets(*pointsView, target);
|
|
REQUIRE(delta.ok());
|
|
|
|
auto applied = proc.publishCellInstanceDelta("world", *request, delta.value(), 2, "asset", "stone");
|
|
REQUIRE(applied.ok());
|
|
CHECK_EQ(applied.value(), uint64_t(2));
|
|
CHECK_EQ(sink.lastDeltaBatch, std::string("world/L0/0/0"));
|
|
CHECK_EQ(sink.lastDelta.baseRevision, uint64_t(1));
|
|
CHECK_EQ(sink.lastDelta.targetRevision, uint64_t(2));
|
|
REQUIRE_EQ(sink.lastDelta.updated.size(), std::size_t(1));
|
|
CHECK_EQ(sink.lastDelta.updated[0].sourcePointId, uint64_t(2001));
|
|
CHECK_EQ(sink.lastDelta.updated[0].id, std::string("tree-renamed"));
|
|
CHECK_EQ(sink.lastDelta.updated[0].x, 9.f);
|
|
REQUIRE_EQ(sink.lastDelta.added.size(), std::size_t(1));
|
|
CHECK_EQ(sink.lastDelta.added[0].sourcePointId, uint64_t(2003));
|
|
CHECK_EQ(sink.lastDelta.added[0].asset, std::string("lily"));
|
|
REQUIRE_EQ(sink.lastDelta.removedPointIds.size(), std::size_t(1));
|
|
CHECK_EQ(sink.lastDelta.removedPointIds[0], uint64_t(2002));
|
|
CHECK_EQ(sink.lastDelta.targetPointOrder, delta.value().targetOrder);
|
|
|
|
auto recoveryResult = proc.newPointSetHandle();
|
|
REQUIRE(recoveryResult.ok());
|
|
const auto recoveryHandle = std::move(recoveryResult).takeValue();
|
|
auto recoveryView = proc.resolvePointSet(recoveryHandle);
|
|
REQUIRE(recoveryView.isBound());
|
|
for (std::size_t pointIndex = 0; pointIndex < target.points().size(); ++pointIndex)
|
|
recoveryView->appendPointFrom(target, pointIndex).expect("scene recovery snapshot");
|
|
auto recovered = proc.publishCellSnapshot("world", *request, recoveryHandle, 5, "asset", "stone");
|
|
REQUIRE(recovered.ok());
|
|
CHECK_EQ(recovered.value(), uint64_t(5));
|
|
CHECK_EQ(sink.batchRevision("world/L0/0/0"), uint64_t(5));
|
|
REQUIRE_EQ(sink.batches.at("world/L0/0/0").size(), std::size_t(2));
|
|
CHECK_EQ(sink.batches.at("world/L0/0/0")[0].id, std::string("tree-renamed"));
|
|
CHECK_EQ(sink.batches.at("world/L0/0/0")[1].sourcePointId, uint64_t(2003));
|
|
auto staleRecovery = proc.publishCellSnapshot("world", *request, recoveryHandle, 4, "asset", "stone");
|
|
CHECK(!staleRecovery.ok());
|
|
CHECK_EQ(sink.batchRevision("world/L0/0/0"), uint64_t(5));
|
|
|
|
auto invalidResult = proc.newPointSetHandle();
|
|
REQUIRE(invalidResult.ok());
|
|
const auto invalidHandle = std::move(invalidResult).takeValue();
|
|
auto invalidView = proc.resolvePointSet(invalidHandle);
|
|
REQUIRE(invalidView.isBound());
|
|
invalidView->add(0.f, 0.f, 0.f);
|
|
auto invalidSnapshot = proc.publishCellSnapshot("world", *request, invalidHandle, 6, "asset", "stone");
|
|
CHECK(!invalidSnapshot.ok());
|
|
CHECK_EQ(sink.batchRevision("world/L0/0/0"), uint64_t(5));
|
|
|
|
REQUIRE(proc.releasePointSet(invalidHandle).ok());
|
|
REQUIRE(proc.releasePointSet(recoveryHandle).ok());
|
|
REQUIRE(proc.releasePointSet(pointsHandle).ok());
|
|
eve::cap::revoke<eve::IProcgenSceneSink>(&sink);
|
|
}
|
|
|
|
TEST_CASE("procgen.sceneSink.synchronizesRuntimeRevisionWithDeltaAndSnapshotRecovery") {
|
|
eve::cap::detail::clearAllRaw();
|
|
MockProcgenSceneSink sink;
|
|
eve::cap::provide<eve::IProcgenSceneSink>(&sink);
|
|
|
|
Procgen proc;
|
|
RuntimeGeneration runtime(91);
|
|
REQUIRE(runtime.addLevel(10.f, 6.f, 2.f) >= 0);
|
|
runtime.updateSource(1.f, 1.f, 1.f, 0.f);
|
|
std::unique_ptr<ProcgenCellRequest> request(runtime.nextGenerate());
|
|
REQUIRE(bool(request));
|
|
|
|
PointSet initial;
|
|
const int tree = initial.add(1.f, 0.f, 2.f);
|
|
REQUIRE(initial.trySetPointId(tree, 3001).ok());
|
|
initial.setStringAttribute(tree, "asset", "oak");
|
|
REQUIRE(runtime.completeGeneration(request.get(), &initial));
|
|
|
|
auto first = proc.synchronizeCellInstances("stream", runtime, *request, "asset", "stone");
|
|
REQUIRE(first.ok());
|
|
CHECK_EQ(first.value(), uint64_t(1));
|
|
CHECK_EQ(sink.replaceCalls, 1);
|
|
CHECK_EQ(sink.deltaCalls, 0);
|
|
auto unchanged = proc.synchronizeCellInstances("stream", runtime, *request, "asset", "stone");
|
|
REQUIRE(unchanged.ok());
|
|
CHECK_EQ(sink.replaceCalls, 1);
|
|
|
|
PointSet revisionTwo = initial;
|
|
revisionTwo.setPosition(0, 4.f, 0.f, 2.f);
|
|
auto updated = runtime.applyCellUpdate(0, 0, 0, 1, revisionTwo);
|
|
REQUIRE(updated.ok());
|
|
auto incremental = proc.synchronizeCellInstances("stream", runtime, *request, "asset", "stone");
|
|
REQUIRE(incremental.ok());
|
|
CHECK_EQ(incremental.value(), uint64_t(2));
|
|
CHECK_EQ(sink.deltaCalls, 1);
|
|
CHECK_EQ(sink.replaceCalls, 1);
|
|
|
|
PointSet revisionThree = revisionTwo;
|
|
revisionThree.setPosition(0, 6.f, 0.f, 2.f);
|
|
REQUIRE(runtime.applyCellUpdate(0, 0, 0, 2, revisionThree).ok());
|
|
PointSet revisionFour = revisionThree;
|
|
revisionFour.setPosition(0, 8.f, 0.f, 2.f);
|
|
REQUIRE(runtime.applyCellUpdate(0, 0, 0, 3, revisionFour).ok());
|
|
auto recovered = proc.synchronizeCellInstances("stream", runtime, *request, "asset", "stone");
|
|
REQUIRE(recovered.ok());
|
|
CHECK_EQ(recovered.value(), uint64_t(4));
|
|
CHECK_EQ(sink.replaceCalls, 2);
|
|
CHECK_EQ(sink.deltaCalls, 1);
|
|
CHECK_EQ(sink.batches.at("stream/L0/0/0")[0].x, 8.f);
|
|
|
|
sink.revisions["stream/L0/0/0"] = 5;
|
|
auto sceneAhead = proc.synchronizeCellInstances("stream", runtime, *request, "asset", "stone");
|
|
CHECK(!sceneAhead.ok());
|
|
CHECK_EQ(sink.replaceCalls, 2);
|
|
CHECK_EQ(sink.deltaCalls, 1);
|
|
|
|
eve::cap::revoke<eve::IProcgenSceneSink>(&sink);
|
|
}
|
|
|
|
TEST_CASE("procgen.sceneSink.synchronizesSeveralRuntimeCellsAtomically") {
|
|
eve::cap::detail::clearAllRaw();
|
|
MockProcgenSceneSink sink;
|
|
eve::cap::provide<eve::IProcgenSceneSink>(&sink);
|
|
|
|
Procgen proc;
|
|
RuntimeGeneration firstRuntime(301);
|
|
RuntimeGeneration secondRuntime(302);
|
|
REQUIRE(firstRuntime.addLevel(10.f, 6.f, 2.f) >= 0);
|
|
REQUIRE(secondRuntime.addLevel(10.f, 6.f, 2.f) >= 0);
|
|
firstRuntime.updateSource(1.f, 1.f, 1.f, 0.f);
|
|
secondRuntime.updateSource(21.f, 1.f, 1.f, 0.f);
|
|
std::unique_ptr<ProcgenCellRequest> firstRequest(firstRuntime.nextGenerate());
|
|
std::unique_ptr<ProcgenCellRequest> secondRequest(secondRuntime.nextGenerate());
|
|
REQUIRE(bool(firstRequest));
|
|
REQUIRE(bool(secondRequest));
|
|
|
|
PointSet firstPoints;
|
|
const int firstPoint = firstPoints.add(1.f, 0.f, 1.f);
|
|
REQUIRE(firstPoints.trySetPointId(firstPoint, 5001).ok());
|
|
PointSet secondPoints;
|
|
const int secondPoint = secondPoints.add(21.f, 0.f, 1.f);
|
|
REQUIRE(secondPoints.trySetPointId(secondPoint, 5002).ok());
|
|
REQUIRE(firstRuntime.completeGeneration(firstRequest.get(), &firstPoints));
|
|
REQUIRE(secondRuntime.completeGeneration(secondRequest.get(), &secondPoints));
|
|
|
|
std::vector<const RuntimeGeneration*> runtimes{&firstRuntime, &secondRuntime};
|
|
std::vector<const ProcgenCellRequest*> requests{firstRequest.get(), secondRequest.get()};
|
|
auto committed = proc.synchronizeCellInstancesAtomic("world", runtimes, requests, "asset", "stone");
|
|
REQUIRE(committed.ok());
|
|
CHECK_EQ(committed.value(), uint64_t(2));
|
|
CHECK_EQ(sink.revisions.at("world/L0/0/0"), uint64_t(1));
|
|
CHECK_EQ(sink.revisions.at("world/L0/2/0"), uint64_t(1));
|
|
|
|
PointSet firstRevisionTwo = firstPoints;
|
|
firstRevisionTwo.setPosition(0, 5.f, 0.f, 1.f);
|
|
PointSet secondRevisionTwo = secondPoints;
|
|
secondRevisionTwo.setPosition(0, 25.f, 0.f, 1.f);
|
|
REQUIRE(firstRuntime.applyCellUpdate(0, 0, 0, 1, firstRevisionTwo).ok());
|
|
REQUIRE(secondRuntime.applyCellUpdate(0, 2, 0, 1, secondRevisionTwo).ok());
|
|
sink.failBatchTransaction = true;
|
|
auto rejected = proc.synchronizeCellInstancesAtomic("world", runtimes, requests, "asset", "stone");
|
|
REQUIRE(!rejected.ok());
|
|
CHECK_EQ(sink.revisions.at("world/L0/0/0"), uint64_t(1));
|
|
CHECK_EQ(sink.revisions.at("world/L0/2/0"), uint64_t(1));
|
|
CHECK_EQ(sink.batches.at("world/L0/0/0")[0].x, 1.f);
|
|
CHECK_EQ(sink.batches.at("world/L0/2/0")[0].x, 21.f);
|
|
|
|
sink.failBatchTransaction = false;
|
|
auto recovered = proc.synchronizeCellInstancesAtomic("world", runtimes, requests, "asset", "stone");
|
|
REQUIRE(recovered.ok());
|
|
CHECK_EQ(sink.batches.at("world/L0/0/0")[0].x, 5.f);
|
|
CHECK_EQ(sink.batches.at("world/L0/2/0")[0].x, 25.f);
|
|
|
|
std::vector<const ProcgenCellRequest*> duplicateRequests{firstRequest.get(), firstRequest.get()};
|
|
auto duplicateRemoval = proc.removeCellInstancesAtomic("world", duplicateRequests);
|
|
REQUIRE(!duplicateRemoval.ok());
|
|
CHECK_EQ(sink.batches.size(), std::size_t(2));
|
|
|
|
sink.failBatchTransaction = true;
|
|
auto removalRejected = proc.removeCellInstancesAtomic("world", requests);
|
|
REQUIRE(!removalRejected.ok());
|
|
CHECK_EQ(sink.batches.size(), std::size_t(2));
|
|
sink.failBatchTransaction = false;
|
|
auto removed = proc.removeCellInstancesAtomic("world", requests);
|
|
REQUIRE(removed.ok());
|
|
CHECK_EQ(removed.value(), uint64_t(2));
|
|
CHECK(sink.batches.empty());
|
|
eve::cap::revoke<eve::IProcgenSceneSink>(&sink);
|
|
}
|
|
|
|
TEST_CASE("procgen.sceneSink.completesSceneAndRuntimeCleanupAtomically") {
|
|
eve::cap::detail::clearAllRaw();
|
|
MockProcgenSceneSink sink;
|
|
eve::cap::provide<eve::IProcgenSceneSink>(&sink);
|
|
Procgen proc;
|
|
|
|
RuntimeGeneration firstRuntime(31);
|
|
RuntimeGeneration secondRuntime(32);
|
|
REQUIRE(firstRuntime.addLevel(10.f, 6.f, 1.5f) >= 0);
|
|
REQUIRE(secondRuntime.addLevel(10.f, 6.f, 1.5f) >= 0);
|
|
firstRuntime.updateSource(1.f, 1.f, 1.f, 0.f);
|
|
secondRuntime.updateSource(21.f, 1.f, 1.f, 0.f);
|
|
auto firstGenerated = std::unique_ptr<ProcgenCellRequest>(firstRuntime.nextGenerate());
|
|
auto secondGenerated = std::unique_ptr<ProcgenCellRequest>(secondRuntime.nextGenerate());
|
|
REQUIRE(bool(firstGenerated));
|
|
REQUIRE(bool(secondGenerated));
|
|
PointSet firstPoints;
|
|
PointSet secondPoints;
|
|
const int firstPoint = firstPoints.add(1.f, 0.f, 1.f);
|
|
const int secondPoint = secondPoints.add(21.f, 0.f, 1.f);
|
|
REQUIRE(firstPoints.trySetPointId(firstPoint, 6001).ok());
|
|
REQUIRE(secondPoints.trySetPointId(secondPoint, 6002).ok());
|
|
REQUIRE(firstRuntime.completeGeneration(firstGenerated.get(), &firstPoints));
|
|
REQUIRE(secondRuntime.completeGeneration(secondGenerated.get(), &secondPoints));
|
|
std::vector<const RuntimeGeneration*> publishRuntimes{&firstRuntime, &secondRuntime};
|
|
std::vector<const ProcgenCellRequest*> publishRequests{firstGenerated.get(), secondGenerated.get()};
|
|
REQUIRE(proc.synchronizeCellInstancesAtomic("cleanup", publishRuntimes, publishRequests, "asset", "stone").ok());
|
|
|
|
firstRuntime.updateSource(1000.f, 1000.f, 1.f, 0.f);
|
|
secondRuntime.updateSource(1000.f, 1000.f, 1.f, 0.f);
|
|
auto firstCleanup = std::unique_ptr<ProcgenCellRequest>(firstRuntime.nextCleanup());
|
|
auto secondCleanup = std::unique_ptr<ProcgenCellRequest>(secondRuntime.nextCleanup());
|
|
REQUIRE(bool(firstCleanup));
|
|
REQUIRE(bool(secondCleanup));
|
|
std::vector<RuntimeGeneration*> cleanupRuntimes{&firstRuntime, &secondRuntime};
|
|
std::vector<const ProcgenCellRequest*> cleanupRequests{firstCleanup.get(), secondCleanup.get()};
|
|
|
|
sink.failBatchTransaction = true;
|
|
auto sceneRejected = proc.completeCellCleanupAtomic("cleanup", cleanupRuntimes, cleanupRequests);
|
|
REQUIRE(!sceneRejected.ok());
|
|
CHECK(firstRuntime.isRequestCurrent(firstCleanup.get()));
|
|
CHECK(secondRuntime.isRequestCurrent(secondCleanup.get()));
|
|
CHECK_EQ(sink.batches.size(), std::size_t(2));
|
|
sink.failBatchTransaction = false;
|
|
|
|
firstRuntime.updateSource(1.f, 1.f, 1.f, 0.f);
|
|
firstRuntime.updateSource(1000.f, 1000.f, 1.f, 0.f);
|
|
auto staleRejected = proc.completeCellCleanupAtomic("cleanup", cleanupRuntimes, cleanupRequests);
|
|
REQUIRE(!staleRejected.ok());
|
|
CHECK(secondRuntime.isRequestCurrent(secondCleanup.get()));
|
|
CHECK_EQ(sink.batches.size(), std::size_t(2));
|
|
firstCleanup = std::unique_ptr<ProcgenCellRequest>(firstRuntime.nextCleanup());
|
|
REQUIRE(bool(firstCleanup));
|
|
cleanupRequests[0] = firstCleanup.get();
|
|
|
|
auto completed = proc.completeCellCleanupAtomic("cleanup", cleanupRuntimes, cleanupRequests);
|
|
REQUIRE(completed.ok());
|
|
CHECK_EQ(completed.value(), uint64_t(2));
|
|
CHECK(!firstRuntime.isRequestCurrent(firstCleanup.get()));
|
|
CHECK(!secondRuntime.isRequestCurrent(secondCleanup.get()));
|
|
CHECK(sink.batches.empty());
|
|
CHECK_EQ(firstRuntime.getResidentPointCount(), 0);
|
|
CHECK_EQ(secondRuntime.getResidentPointCount(), 0);
|
|
eve::cap::revoke<eve::IProcgenSceneSink>(&sink);
|
|
}
|
|
|
|
TEST_CASE("procgen.sceneSink.squirrelCompletesCoordinatedCleanup") {
|
|
eve::cap::detail::clearAllRaw();
|
|
MockProcgenSceneSink sink;
|
|
eve::cap::provide<eve::IProcgenSceneSink>(&sink);
|
|
ssq::VM vm(1024, ssq::Libs::ALL);
|
|
eve::ModuleManager::expose(vm);
|
|
vm.run(vm.compileSource(R"(
|
|
result <- "fail";
|
|
local procgen = eve.Procgen();
|
|
local runtimeResult = procgen.newRuntimeGeneration(33);
|
|
local pointsResult = procgen.sampleGrid(1, 1, 1.0, 34, 0.0);
|
|
if (runtimeResult.ok && pointsResult.ok) {
|
|
local runtime = runtimeResult.value;
|
|
runtime.addLevel(10.0, 6.0, 1.5);
|
|
runtime.updateSource(1.0, 1.0, 1.0, 0.0);
|
|
local generated = runtime.nextGenerate();
|
|
if (generated != null && runtime.completeGeneration(generated, pointsResult.value)) {
|
|
local published = procgen.synchronizeCellInstances(
|
|
"script-cleanup", runtime, generated, "asset", "stone");
|
|
runtime.updateSource(1000.0, 1000.0, 1.0, 0.0);
|
|
local cleanup = runtime.nextCleanup();
|
|
local completed = procgen.completeCellCleanupAtomic(
|
|
"script-cleanup", [runtime], [cleanup]);
|
|
if (published.ok && completed.ok && completed.value == "1" &&
|
|
!runtime.isRequestCurrent(cleanup))
|
|
result = "ok";
|
|
}
|
|
}
|
|
)"));
|
|
CHECK_EQ(vm.find("result").toString(), std::string("ok"));
|
|
CHECK(sink.batches.empty());
|
|
eve::cap::revoke<eve::IProcgenSceneSink>(&sink);
|
|
}
|
|
|
|
TEST_CASE("procgen.sceneSink.reportsMissingProvider") {
|
|
eve::cap::detail::clearAllRaw();
|
|
Procgen proc;
|
|
auto pointsResult = proc.newPointSetHandle();
|
|
REQUIRE(pointsResult.ok());
|
|
const auto pointsHandle = std::move(pointsResult).takeValue();
|
|
auto pointsView = proc.resolvePointSet(pointsHandle);
|
|
REQUIRE(pointsView.isBound());
|
|
pointsView->add(0.f, 0.f, 0.f);
|
|
|
|
auto publishResult = proc.publishInstances("missing", pointsHandle, "asset", "tree");
|
|
REQUIRE(!publishResult.ok());
|
|
CHECK(publishResult.status().describe().find("unavailable") != std::string::npos);
|
|
|
|
RuntimeGeneration runtime(12);
|
|
REQUIRE(runtime.addLevel(10.f, 6.f, 2.f) >= 0);
|
|
runtime.updateSource(1.f, 1.f, 1.f, 0.f);
|
|
std::unique_ptr<ProcgenCellRequest> request(runtime.nextGenerate());
|
|
REQUIRE(bool(request));
|
|
PointSet generated;
|
|
const int point = generated.add(0.f, 0.f, 0.f);
|
|
REQUIRE(generated.trySetPointId(point, 4001).ok());
|
|
REQUIRE(runtime.completeGeneration(request.get(), &generated));
|
|
auto synchronizeResult = proc.synchronizeCellInstances("missing", runtime, *request, "asset", "tree");
|
|
REQUIRE(!synchronizeResult.ok());
|
|
CHECK(synchronizeResult.status().describe().find("unavailable") != std::string::npos);
|
|
std::vector<const ProcgenCellRequest*> removalRequests{request.get()};
|
|
auto removeResult = proc.removeCellInstancesAtomic("missing", removalRequests);
|
|
REQUIRE(!removeResult.ok());
|
|
CHECK(removeResult.status().describe().find("unavailable") != std::string::npos);
|
|
REQUIRE(proc.releasePointSet(pointsHandle).ok());
|
|
}
|