Skip to content

Commit 69a6f3e

Browse files
committed
fix(config_compiler): enforce dependency priorities
1 parent 25c28f8 commit 69a6f3e

File tree

3 files changed

+67
-19
lines changed

3 files changed

+67
-19
lines changed

data/test/config_compiler_test.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ dependency_chaining:
4242
__include: /dependency_chaining/beta
4343
epsilon: success
4444

45+
dependency_priorities:
46+
terrans:
47+
__include: /starcraft/terrans
48+
__patch:
49+
player: nada
50+
protoss:
51+
__patch:
52+
player: bisu
53+
__include: /starcraft/protoss
54+
4555
local:
4656
patch:
4757
battlefields/@next: match point

src/rime/config/config_compiler.cc

+43-19
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,26 @@
77

88
namespace rime {
99

10+
enum DependencyPriority {
11+
kPendingChild = 0,
12+
kInclude = 1,
13+
kPatch = 2,
14+
};
15+
1016
struct Dependency {
1117
an<ConfigItemRef> target;
1218

13-
virtual bool blocking() const = 0;
19+
virtual DependencyPriority priority() const = 0;
20+
bool blocking() const {
21+
return priority() > kPendingChild;
22+
}
1423
virtual string repr() const = 0;
1524
virtual bool Resolve(ConfigCompiler* compiler) = 0;
1625
};
1726

1827
template <class StreamT>
1928
StreamT& operator<< (StreamT& stream, const Dependency& dep) {
20-
return stream << dep.repr() << (dep.blocking() ? "(blocking)" : "");
29+
return stream << dep.repr();
2130
}
2231

2332
struct PendingChild : Dependency {
@@ -27,8 +36,8 @@ struct PendingChild : Dependency {
2736
PendingChild(const string& path, const an<ConfigItemRef>& ref)
2837
: child_path(path), child_ref(ref) {
2938
}
30-
bool blocking() const override {
31-
return false;
39+
DependencyPriority priority() const override {
40+
return kPendingChild;
3241
}
3342
string repr() const override {
3443
return "PendingChild(" + child_path + ")";
@@ -48,8 +57,8 @@ StreamT& operator<< (StreamT& stream, const Reference& reference) {
4857
struct IncludeReference : Dependency {
4958
IncludeReference(const Reference& r) : reference(r) {
5059
}
51-
bool blocking() const override {
52-
return true;
60+
DependencyPriority priority() const override {
61+
return kInclude;
5362
}
5463
string repr() const override {
5564
return "Include(" + reference.repr() + ")";
@@ -62,8 +71,8 @@ struct IncludeReference : Dependency {
6271
struct PatchReference : Dependency {
6372
PatchReference(const Reference& r) : reference(r) {
6473
}
65-
bool blocking() const override {
66-
return true;
74+
DependencyPriority priority() const override {
75+
return kPatch;
6776
}
6877
string repr() const override {
6978
return "Patch(" + reference.repr() + ")";
@@ -78,8 +87,8 @@ struct PatchLiteral : Dependency {
7887

7988
PatchLiteral(an<ConfigMap> map) : patch(map) {
8089
}
81-
bool blocking() const override {
82-
return true;
90+
DependencyPriority priority() const override {
91+
return kPatch;
8392
}
8493
string repr() const override {
8594
return "Patch(<literal>)";
@@ -91,7 +100,7 @@ struct ConfigDependencyGraph {
91100
map<string, of<ConfigResource>> resources;
92101
vector<of<ConfigItemRef>> node_stack;
93102
vector<string> key_stack;
94-
map<string, list<of<Dependency>>> deps;
103+
map<string, vector<of<Dependency>>> deps;
95104

96105
void Add(an<Dependency> dependency);
97106

@@ -162,21 +171,34 @@ bool PatchLiteral::Resolve(ConfigCompiler* compiler) {
162171
return success;
163172
}
164173

174+
static void InsertByPriority(vector<of<Dependency>>& list,
175+
const an<Dependency>& value) {
176+
auto upper = std::upper_bound(
177+
list.begin(), list.end(), value,
178+
[](const an<Dependency>& lhs, const an<Dependency>& rhs) {
179+
return lhs->priority() < rhs->priority();
180+
});
181+
list.insert(upper, value);
182+
}
183+
165184
void ConfigDependencyGraph::Add(an<Dependency> dependency) {
166-
LOG(INFO) << "ConfigDependencyGraph::Add(), node_stack.size() = " << node_stack.size();
185+
LOG(INFO) << "ConfigDependencyGraph::Add(), node_stack.size() = "
186+
<< node_stack.size();
167187
if (node_stack.empty()) return;
168188
const auto& target = node_stack.back();
169189
dependency->target = target;
170190
auto target_path = ConfigData::JoinPath(key_stack);
171191
auto& target_deps = deps[target_path];
172192
bool target_was_pending = !target_deps.empty();
173-
target_deps.push_back(dependency);
174-
LOG(INFO) << "target_path = " << target_path << ", #deps = " << target_deps.size();
193+
InsertByPriority(target_deps, dependency);
194+
LOG(INFO) << "target_path = " << target_path
195+
<< ", #deps = " << target_deps.size();
175196
if (target_was_pending || // so was all ancestors
176197
key_stack.size() == 1) { // this is the progenitor
177198
return;
178199
}
179-
// The current pending node becomes a prioritized dependency of parent node
200+
// The current pending node becomes a prioritized non-blocking dependency of
201+
// its parent node; spread the pending state to its non-pending ancestors
180202
auto keys = key_stack;
181203
for (auto child = node_stack.rbegin(); child != node_stack.rend(); ++child) {
182204
auto last_key = keys.back();
@@ -185,8 +207,10 @@ void ConfigDependencyGraph::Add(an<Dependency> dependency) {
185207
auto& parent_deps = deps[parent_path];
186208
bool parent_was_pending = !parent_deps.empty();
187209
// Pending children should be resolved before applying __include or __patch
188-
parent_deps.push_front(New<PendingChild>(parent_path + "/" + last_key, *child));
189-
LOG(INFO) << "parent_path = " << parent_path << ", #deps = " << parent_deps.size();
210+
InsertByPriority(parent_deps,
211+
New<PendingChild>(parent_path + "/" + last_key, *child));
212+
LOG(INFO) << "parent_path = " << parent_path
213+
<< ", #deps = " << parent_deps.size();
190214
if (parent_was_pending || // so was all ancestors
191215
keys.size() == 1) { // this parent is the progenitor
192216
return;
@@ -418,10 +442,10 @@ bool ConfigCompiler::ResolveDependencies(const string& path) {
418442
auto& deps = graph_->deps[path];
419443
for (auto iter = deps.begin(); iter != deps.end(); ) {
420444
if (!(*iter)->Resolve(this)) {
421-
LOG(ERROR) << "unesolved dependency:" << **iter;
445+
LOG(ERROR) << "unresolved dependency: " << **iter;
422446
return false;
423447
}
424-
LOG(INFO) << "resolved " << **iter;
448+
LOG(INFO) << "resolved: " << **iter;
425449
iter = deps.erase(iter);
426450
}
427451
LOG(INFO) << "all dependencies resolved.";

test/config_compiler_test.cc

+14
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,18 @@ TEST_F(RimeConfigCompilerTest, DependencyChaining) {
115115
EXPECT_EQ("success", value);
116116
}
117117

118+
// Unit test for https://github.com/rime/librime/issues/141
119+
TEST_F(RimeConfigCompilerTest, DependencyPriorities) {
120+
const string& prefix = "dependency_priorities/";
121+
EXPECT_TRUE(config_->IsNull(prefix + "terrans/__include"));
122+
EXPECT_TRUE(config_->IsNull(prefix + "terrans/__patch"));
123+
EXPECT_TRUE(config_->IsNull(prefix + "protoss/__include"));
124+
EXPECT_TRUE(config_->IsNull(prefix + "protoss/__patch"));
125+
string player;
126+
EXPECT_TRUE(config_->GetString(prefix + "terrans/player", &player));
127+
EXPECT_EQ("nada", player);
128+
EXPECT_TRUE(config_->GetString(prefix + "protoss/player", &player));
129+
EXPECT_EQ("bisu", player);
130+
}
131+
118132
// TODO: test failure cases

0 commit comments

Comments
 (0)