192 lines
9.8 KiB
C++
192 lines
9.8 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include "card/CardDefinitionRuntime.h"
|
|
#include "common/definitions/DefinitionRuntime.h"
|
|
#include "definitions/Definitions.h"
|
|
#include "effects/EffectDefinitionRuntime.h"
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
namespace {
|
|
|
|
struct TypedState {
|
|
int value = 0;
|
|
std::string label;
|
|
};
|
|
|
|
eve::PersistentId testId() {
|
|
const auto parsed = eve::PersistentId::parse("11111111-1111-7111-8111-111111111111");
|
|
return *parsed;
|
|
}
|
|
|
|
eve::DefinitionRef testReference(const char* text) {
|
|
auto parsed = eve::DefinitionRef::parse(text);
|
|
if (!parsed.ok()) return {};
|
|
return std::move(parsed).takeValue();
|
|
}
|
|
|
|
eve::SnapshotHashProvider testHash() {
|
|
return [](std::string_view input) -> eve::Result<eve::ContentId> {
|
|
eve::ContentId::Bytes bytes{};
|
|
std::uint8_t value = 29;
|
|
for (std::size_t index = 0; index < bytes.size(); ++index) {
|
|
for (const unsigned char byte : input) value = static_cast<std::uint8_t>(value * 33u + byte);
|
|
bytes[index] = static_cast<std::uint8_t>(value + index);
|
|
}
|
|
return eve::Result<eve::ContentId>::success(eve::ContentId(bytes));
|
|
};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("definitionRuntime.policiesAndGenerationQualifiedStaleChecks") {
|
|
const auto reference = testReference("test:unit");
|
|
auto created = eve::definition::RuntimeInstance<TypedState>::create(testId(), reference, eve::Generation(1),
|
|
TypedState{7, "original"});
|
|
REQUIRE(created.ok());
|
|
auto runtime = std::move(created).takeValue();
|
|
|
|
auto wrongGeneration = runtime.checkDefinition(eve::definition::DefinitionHandle{reference, eve::Generation(2)});
|
|
CHECK(!wrongGeneration.ok());
|
|
CHECK(static_cast<int>(wrongGeneration.error()->code()) == static_cast<int>(eve::DiagnosticCode::StaleHandle));
|
|
|
|
auto kept = runtime.reload(eve::definition::DefinitionHandle{reference, eve::Generation(2)},
|
|
eve::definition::ReloadPolicy::KeepInstanceValues, TypedState{99, "new defaults"});
|
|
REQUIRE(kept.ok());
|
|
CHECK(static_cast<int>(kept.value().disposition) == static_cast<int>(eve::definition::ReloadDisposition::Kept));
|
|
CHECK_EQ(runtime.state().value, 7);
|
|
CHECK_EQ(runtime.identity().definitionGeneration.value(), uint64_t{2});
|
|
|
|
auto reapplied = runtime.reload(eve::definition::DefinitionHandle{reference, eve::Generation(3)},
|
|
eve::definition::ReloadPolicy::ReapplyDefaults, TypedState{42, "defaults"});
|
|
REQUIRE(reapplied.ok());
|
|
CHECK(static_cast<int>(reapplied.value().disposition) ==
|
|
static_cast<int>(eve::definition::ReloadDisposition::DefaultsReapplied));
|
|
CHECK_EQ(runtime.state().value, 42);
|
|
CHECK_EQ(runtime.state().label, std::string("defaults"));
|
|
|
|
auto rebuilt =
|
|
runtime.reload(eve::definition::DefinitionHandle{reference, eve::Generation(4)},
|
|
eve::definition::ReloadPolicy::RebuildInstance, TypedState{100, "ignored"},
|
|
[](const TypedState& oldState, const eve::definition::InstanceIdentity& identity,
|
|
const eve::definition::DefinitionHandle& handle) -> eve::Result<TypedState> {
|
|
if (!identity.isValid() || !handle.isValid())
|
|
return eve::Result<TypedState>::failure(eve::Diagnostic::error(
|
|
eve::DiagnosticCode::InvalidArgument, "test rebuild received invalid identity"));
|
|
return eve::Result<TypedState>::success(TypedState{oldState.value + 1, "rebuilt"});
|
|
});
|
|
REQUIRE(rebuilt.ok());
|
|
CHECK(static_cast<int>(rebuilt.value().disposition) ==
|
|
static_cast<int>(eve::definition::ReloadDisposition::Rebuilt));
|
|
CHECK_EQ(runtime.state().value, 43);
|
|
|
|
auto failedRebuild = runtime.reload(eve::definition::DefinitionHandle{reference, eve::Generation(5)},
|
|
eve::definition::ReloadPolicy::RebuildInstance, TypedState{0, "must not apply"},
|
|
[](const TypedState&, const eve::definition::InstanceIdentity&,
|
|
const eve::definition::DefinitionHandle&) -> eve::Result<TypedState> {
|
|
return eve::Result<TypedState>::failure(eve::Diagnostic::error(
|
|
eve::DiagnosticCode::Failed, "injected rebuild failure"));
|
|
});
|
|
CHECK(!failedRebuild.ok());
|
|
CHECK_EQ(runtime.identity().definitionGeneration.value(), uint64_t{4});
|
|
CHECK_EQ(runtime.state().value, 43);
|
|
|
|
auto rejected = runtime.reload(eve::definition::DefinitionHandle{reference, eve::Generation(5)},
|
|
eve::definition::ReloadPolicy::RejectWhileActive, TypedState{0, "must not apply"});
|
|
CHECK(!rejected.ok());
|
|
CHECK(static_cast<int>(rejected.error()->code()) == static_cast<int>(eve::DiagnosticCode::Conflict));
|
|
CHECK_EQ(runtime.identity().definitionGeneration.value(), uint64_t{4});
|
|
CHECK_EQ(runtime.state().value, 43);
|
|
|
|
runtime.setActive(false);
|
|
auto inactiveReject = runtime.reload(eve::definition::DefinitionHandle{reference, eve::Generation(5)},
|
|
eve::definition::ReloadPolicy::RejectWhileActive, TypedState{0, "still kept"});
|
|
REQUIRE(inactiveReject.ok());
|
|
CHECK(static_cast<int>(inactiveReject.value().disposition) ==
|
|
static_cast<int>(eve::definition::ReloadDisposition::Kept));
|
|
CHECK_EQ(runtime.identity().definitionGeneration.value(), uint64_t{5});
|
|
CHECK_EQ(runtime.state().value, 43);
|
|
}
|
|
|
|
TEST_CASE("definitionRuntime.cardAdapterProjectsTypedIdentityAndReloads") {
|
|
eve::definitions::DefinitionRegistry registry;
|
|
auto inserted = registry.insert(
|
|
"card", "scout", 1, R"({"name":"Scout","kind":"creature","cost":2,"attack":3,"health":5,"tags":["unit"]})");
|
|
REQUIRE(inserted.ok());
|
|
|
|
auto runtimeResult = eve::card::CardDefinitionRuntime::create(registry, testReference("card:scout"), testId(),
|
|
eve::definition::ReloadPolicy::RebuildInstance);
|
|
REQUIRE(runtimeResult.ok());
|
|
auto runtime = std::move(runtimeResult).takeValue();
|
|
CHECK_EQ(runtime.state().attack, 3);
|
|
CHECK_EQ(runtime.identity().definitionGeneration.value(), uint64_t{1});
|
|
runtime.state().currentHealth = 2;
|
|
|
|
auto snapshot = runtime.snapshot(eve::Revision(1), eve::SimulationTick(2), testHash());
|
|
REQUIRE(snapshot.ok());
|
|
runtime.state().currentHealth = 1;
|
|
auto restored = runtime.restore(snapshot.value(), testHash());
|
|
REQUIRE(restored.ok());
|
|
CHECK_EQ(runtime.state().currentHealth, 2);
|
|
|
|
auto card = eve::card::CardData::createCard();
|
|
REQUIRE(card != nullptr);
|
|
auto projected = runtime.applyTo(card);
|
|
REQUIRE(projected.ok());
|
|
CHECK_EQ(card->stats()->health, 2);
|
|
CHECK_EQ(card->definitionBinding()->identity.instanceId, testId());
|
|
CHECK_EQ(card->definitionBinding()->identity.definitionGeneration.value(), uint64_t{1});
|
|
card->release();
|
|
|
|
auto replaced = registry.replace("card", "scout", 2,
|
|
R"({"name":"Veteran Scout","kind":"creature","cost":3,"attack":6,"health":4})");
|
|
REQUIRE(replaced.ok());
|
|
auto staleSnapshot = runtime.restore(snapshot.value(), testHash());
|
|
CHECK(!staleSnapshot.ok());
|
|
CHECK_EQ(runtime.state().currentHealth, 2);
|
|
auto reloaded = runtime.reload(eve::definition::ReloadPolicy::RebuildInstance);
|
|
REQUIRE(reloaded.ok());
|
|
CHECK(static_cast<int>(reloaded.value().disposition) ==
|
|
static_cast<int>(eve::definition::ReloadDisposition::Rebuilt));
|
|
CHECK_EQ(runtime.state().attack, 6);
|
|
CHECK_EQ(runtime.state().currentHealth, 2);
|
|
CHECK_EQ(runtime.identity().definitionGeneration.value(), uint64_t{2});
|
|
}
|
|
|
|
TEST_CASE("definitionRuntime.effectAdapterKeepsTypedPolicyAndRejectsActiveReload") {
|
|
eve::definitions::DefinitionRegistry registry;
|
|
auto inserted = registry.insert(
|
|
"effect", "burn", 1,
|
|
R"({"stackKey":"burn","priority":4,"duration":8.0,"magnitude":3.5,"stackCount":1,"maxStacks":3,"stackMode":"reuse","stackCountPolicy":"increment","payload":{"damageType":"fire"}})");
|
|
REQUIRE(inserted.ok());
|
|
|
|
auto runtimeResult = eve::effects::EffectDefinitionRuntime::create(
|
|
registry, testReference("effect:burn"), "actor:target", "spell:ember", testId(),
|
|
eve::definition::ReloadPolicy::RejectWhileActive);
|
|
REQUIRE(runtimeResult.ok());
|
|
auto runtime = std::move(runtimeResult).takeValue();
|
|
CHECK(static_cast<int>(runtime.state().policy.stackMode) == static_cast<int>(eve::effects::StackMode::Reuse));
|
|
CHECK(static_cast<int>(runtime.state().policy.stackCount) ==
|
|
static_cast<int>(eve::effects::StackCountPolicy::Increment));
|
|
|
|
eve::effects::EffectInstance instance;
|
|
auto projected = runtime.applyTo(&instance);
|
|
REQUIRE(projected.ok());
|
|
CHECK(static_cast<bool>(instance.definitionIdentity == runtime.identity()));
|
|
CHECK(static_cast<int>(instance.policy.stackMode) == static_cast<int>(eve::effects::StackMode::Reuse));
|
|
CHECK_EQ(instance.payload.getJson("damageType"), std::string("\"fire\""));
|
|
|
|
auto replaced = registry.replace(
|
|
"effect", "burn", 2,
|
|
R"({"stackKey":"burn","priority":5,"duration":10.0,"magnitude":4.0,"stackCount":1,"maxStacks":3})");
|
|
REQUIRE(replaced.ok());
|
|
auto rejected = runtime.reload(eve::definition::ReloadPolicy::RejectWhileActive);
|
|
CHECK(!rejected.ok());
|
|
CHECK(static_cast<int>(rejected.error()->code()) == static_cast<int>(eve::DiagnosticCode::Conflict));
|
|
CHECK_EQ(runtime.identity().definitionGeneration.value(), uint64_t{1});
|
|
}
|