// this is a naive-ish implementation // this is not pure naive, since it implements the less-branch Criterion compare variant discussed in the valve talk #include #include #include #include #include #include #include #include #include #include // context aka, kv-dict // criterion, predicate that returns true or false based on state and context(query) // equal: value == min == max // smaller: value == max -e; min == -inf // greater: value == min +e; max == +inf struct Criterion { std::string key; float min, max; bool compare(const float x) const { return x >= min && x <= max; } bool operator<(const Criterion& other) const { return key < other.key && min < other.min && max < other.max; } }; // rule, list of criterions (obv &&) struct Rules { std::set crits; }; // query, ...? struct Query { // sorted? std::map dict; Query& add(const std::string& key, float value) { dict[key] = value; return *this; } Query& add(const std::string& key, const std::string& value) { // lol dict[key] = std::hash{}(value); return *this; } bool match_criterion(const Criterion& crit) const { // TODO: lookup is performed 2x, optimize return dict.count(crit.key) && crit.compare(dict.at(crit.key)); } bool match_rules(const Rules& rules) const { for (const auto& crit : rules.crits) { if (!match_criterion(crit)) { return false; } } return true; } }; // response -> action (list?) struct Response { Rules rules; // normally you would give it the caller(entity f is called on?) std::function f; Response(void) = default; Response(const Rules& r, std::function&& _f) : rules(r), f(_f) {} size_t criterion_size(void) const { return rules.crits.size(); } }; // special keys (usually bucketalbe): // - concept: onHit, onIdle, onResponse, onObject? // - "who": which char talked. gonna name this differently struct ResponseDB { std::vector responses; // returns index template std::optional match(const Query& q, RNG& rng) const { size_t crit_count_for_match = 0; std::vector match_list; //for (const auto& r : responses) { for (size_t i = 0; i < responses.size(); i++) { const auto& r = responses[i]; // skip rules with less criteria matching if (r.criterion_size() < crit_count_for_match) { continue; } if (q.match_rules(r.rules)) { if (crit_count_for_match < r.criterion_size()) { match_list.clear(); crit_count_for_match = r.criterion_size(); } match_list.push_back(i); } } if (match_list.empty()) { return std::nullopt; } std::shuffle(match_list.begin(), match_list.end(), rng); return match_list.front(); } template bool match_and_run(const Query& q, RNG& rng) const { auto idx = match(q, rng); if (idx.has_value()) { responses.at(*idx).f(); return true; } return false; } }; int main(void) { std::map reverse_string_map; reverse_string_map[std::hash{}("who")] = "who"; // crit test Criterion hp_not_critical { "hp", // have at least 15% of max hp 0.15f, std::numeric_limits::infinity() }; assert(hp_not_critical.compare(0.0f) == false); assert(hp_not_critical.compare(0.15f) == true); assert(hp_not_critical.compare(0.15f - std::numeric_limits::epsilon()) == false); assert(hp_not_critical.compare(1.0f) == true); Criterion is_hero { "who", // tmp 0.f, 0.f }; // key "who" is value "hero" is_hero.min = std::hash{}("hero"); is_hero.max = std::hash{}("hero"); assert(is_hero.compare(std::hash{}("hero")) == true); assert(is_hero.compare(std::hash{}("enemy")) == false); assert(is_hero.compare(std::hash{}("ally")) == false); Criterion on_idle { "concept", // tmp 0.f, 0.f }; // key "concept" is value "idle" on_idle.min = std::hash{}("idle"); on_idle.max = std::hash{}("idle"); assert(on_idle.compare(std::hash{}("idle")) == true); assert(on_idle.compare(std::hash{}("hero")) == false); assert(on_idle.compare(std::hash{}("hit")) == false); Rules rules_hero_idle_chatter {{ on_idle, is_hero, hp_not_critical }}; // query test Query q1{}; q1 .add("concept", "idle") .add("who", "hero") .add("hp", 0.843122f) .add("ally_near", 1.f) .add("enemy_near", 0.f) .add("in_danger", 0.f) ; Query q2{}; q2 .add("concept", "hit") .add("who", "enemy") .add("hp", 0.12333f) .add("ally_near", 0.f) .add("enemy_near", 1.f) .add("in_danger", 1.f) ; // add junk to q1 // 100'000 -> >200ms // 10'000 -> >ms for (int i = 0; i < 10'000; i++) { q1.add(std::to_string(i) + "asdf", (i - 155) * 12399.3249f); } assert(q1.match_criterion(hp_not_critical) == true); assert(q1.match_criterion(is_hero) == true); assert(q2.match_criterion(hp_not_critical) == false); assert(q2.match_criterion(is_hero) == false); assert(q1.match_rules(rules_hero_idle_chatter) == true); assert(q2.match_rules(rules_hero_idle_chatter) == false); // response DB std::mt19937 rng{1337*433}; ResponseDB db; size_t fn_called = 0; db.responses.emplace_back(rules_hero_idle_chatter, [&fn_called](){ fn_called++; }); assert(fn_called == 0); assert(db.match_and_run(q1, rng) == true); assert(fn_called == 1); assert(db.match_and_run(q2, rng) == false); assert(fn_called == 1); return 0; }