Adds a capture -> reload -> restore pipeline so code/asset changes can be hot-applied without resetting running state: - common: StateValue (JSON-compatible state tree) + IStateProvider capability interface. - devtools: Snapshot v2 (script roots + native provider section, v1 compatible, strict rejection of non-data roots), ReloadSession (begin/commit/abort), eve.dev script API (stateRoots / beginStateReload / commitStateReload / abortStateReload). - load.nut: three-phase soft reload (capture -> dofile -> restore) with migrate_instance / remap_instances helpers and eve_before_reload / eve_after_reload hooks. - dialogue + AnimStateMachine: IStateProvider for phase/vars/rng and params/current state. - animation: AnimClip::adopt + AnimClipRegistry + 'animclip' IAssetReloader so .eva content reloads keep clip identity. - rpg + card: IStateProvider for casting/cooldowns and card phases. - Tests: stateValue, snapshot v2/strict, reload script protocol, dialogue, animation state machine + reload, rpg/card state roundtrips.
119 lines
3.9 KiB
C++
119 lines
3.9 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include "common/StateValue.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace eve;
|
|
|
|
TEST_CASE("stateValue.factoriesAndKinds") {
|
|
CHECK(StateValue::null().isNull());
|
|
CHECK(StateValue::boolean(true).asBool());
|
|
CHECK_EQ(StateValue::integer(42).asInt(), int64_t(42));
|
|
CHECK(StateValue::number(1.5).asDouble() == 1.5);
|
|
CHECK_EQ(StateValue::string("hi").asString(), std::string("hi"));
|
|
CHECK(StateValue::array().isArray());
|
|
CHECK(StateValue::object().isObject());
|
|
}
|
|
|
|
TEST_CASE("stateValue.objectSetFindKeys") {
|
|
StateValue o = StateValue::object();
|
|
o.set("a", StateValue::integer(1));
|
|
o.set("b", StateValue::string("x"));
|
|
|
|
const StateValue* a = o.find("a");
|
|
REQUIRE(a != nullptr);
|
|
CHECK_EQ(a->asInt(), int64_t(1));
|
|
CHECK(o.find("missing") == nullptr);
|
|
|
|
const std::vector<std::string> keys = o.keys();
|
|
REQUIRE(keys.size() == 2u);
|
|
CHECK_EQ(keys[0], std::string("a"));
|
|
CHECK_EQ(keys[1], std::string("b"));
|
|
|
|
// set() replaces in place without reordering or duplicating.
|
|
o.set("a", StateValue::integer(2));
|
|
CHECK_EQ(o.find("a")->asInt(), int64_t(2));
|
|
CHECK_EQ(o.keys().size(), 2u);
|
|
}
|
|
|
|
TEST_CASE("stateValue.arrayOps") {
|
|
StateValue a = StateValue::array();
|
|
a.pushBack(StateValue::integer(10));
|
|
a.pushBack(StateValue::string("v"));
|
|
|
|
REQUIRE(a.arraySize() == 2u);
|
|
CHECK_EQ(a.at(0).asInt(), int64_t(10));
|
|
CHECK_EQ(a.at(1).asString(), std::string("v"));
|
|
}
|
|
|
|
TEST_CASE("stateValue.dottedPathGetSet") {
|
|
StateValue o = StateValue::object();
|
|
CHECK(o.setPath("player.level", StateValue::integer(3)));
|
|
const StateValue* level = o.get("player.level");
|
|
REQUIRE(level != nullptr);
|
|
CHECK_EQ(level->asInt(), int64_t(3));
|
|
|
|
StateValue items = StateValue::array();
|
|
items.pushBack(StateValue::string("sword"));
|
|
o.set("items", std::move(items));
|
|
|
|
const StateValue* first = o.get("items[0]");
|
|
REQUIRE(first != nullptr);
|
|
CHECK_EQ(first->asString(), std::string("sword"));
|
|
|
|
CHECK(o.get("player.missing") == nullptr);
|
|
CHECK(o.get("items[5]") == nullptr);
|
|
CHECK(o.get("player.level.x") == nullptr); // scalar segment is not an object
|
|
|
|
// Array element replacement on the final segment.
|
|
CHECK(o.setPath("items[0]", StateValue::string("axe")));
|
|
CHECK_EQ(o.get("items[0]")->asString(), std::string("axe"));
|
|
|
|
// Non-object roots cannot take object paths; arrays must be pre-sized.
|
|
StateValue n = StateValue::null();
|
|
CHECK(!n.setPath("a.b", StateValue::integer(1)));
|
|
CHECK(!o.setPath("items[2]", StateValue::integer(1)));
|
|
}
|
|
|
|
TEST_CASE("stateValue.mergeDefaults") {
|
|
StateValue o = StateValue::object();
|
|
o.set("keep", StateValue::integer(1));
|
|
|
|
StateValue defaults = StateValue::object();
|
|
defaults.set("keep", StateValue::integer(99));
|
|
defaults.set("new", StateValue::string("d"));
|
|
StateValue nested = StateValue::object();
|
|
nested.set("x", StateValue::boolean(true));
|
|
defaults.set("nested", nested);
|
|
|
|
CHECK(o.mergeDefaults(defaults));
|
|
CHECK_EQ(o.find("keep")->asInt(), int64_t(1)); // existing values untouched
|
|
CHECK_EQ(o.find("new")->asString(), std::string("d"));
|
|
const StateValue* n = o.find("nested");
|
|
REQUIRE(n != nullptr);
|
|
REQUIRE(n->isObject());
|
|
CHECK(n->find("x")->asBool());
|
|
|
|
// Merging into a non-object is a no-op; a second merge adds nothing.
|
|
CHECK(!StateValue::integer(1).mergeDefaults(defaults));
|
|
CHECK(!o.mergeDefaults(defaults));
|
|
}
|
|
|
|
TEST_CASE("stateValue.equality") {
|
|
CHECK(StateValue::integer(1) == StateValue::integer(1));
|
|
CHECK(StateValue::integer(1) != StateValue::integer(2));
|
|
|
|
StateValue a = StateValue::object();
|
|
a.set("k", StateValue::string("v"));
|
|
StateValue b = StateValue::object();
|
|
b.set("k", StateValue::string("v"));
|
|
CHECK(a == b);
|
|
b.set("k", StateValue::string("w"));
|
|
CHECK(a != b);
|
|
|
|
CHECK(StateValue::null() == StateValue::null());
|
|
}
|