Skip to content

Commit e9c5e99

Browse files
committed
refactor: clean up ShadowTreeUpdateManager
1 parent 65e5d6d commit e9c5e99

2 files changed

Lines changed: 172 additions & 180 deletions

File tree

cpp/ShadowTreeUpdateManager.cpp

Lines changed: 168 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -9,205 +9,194 @@
99
#include <NitroModules/AnyMap.hpp>
1010
#include <variant>
1111
#include <functional>
12+
#include <type_traits>
13+
#include <string>
1214
#include <cctype>
1315
#include <react/renderer/uimanager/UIManagerBinding.h>
1416

15-
namespace margelo {
16-
namespace nitro {
17-
namespace cssnitro {
18-
19-
using jsi::Runtime;
20-
using reactnativecss::Observable;
21-
namespace nitro_ns = ::margelo::nitro;
22-
23-
namespace {
24-
static bool containsColorInsensitive(const std::string &key) {
25-
if (key.size() < 5) return false;
26-
std::string lower;
27-
lower.reserve(key.size());
28-
for (char c: key)
29-
lower.push_back(
30-
static_cast<char>(::tolower(static_cast<unsigned char>(c))));
31-
return lower.find("color") != std::string::npos;
32-
}
33-
}
17+
namespace margelo::nitro::cssnitro {
18+
19+
using jsi::Runtime;
20+
using reactnativecss::Observable;
21+
namespace nitro_ns = ::margelo::nitro;
22+
23+
namespace {
24+
bool containsColorInsensitive(const std::string &key) {
25+
if (key.size() < 5) return false;
26+
std::string lower;
27+
lower.reserve(key.size());
28+
for (char c: key)
29+
lower.push_back(
30+
static_cast<char>(::tolower(static_cast<unsigned char>(c))));
31+
return lower.find("color") != std::string::npos;
32+
}
33+
}
3434

35-
// Hook-aware conversion: allow a key-based transform of dynamic values during object conversion
36-
static folly::dynamic variantToDynamicWithHook(
37-
const nitro_ns::VariantType &var,
38-
const std::function<folly::dynamic(const std::string &,
39-
const folly::dynamic &)> &hook) {
40-
return std::visit(
41-
[&hook](auto &&arg) -> folly::dynamic {
42-
using T = std::decay_t<decltype(arg)>;
43-
if constexpr (std::is_same_v<T, std::monostate>) {
44-
return folly::dynamic(nullptr);
45-
} else if constexpr (std::is_same_v<T, bool>) {
46-
return folly::dynamic(arg);
47-
} else if constexpr (std::is_same_v<T, double>) {
48-
return folly::dynamic(arg);
49-
} else if constexpr (std::is_same_v<T, int64_t>) {
50-
return folly::dynamic(static_cast<int64_t>(arg));
51-
} else if constexpr (std::is_same_v<T, std::string>) {
52-
return folly::dynamic(arg);
53-
} else if constexpr (std::is_same_v<T, nitro_ns::AnyArray>) {
54-
folly::dynamic arr = folly::dynamic::array();
55-
for (const auto &elem: arg) {
56-
const nitro_ns::VariantType &v = static_cast<const nitro_ns::VariantType &>(elem);
57-
arr.push_back(variantToDynamicWithHook(v, hook));
58-
}
59-
return arr;
60-
} else if constexpr (std::is_same_v<T, nitro_ns::AnyObject>) {
61-
folly::dynamic obj = folly::dynamic::object();
62-
for (const auto &kv: arg) {
63-
auto dynVal = variantToDynamicWithHook(
64-
static_cast<const nitro_ns::VariantType &>(kv.second),
65-
hook);
66-
if (containsColorInsensitive(kv.first)) {
67-
obj[kv.first] = hook(kv.first, dynVal);
68-
} else {
69-
obj[kv.first] = std::move(dynVal);
70-
}
35+
struct VariantConverter {
36+
static folly::dynamic convert(ShadowTreeUpdateManager &self,
37+
Runtime &runtime,
38+
const nitro_ns::VariantType &var) {
39+
return std::visit(
40+
[&self, &runtime](auto &&arg) -> folly::dynamic {
41+
using T = std::decay_t<decltype(arg)>;
42+
if constexpr (std::is_same_v<T, int64_t>) {
43+
return folly::dynamic(static_cast<int64_t>(arg));
44+
} else if constexpr (
45+
std::is_same_v<T, bool> ||
46+
std::is_same_v<T, double> ||
47+
std::is_same_v<T, std::string>
48+
) {
49+
return folly::dynamic(arg);
50+
} else if constexpr (std::is_same_v<T, nitro_ns::AnyArray>) {
51+
folly::dynamic arr = folly::dynamic::array();
52+
for (const auto &elem: arg) {
53+
const auto &v = static_cast<const nitro_ns::VariantType &>(elem);
54+
arr.push_back(convert(self, runtime, v));
55+
}
56+
return arr;
57+
} else if constexpr (std::is_same_v<T, nitro_ns::AnyObject>) {
58+
folly::dynamic obj = folly::dynamic::object();
59+
for (const auto &kv: arg) {
60+
auto dynVal = convert(self, runtime,
61+
static_cast<const nitro_ns::VariantType &>(kv.second));
62+
if (containsColorInsensitive(kv.first)) {
63+
obj[kv.first] = self.processColorDynamic(runtime, dynVal);
64+
} else {
65+
obj[kv.first] = std::move(dynVal);
7166
}
72-
return obj;
73-
} else {
74-
return folly::dynamic(nullptr);
7567
}
76-
},
77-
var);
78-
}
79-
80-
// Convert a single style entry (AnyMap) into an update object, applying color processing for string values on color keys
81-
folly::dynamic ShadowTreeUpdateManager::styleEntryToUpdate(Runtime &runtime,
82-
const nitro_ns::AnyMap &entry) {
83-
// Define a small key-aware transformer that applies string color processing when needed
84-
auto transform = [this, &runtime](const std::string &key,
85-
const folly::dynamic &value) -> folly::dynamic {
86-
if (!containsColorInsensitive(key)) return value;
87-
return processColorDynamic(runtime, value);
88-
};
89-
90-
folly::dynamic obj = folly::dynamic::object();
91-
const auto &map = entry.getMap();
92-
for (const auto &kv: map) {
93-
const nitro_ns::VariantType &var = static_cast<const nitro_ns::VariantType &>(kv.second);
94-
auto dynVal = variantToDynamicWithHook(var, transform);
95-
if (containsColorInsensitive(kv.first)) {
96-
obj[kv.first] = transform(kv.first, dynVal);
97-
} else {
98-
obj[kv.first] = std::move(dynVal);
99-
}
100-
}
101-
return obj;
102-
}
103-
104-
ShadowTreeUpdateManager::ShadowTreeUpdateManager() = default;
105-
106-
void ShadowTreeUpdateManager::linkComponent(Runtime &runtime,
107-
const std::string &componentId,
108-
facebook::react::Tag tag) {
109-
component_links_[componentId] = ComponentLink{tag, &runtime};
110-
ensureRuntimeEffect(runtime);
68+
return obj;
69+
} else {
70+
return {nullptr};
71+
}
72+
},
73+
var);
74+
}
75+
};
76+
77+
// Convert a single style entry (AnyMap) into an update object, applying color processing only for keys containing "color"
78+
folly::dynamic ShadowTreeUpdateManager::styleEntryToUpdate(Runtime &runtime,
79+
const nitro_ns::AnyMap &entry) {
80+
folly::dynamic obj = folly::dynamic::object();
81+
const auto &map = entry.getMap();
82+
for (const auto &kv: map) {
83+
const auto &var = static_cast<const nitro_ns::VariantType &>(kv.second);
84+
auto dynVal = VariantConverter::convert(*this, runtime, var);
85+
if (containsColorInsensitive(kv.first)) {
86+
obj[kv.first] = processColorDynamic(runtime, dynVal);
87+
} else {
88+
obj[kv.first] = std::move(dynVal);
11189
}
90+
}
91+
return obj;
92+
}
11293

113-
void ShadowTreeUpdateManager::unlinkComponent(const std::string &componentId) {
114-
auto it = component_links_.find(componentId);
115-
if (it != component_links_.end()) component_links_.erase(it);
116-
}
94+
ShadowTreeUpdateManager::ShadowTreeUpdateManager() = default;
11795

118-
void ShadowTreeUpdateManager::addUpdates(
119-
const std::string &componentId,
120-
const std::vector<std::shared_ptr<nitro_ns::AnyMap>> &styleEntries) {
121-
auto it = component_links_.find(componentId);
122-
if (it == component_links_.end()) return;
96+
void ShadowTreeUpdateManager::linkComponent(Runtime &runtime,
97+
const std::string &componentId,
98+
facebook::react::Tag tag) {
99+
component_links_[componentId] = ComponentLink{tag, &runtime};
100+
ensureRuntimeEffect(runtime);
101+
}
123102

124-
ComponentLink &link = it->second;
125-
if (link.runtime == nullptr) return;
103+
void ShadowTreeUpdateManager::unlinkComponent(const std::string &componentId) {
104+
auto it = component_links_.find(componentId);
105+
if (it != component_links_.end()) component_links_.erase(it);
106+
}
126107

127-
auto &obs = runtime_updates_[link.runtime];
128-
if (!obs) {
129-
obs = Observable<UpdatesMap>::create(UpdatesMap{});
130-
}
108+
void ShadowTreeUpdateManager::addUpdates(
109+
const std::string &componentId,
110+
const std::vector<std::shared_ptr<nitro_ns::AnyMap>> &styleEntries) {
111+
auto it = component_links_.find(componentId);
112+
if (it == component_links_.end()) return;
131113

132-
// Convert each style entry into a dynamic update object (merged into a single object per tag)
133-
folly::dynamic payload = folly::dynamic::object();
134-
for (const auto &p: styleEntries) {
135-
if (!p) {
136-
continue;
137-
}
138-
auto obj = styleEntryToUpdate(*link.runtime, *p);
139-
if (obj.isObject()) {
140-
for (auto &kv: obj.items()) {
141-
payload[kv.first] = std::move(kv.second);
142-
}
143-
}
144-
}
114+
ComponentLink &link = it->second;
115+
if (link.runtime == nullptr) return;
145116

146-
UpdatesMap cur = obs->get();
147-
cur[link.tag] = std::move(payload);
148-
obs->set(std::move(cur));
149-
}
117+
auto &obs = runtime_updates_[link.runtime];
118+
if (!obs) {
119+
obs = Observable<UpdatesMap>::create(UpdatesMap{});
120+
}
150121

151-
void ShadowTreeUpdateManager::registerProcessColorFunction(jsi::Function &&fn) {
152-
this->process_color_ = std::make_shared<jsi::Function>(std::move(fn));
153-
process_color_cache_.clear();
122+
// Convert each style entry into a dynamic update object (merged into a single object per tag)
123+
folly::dynamic payload = folly::dynamic::object();
124+
for (const auto &p: styleEntries) {
125+
if (!p) {
126+
continue;
154127
}
155-
156-
void ShadowTreeUpdateManager::ensureRuntimeEffect(Runtime &runtime) {
157-
auto *rt = &runtime;
158-
auto rtObsIt = runtime_updates_.find(rt);
159-
if (rtObsIt == runtime_updates_.end()) {
160-
auto obs = Observable<UpdatesMap>::create(UpdatesMap{});
161-
rtObsIt = runtime_updates_.emplace(rt, std::move(obs)).first;
162-
}
163-
if (runtime_effects_.find(rt) == runtime_effects_.end()) {
164-
auto obs = rtObsIt->second;
165-
auto holder = std::make_shared<RuntimeEffectHolder>();
166-
holder->effect = std::make_shared<reactnativecss::Effect>(
167-
[this, obs, rt, holder]() {
168-
const UpdatesMap &updates = obs->get(*holder->effect);
169-
if (updates.empty()) return;
170-
applyUpdates(*rt, updates);
171-
obs->set(UpdatesMap{});
172-
});
173-
holder->effect->run();
174-
runtime_effects_.emplace(rt, std::move(holder));
128+
auto obj = styleEntryToUpdate(*link.runtime, *p);
129+
if (obj.isObject()) {
130+
for (auto &kv: obj.items()) {
131+
payload[kv.first] = std::move(kv.second);
175132
}
176133
}
134+
}
177135

178-
// Process a single dynamic color value: if string -> call JSI fn (cached), else return as-is
179-
folly::dynamic ShadowTreeUpdateManager::processColorDynamic(Runtime &runtime,
180-
const folly::dynamic &value) {
181-
if (!value.isString()) {
182-
return value;
183-
}
184-
const std::string &colorStr = value.getString();
185-
auto it = process_color_cache_.find(colorStr);
186-
if (it != process_color_cache_.end()) {
187-
return folly::dynamic(it->second);
188-
}
189-
if (!process_color_) {
190-
return value;
191-
}
192-
jsi::String str = jsi::String::createFromUtf8(runtime, colorStr);
193-
jsi::Value result = process_color_->call(runtime, jsi::Value(runtime, str));
194-
if (!result.isNumber()) {
195-
return value;
196-
}
197-
int processed = static_cast<int>(result.asNumber());
198-
process_color_cache_.emplace(colorStr, processed);
199-
return folly::dynamic(processed);
200-
}
136+
UpdatesMap cur = obs->get();
137+
cur[link.tag] = std::move(payload);
138+
obs->set(std::move(cur));
139+
}
201140

202-
void
203-
ShadowTreeUpdateManager::applyUpdates(Runtime &runtime, const UpdatesMap &updates) {
204-
if (updates.empty()) return;
205-
auto binding = facebook::react::UIManagerBinding::getBinding(runtime);
206-
if (!binding) return;
207-
auto &uiManager = binding->getUIManager();
208-
uiManager.updateShadowTree(updates);
209-
}
141+
void ShadowTreeUpdateManager::registerProcessColorFunction(jsi::Function &&fn) {
142+
this->process_color_ = std::make_shared<jsi::Function>(std::move(fn));
143+
process_color_cache_.clear();
144+
}
210145

146+
void ShadowTreeUpdateManager::ensureRuntimeEffect(Runtime &runtime) {
147+
auto *rt = &runtime;
148+
auto rtObsIt = runtime_updates_.find(rt);
149+
if (rtObsIt == runtime_updates_.end()) {
150+
auto obs = Observable<UpdatesMap>::create(UpdatesMap{});
151+
rtObsIt = runtime_updates_.emplace(rt, std::move(obs)).first;
152+
}
153+
if (runtime_effects_.find(rt) == runtime_effects_.end()) {
154+
auto obs = rtObsIt->second;
155+
auto holder = std::make_shared<RuntimeEffectHolder>();
156+
holder->effect = std::make_shared<reactnativecss::Effect>(
157+
[obs, rt, holder]() {
158+
const ShadowTreeUpdateManager::UpdatesMap &updates = obs->get(
159+
*holder->effect);
160+
if (updates.empty()) return;
161+
ShadowTreeUpdateManager::applyUpdates(*rt, updates);
162+
obs->set(ShadowTreeUpdateManager::UpdatesMap{});
163+
});
164+
holder->effect->run();
165+
runtime_effects_.emplace(rt, std::move(holder));
211166
}
212167
}
168+
169+
// Process a single dynamic color value: if string -> call JSI fn (cached), else return as-is
170+
folly::dynamic ShadowTreeUpdateManager::processColorDynamic(Runtime &runtime,
171+
const folly::dynamic &value) {
172+
if (!value.isString()) {
173+
return value;
174+
}
175+
const std::string &colorStr = value.getString();
176+
auto it = process_color_cache_.find(colorStr);
177+
if (it != process_color_cache_.end()) {
178+
return {it->second};
179+
}
180+
if (!process_color_) {
181+
return value;
182+
}
183+
jsi::String str = jsi::String::createFromUtf8(runtime, colorStr);
184+
jsi::Value result = process_color_->call(runtime, jsi::Value(runtime, str));
185+
if (!result.isNumber()) {
186+
return value;
187+
}
188+
int processed = static_cast<int>(result.asNumber());
189+
process_color_cache_.emplace(colorStr, processed);
190+
return {processed};
191+
}
192+
193+
void
194+
ShadowTreeUpdateManager::applyUpdates(Runtime &runtime, const UpdatesMap &updates) {
195+
if (updates.empty()) return;
196+
auto binding = facebook::react::UIManagerBinding::getBinding(runtime);
197+
if (!binding) return;
198+
auto &uiManager = binding->getUIManager();
199+
uiManager.updateShadowTree(updates);
200+
}
201+
213202
} // namespace margelo::nitro::cssnitro

cpp/ShadowTreeUpdateManager.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ namespace margelo {
2626
namespace nitro {
2727
namespace cssnitro {
2828

29+
struct VariantConverter; // forward decl for friend
30+
2931
class ShadowTreeUpdateManager final {
3032
public:
3133
using UpdatesMap = std::unordered_map<facebook::react::Tag, folly::dynamic>;
@@ -45,6 +47,7 @@ namespace margelo {
4547
void registerProcessColorFunction(jsi::Function &&fn);
4648

4749
private:
50+
friend struct VariantConverter;
4851
struct ComponentLink {
4952
facebook::react::Tag tag{0};
5053
jsi::Runtime *runtime{nullptr};
@@ -67,7 +70,7 @@ namespace margelo {
6770

6871
void ensureRuntimeEffect(jsi::Runtime &runtime);
6972

70-
void applyUpdates(jsi::Runtime &runtime, const UpdatesMap &updates);
73+
static void applyUpdates(jsi::Runtime &runtime, const UpdatesMap &updates);
7174

7275
// String color processing (with caching)
7376
folly::dynamic

0 commit comments

Comments
 (0)