Skip to content

Commit 1e21716

Browse files
committed
Refactor RuleSet
1 parent 6c7dd04 commit 1e21716

3 files changed

Lines changed: 225 additions & 1 deletion

File tree

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <string_view>
5+
6+
#include "openvic-simulation/core/string/StringLiteral.hpp"
7+
#include "openvic-simulation/core/Typedefs.hpp"
8+
#include "openvic-simulation/dataloader/NodeTools.hpp"
9+
#include "openvic-simulation/types/OptionalBool.hpp"
10+
#include "openvic-simulation/utility/Logger.hpp"
11+
12+
namespace OpenVic {
13+
//TODO replace with strategy pattern?
14+
enum struct ApportionmentMethod : std::uint8_t {
15+
largest_share,
16+
dhont,
17+
sainte_laque
18+
};
19+
enum struct UpperHouseComposition : std::uint8_t {
20+
same_as_ruling_party,
21+
rich_only,
22+
state_vote,
23+
population_vote
24+
};
25+
enum struct CulturalVotingRight : std::uint8_t {
26+
primary_culture_voting,
27+
culture_voting,
28+
all_voting
29+
};
30+
31+
struct RuleSet {
32+
private:
33+
struct RuleDefinition {
34+
const std::string_view name;
35+
OptionalBool const& value;
36+
37+
template<string_literal Name>
38+
static constexpr RuleDefinition Define(OptionalBool const& value) {
39+
return RuleDefinition { { Name }, value };
40+
}
41+
};
42+
43+
#define DO_FOR_ALL_RULES(F) \
44+
F(build_railway) \
45+
F(build_factory) \
46+
F(expand_factory) \
47+
F(open_factory) \
48+
F(destroy_factory) \
49+
F(pop_build_factory) \
50+
F(pop_expand_factory) \
51+
F(pop_open_factory) \
52+
F(can_subsidise) \
53+
F(factory_priority) \
54+
F(delete_factory_if_no_input) \
55+
F(build_factory_invest) \
56+
F(expand_factory_invest) \
57+
F(open_factory_invest) \
58+
F(build_railway_invest) \
59+
F(pop_build_factory_invest) \
60+
F(pop_expand_factory_invest) \
61+
F(pop_open_factory_invest) \
62+
F(can_invest_in_pop_projects) \
63+
F(allow_foreign_investment) \
64+
F(primary_culture_voting) \
65+
F(culture_voting) \
66+
F(all_voting) \
67+
F(slavery_allowed) \
68+
F(same_as_ruling_party) \
69+
F(rich_only) \
70+
F(state_vote) \
71+
F(population_vote) \
72+
F(largest_share) \
73+
F(dhont) \
74+
F(sainte_laque)
75+
76+
#define RULE_FIELDS(rule) \
77+
OptionalBool rule : 2 = OptionalBool::UNSPECIFIED;
78+
79+
DO_FOR_ALL_RULES(RULE_FIELDS)
80+
#undef RULE_FIELDS
81+
82+
static NodeTools::node_callback_t expect_rule_set(NodeTools::callback_t<RuleSet&&> ruleset_callback) {
83+
return [ruleset_callback](ast::NodeCPtr root) mutable -> bool {
84+
RuleSet ruleset;
85+
using enum NodeTools::dictionary_entry_t::expected_count_t;
86+
bool ret = NodeTools::expect_dictionary_keys(
87+
"sainte_laque", ZERO_OR_ONE, NodeTools::expect_bool(
88+
[&ruleset](const bool value) mutable -> bool {
89+
const bool ret = ruleset.sainte_laque == OptionalBool::UNSPECIFIED;
90+
ruleset.sainte_laque = value ? OptionalBool::TRUE : OptionalBool::FALSE;
91+
return ret;
92+
}
93+
)
94+
//TODO add the rest using the DO_FOR_ALL_RULES macro and avoid duplicating inline lambda callbacks
95+
)(root);
96+
ret &= ruleset_callback(std::move(ruleset));
97+
return ret;
98+
};
99+
}
100+
101+
#undef DO_FOR_ALL_RULES
102+
103+
template <typename EnumType, size_t N>
104+
constexpr EnumType select_with_priority(const RuleDefinition (&rules)[N]) const {
105+
const std::string_view context_name = type_name<EnumType>();
106+
107+
// 1. Check for explicit TRUE flags (Highest Priority)
108+
for (size_t i = 0; i < N; ++i) {
109+
if (rules[i].value == OptionalBool::TRUE) {
110+
if (!std::is_constant_evaluated()) {
111+
// Log conflicts with lower-priority methods
112+
for (size_t j = i + 1; j < N; ++j) {
113+
if (OV_unlikely(rules[j].value == OptionalBool::TRUE)) {
114+
spdlog::warn_s(
115+
"Both {} and {} are enabled for {}. {} is picked.",
116+
rules[i].name,
117+
rules[j].name,
118+
context_name,
119+
rules[i].name
120+
);
121+
}
122+
}
123+
}
124+
return EnumType(i);
125+
}
126+
}
127+
128+
// 2. Check for UNSPECIFIED (Defaulting logic)
129+
for (size_t i = 0; i < N; ++i) {
130+
if (rules[i].value == OptionalBool::UNSPECIFIED) {
131+
if (!std::is_constant_evaluated()) {
132+
spdlog::warn_s("No {} enabled. Picking {} (unspecified).", context_name, rules[i].name);
133+
}
134+
return EnumType(i);
135+
}
136+
}
137+
138+
// 3. Absolute Fallback
139+
if (!std::is_constant_evaluated()) {
140+
spdlog::error_s("All {} options are disabled. Falling back to {}.", context_name, rules[0].name);
141+
}
142+
return EnumType(0);
143+
}
144+
145+
#define DEF(Value) RuleDefinition::Define<#Value>(Value)
146+
147+
#define RESOLVE_DEFAULT_FALSE(method_name, field) \
148+
constexpr bool method_name() const { \
149+
const bool is_true = field == OptionalBool::TRUE; \
150+
if (!std::is_constant_evaluated() && field == OptionalBool::UNSPECIFIED) { \
151+
spdlog::warn_s("{} is not specified, returning {}.", #field, is_true); \
152+
} \
153+
return is_true; \
154+
}
155+
156+
public:
157+
//political
158+
constexpr ApportionmentMethod get_apportionment_method() const {
159+
const RuleDefinition rules[] {
160+
DEF(largest_share),
161+
DEF(dhont),
162+
DEF(sainte_laque)
163+
};
164+
return select_with_priority<ApportionmentMethod>(rules);
165+
}
166+
constexpr UpperHouseComposition get_upper_house_composition() const {
167+
const RuleDefinition rules[] {
168+
DEF(same_as_ruling_party),
169+
DEF(rich_only),
170+
DEF(state_vote),
171+
DEF(population_vote)
172+
};
173+
return select_with_priority<UpperHouseComposition>(rules);
174+
}
175+
constexpr CulturalVotingRight get_cultural_voting_rights() const {
176+
const RuleDefinition rules[] {
177+
DEF(primary_culture_voting),
178+
DEF(culture_voting),
179+
DEF(all_voting)
180+
};
181+
return select_with_priority<CulturalVotingRight>(rules);
182+
}
183+
#undef DEF
184+
RESOLVE_DEFAULT_FALSE(is_slavery_legal, slavery_allowed)
185+
//economic
186+
RESOLVE_DEFAULT_FALSE(may_build_infrastructure_domestically, build_railway)
187+
RESOLVE_DEFAULT_FALSE(may_build_factory_domestically, build_factory)
188+
RESOLVE_DEFAULT_FALSE(may_expand_factory_domestically, expand_factory)
189+
RESOLVE_DEFAULT_FALSE(may_open_factory_domestically, open_factory)
190+
constexpr bool may_close_factory_domestically() const {
191+
return may_open_factory_domestically();
192+
}
193+
RESOLVE_DEFAULT_FALSE(may_destroy_factory_domestically, destroy_factory)
194+
RESOLVE_DEFAULT_FALSE(may_subsidise_factory_domestically, can_subsidise)
195+
RESOLVE_DEFAULT_FALSE(may_set_factory_priority_domestically, factory_priority)
196+
197+
RESOLVE_DEFAULT_FALSE(pop_may_build_factory_domestically, pop_build_factory)
198+
RESOLVE_DEFAULT_FALSE(pop_may_expand_factory_domestically, pop_expand_factory)
199+
RESOLVE_DEFAULT_FALSE(pop_may_open_factory_domestically, pop_open_factory)
200+
RESOLVE_DEFAULT_FALSE(may_automatically_delete_factory_if_no_input_domestically, delete_factory_if_no_input)
201+
RESOLVE_DEFAULT_FALSE(may_invest_in_pop_projects_domestically, can_invest_in_pop_projects)
202+
203+
RESOLVE_DEFAULT_FALSE(may_invest_in_building_factory_abroad, build_factory_invest)
204+
RESOLVE_DEFAULT_FALSE(may_invest_in_expanding_factory_abroad, expand_factory_invest)
205+
RESOLVE_DEFAULT_FALSE(may_invest_in_opening_factory_abroad, open_factory_invest)
206+
RESOLVE_DEFAULT_FALSE(pop_may_invest_in_building_factory_abroad, pop_build_factory_invest)
207+
RESOLVE_DEFAULT_FALSE(pop_may_invest_in_expanding_factory_abroad, pop_expand_factory_invest)
208+
RESOLVE_DEFAULT_FALSE(pop_may_invest_in_opening_factory_abroad, pop_open_factory_invest)
209+
RESOLVE_DEFAULT_FALSE(may_invest_in_expanding_infrastructure_abroad, build_railway_invest)
210+
211+
RESOLVE_DEFAULT_FALSE(foreigners_may_invest, allow_foreign_investment)
212+
#undef RESOLVE_DEFAULT_FALSE
213+
};
214+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
5+
namespace OpenVic {
6+
enum struct OptionalBool : std::uint8_t {
7+
UNSPECIFIED = 0,
8+
TRUE = 1,
9+
FALSE = 2
10+
};
11+
}

src/openvic-simulation/types/TypedIndices.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ TYPED_INDEX(rebel_type_index_t)
4646
TYPED_INDEX(reform_index_t)
4747
TYPED_INDEX(reform_group_index_t)
4848
TYPED_INDEX(regiment_type_index_t)
49-
TYPED_INDEX(rule_index_t)
5049
TYPED_INDEX(ship_type_index_t)
5150
TYPED_INDEX(strata_index_t)
5251
TYPED_INDEX(technology_index_t)

0 commit comments

Comments
 (0)