/** * @file script_ownership.cpp * @brief Contract tests for Value/Owned/Borrowed script object semantics. */ #include "authority/Authority.h" #include "common/SquirrelOwnership.h" #include "decision/Decision.h" #include "effects/Effects.h" #include "orders/CommandQueue.h" #include "procgen/Procgen.h" #include "production/Production.h" #include "statepatch/StatePatch.h" #include "zeroerr/unittest.h" #include #include #include TEST_CASE("scriptOwnership.commonSemanticsAndRegistryStaleGeneration") { struct Tag {}; struct Item { int value = 7; }; CHECK_EQ(std::string(eve::script::objectSemanticName(eve::script::ObjectSemantic::Value)), std::string("value")); CHECK_EQ(std::string(eve::script::objectSemanticName(eve::script::ObjectSemantic::Owned)), std::string("owned")); CHECK_EQ(std::string(eve::script::objectSemanticName(eve::script::ObjectSemantic::Borrowed)), std::string("borrowed")); eve::script::RuntimeObjectRegistry registry; auto created = registry.emplace(std::make_unique()); REQUIRE(created.ok()); const auto reference = std::move(created).takeValue(); auto borrowed = registry.resolve(reference); REQUIRE(borrowed.isBound()); CHECK_EQ(borrowed->value, 7); CHECK(!registry.isStale(reference)); auto released = registry.erase(reference); REQUIRE(released.ok()); CHECK(registry.isStale(reference)); CHECK(!registry.resolve(reference).isBound()); auto replacement = registry.emplace(std::make_unique()); REQUIRE(replacement.ok()); const auto replacementReference = std::move(replacement).takeValue(); CHECK(replacementReference.handle.index() == reference.handle.index()); CHECK(replacementReference.handle.generation() != reference.handle.generation()); CHECK(registry.isStale(reference)); CHECK(!registry.isStale(replacementReference)); const auto oldEpoch = registry.ownerEpoch(); eve::script::RuntimeHandleRef unloadedReference = replacementReference; registry.clear(); CHECK(registry.isStale(unloadedReference)); CHECK(!registry.resolve(unloadedReference).isBound()); { eve::script::RuntimeObjectRegistry reloadedRegistry; CHECK(reloadedRegistry.ownerEpoch() != oldEpoch); CHECK(reloadedRegistry.isStale(unloadedReference)); } } TEST_CASE("scriptOwnership.ordersAndEffectsOwnedBinding") { ssq::VM vm(1024, ssq::Libs::ALL); eve::ModuleManager::expose(vm); vm.run(vm.compileSource(R"( result <- "fail"; local orders = eve.Orders(); local oq = orders.newQueueOwned(); local effects = eve.Effects(); local ec = effects.newContainer(); if (oq.ok && ec.ok && oq.value.ownership() == "owned" && ec.value.ownership() == "owned" && !oq.value.isStale() && !ec.value.isStale()) { local order = oq.value.append("ownership_probe", 1, 0.0); local effect = ec.value.apply("subject", "probe", "test", 1, 0.0, "probe", "refresh"); local releasedOrder = oq.value.release(); local releasedEffect = ec.value.release(); if (order.ok && effect.ok && releasedOrder.ok && releasedEffect.ok && oq.value.isStale() && ec.value.isStale() && oq.value.release().code == "rejected" && ec.value.release().code == "rejected") result = "ok"; } )")); CHECK_EQ(vm.find("result").toString(), std::string("ok")); } TEST_CASE("scriptOwnership.procgenAndStateModulesOwnedBindings") { ssq::VM vm(1024, ssq::Libs::ALL); eve::ModuleManager::expose(vm); vm.run(vm.compileSource(R"( result <- "fail"; local procgen = eve.Procgen(); local params = procgen.newParams(); local grid = procgen.newGrid(2, 3); local context = procgen.beginSystem("ownership_probe", 7); local production = eve.Production().newWorkQueue(); local authority = eve.Authority().newStore(); local statepatch = eve.StatePatch().newStore(); local decision = eve.Decision().newContext(); local pointSet = procgen.newPointSet(); local output = procgen.newOutput(); local cloud = procgen.newCloudField(); local batch = statepatch.ok ? statepatch.value.newBatch() : { ok = false }; if (params.ok && grid.ok && context.ok && production.ok && authority.ok && statepatch.ok && decision.ok && pointSet.ok && output.ok && cloud.ok && batch.ok && params.value.ownerEpoch() > 0 && pointSet.ownerEpoch > 0 && output.ownerEpoch > 0 && cloud.ownerEpoch > 0 && pointSet.value.empty() && output.value.getTarget() == "" && grid.value.getWidth() == 2 && grid.value.getHeight() == 3 && params.value.setSeed(42).ok && params.value.getSeed() == 42 && grid.value.fill(1).ok && grid.value.getCell(1, 2) == 1 && context.value.getName() == "ownership_probe" && production.value.enqueue("probe", "build", "unit", "{}", 1.0, 0).ok && authority.value.grant("actor", "scope", "cap", "probe", 1, 0.0).ok && statepatch.value.revision() == 0 && decision.value.setState("fsm", "idle").ok && decision.value.state("fsm") == "idle" && batch.value.set("actor", "probe", "true").ok) { local pReleased = params.value.release(); local gReleased = grid.value.release(); local cReleased = context.value.release(); local qReleased = production.value.release(); local aReleased = authority.value.release(); local bReleased = batch.value.release(); local sReleased = statepatch.value.release(); local dReleased = decision.value.release(); if (pReleased.ok && gReleased.ok && cReleased.ok && qReleased.ok && aReleased.ok && bReleased.ok && sReleased.ok && dReleased.ok && params.value.isStale() && grid.value.isStale() && context.value.isStale() && production.value.isStale() && authority.value.isStale() && statepatch.value.isStale() && batch.value.isStale() && decision.value.isStale()) result = "ok"; } )")); CHECK_EQ(vm.find("result").toString(), std::string("ok")); } TEST_CASE("scriptOwnership.procgenOwnedGridExposesAssetPlacement") { ssq::VM vm(1024, ssq::Libs::ALL); eve::ModuleManager::expose(vm); vm.run(vm.compileSource(R"( result <- "fail"; local procgen = eve.Procgen(); local paramsResult = procgen.newParams(); if (paramsResult.ok) { local params = paramsResult.value; params.setSize(32, 24); params.setSeed(23); params.setString("assets.light", "sconce_probe"); local generated = procgen.generate("level.roguelike", params); if (generated.ok) { local grid = generated.value; for (local i = 0; i < grid.getObjectCount(); ++i) { if (grid.getObjectType(i) == "light" && grid.getObjectAsset(i) == "sconce_probe" && grid.getObjectRotation(i) >= 0.0 && (grid.getObjectFlags(i) & 4) != 0) result = "ok"; } } } )")); CHECK_EQ(vm.find("result").toString(), std::string("ok")); }