Skip to content

Commit c899766

Browse files
committed
Apply deferred Tier 2 findings (breaking changes)
1 parent 155b555 commit c899766

10 files changed

Lines changed: 214 additions & 497 deletions

File tree

examples/event_replay/main.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,28 @@ class EventRecorder : public PluginObserver {
3434

3535
// PluginObserver overrides
3636
void on_plugin_loaded(const std::string& name,
37-
const std::string&) override {
37+
const std::string&,
38+
const PluginEntry&) override {
3839
record("lifecycle", "loaded", name);
3940
}
4041
void on_plugin_unloaded(const std::string& name,
41-
const std::string&) override {
42+
const std::string&,
43+
const PluginEntry&) override {
4244
record("lifecycle", "unloaded", name);
4345
}
4446
void on_plugin_enabled(const std::string& name,
45-
const std::string&) override {
47+
const std::string&,
48+
const PluginEntry&) override {
4649
record("lifecycle", "enabled", name);
4750
}
4851
void on_plugin_disabled(const std::string& name,
49-
const std::string&) override {
52+
const std::string&,
53+
const PluginEntry&) override {
5054
record("lifecycle", "disabled", name);
5155
}
5256
void on_plugin_reloaded(const std::string& name,
53-
const std::string&) override {
57+
const std::string&,
58+
const PluginEntry&) override {
5459
record("lifecycle", "reloaded", name);
5560
}
5661

examples/metrics_observer/main.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,38 @@ using namespace plugin_arch;
2020
class MetricsObserver : public PluginObserver {
2121
public:
2222
void on_plugin_loaded(const std::string& name,
23-
const std::string&) override {
23+
const std::string&,
24+
const PluginEntry&) override {
2425
++total_loads_;
2526
++plugin_loads_[name];
2627
std::cout << " [metrics] loaded: " << name
2728
<< " (total loads: " << total_loads_ << ")\n";
2829
}
2930

3031
void on_plugin_unloaded(const std::string& name,
31-
const std::string&) override {
32+
const std::string&,
33+
const PluginEntry&) override {
3234
++total_unloads_;
3335
std::cout << " [metrics] unloaded: " << name << "\n";
3436
}
3537

3638
void on_plugin_enabled(const std::string& name,
37-
const std::string&) override {
39+
const std::string&,
40+
const PluginEntry&) override {
3841
++total_enables_;
3942
std::cout << " [metrics] enabled: " << name << "\n";
4043
}
4144

4245
void on_plugin_disabled(const std::string& name,
43-
const std::string&) override {
46+
const std::string&,
47+
const PluginEntry&) override {
4448
++total_disables_;
4549
std::cout << " [metrics] disabled: " << name << "\n";
4650
}
4751

4852
void on_plugin_reloaded(const std::string& name,
49-
const std::string&) override {
53+
const std::string&,
54+
const PluginEntry&) override {
5055
++total_reloads_;
5156
std::cout << " [metrics] reloaded: " << name << "\n";
5257
}

plugin_arch/PluginManager.hpp

Lines changed: 83 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ enum class LoadPolicy {
5757

5858
// Plugin metadata + dependency list — used for discovery and topological sort.
5959
// Public so tests and hosts can construct instances for PluginManager::add_plugin().
60-
struct PluginInfo {
60+
struct DiscoveredPlugin {
6161
PluginEntry entry;
6262
std::vector<std::string> deps; // type strings (version stripped, for topo sort)
6363
std::vector<std::string> version_constraints; // original strings (for version checking)
@@ -69,7 +69,7 @@ struct PluginInfo {
6969
// Throws on cycles or missing dependencies.
7070
// Free function so it can be tested independently.
7171
inline std::vector<std::vector<std::size_t>> topological_sort_levels(
72-
const std::vector<PluginInfo>& infos,
72+
const std::vector<DiscoveredPlugin>& infos,
7373
const std::unordered_map<std::string, std::size_t>& type_to_index) {
7474
const std::size_t n = infos.size();
7575

@@ -192,7 +192,7 @@ inline std::vector<std::vector<std::size_t>> topological_sort_levels(
192192
// Check version constraints between discovered plugins.
193193
// Throws in strict mode, records errors in best_effort mode.
194194
inline void check_version_constraints(
195-
const std::vector<PluginInfo>& infos,
195+
const std::vector<DiscoveredPlugin>& infos,
196196
const std::unordered_map<std::string, std::size_t>& type_to_index,
197197
LoadPolicy policy,
198198
std::vector<ErrorRecord>* errors_out) {
@@ -233,7 +233,7 @@ inline void check_version_constraints(
233233
// Check mutual exclusion constraints between discovered plugins.
234234
// Throws in strict mode, records errors in best_effort mode.
235235
inline void check_conflict_constraints(
236-
const std::vector<PluginInfo>& infos,
236+
const std::vector<DiscoveredPlugin>& infos,
237237
const std::unordered_map<std::string, std::size_t>& type_to_index,
238238
LoadPolicy policy,
239239
std::vector<ErrorRecord>* errors_out) {
@@ -305,7 +305,7 @@ inline void apply_config_defaults(IPlugin* instance, PluginConfig& config) {
305305
class PluginManager {
306306
public:
307307
PluginManager() = default;
308-
~PluginManager() = default;
308+
virtual ~PluginManager() = default;
309309

310310
PluginManager(const PluginManager&) = delete;
311311
PluginManager& operator=(const PluginManager&) = delete;
@@ -568,7 +568,7 @@ class PluginManager {
568568
{std::move(instance), std::nullopt, entry, std::move(deps)});
569569
rebuild_name_index();
570570
wire_instance_tracked(plugins_.back(), config_map, /*skip_validation=*/true);
571-
notify_loaded(entry.name, entry.type);
571+
notify_loaded(entry.name, entry.type, entry);
572572

573573
}
574574

@@ -593,12 +593,12 @@ class PluginManager {
593593
// Shutdown reverse dependents
594594
for (std::size_t idx : dep_indices) {
595595
shutdown_plugin(plugins_[idx]);
596-
notify_unloaded(plugins_[idx].entry.name, plugins_[idx].entry.type);
596+
notify_unloaded(plugins_[idx].entry.name, plugins_[idx].entry.type, plugins_[idx].entry);
597597
}
598598

599599
// Shutdown the target
600600
shutdown_plugin(plugins_[it->second]);
601-
notify_unloaded(name, target_type);
601+
notify_unloaded(name, target_type, plugins_[it->second].entry);
602602

603603
// Collect all indices to remove
604604
std::unordered_set<std::size_t> to_remove(dep_indices.begin(),
@@ -688,7 +688,7 @@ class PluginManager {
688688
wire_instance_tracked(plugins_[idx], config_map);
689689
}
690690

691-
notify_reloaded(name, target_type);
691+
notify_reloaded(name, target_type, target.entry);
692692

693693
}
694694

@@ -754,7 +754,7 @@ class PluginManager {
754754
lp.enabled = true;
755755
rebuild_locator();
756756
wire_instance_tracked(lp, config_map);
757-
notify_enabled(lp.entry.name, lp.entry.type);
757+
notify_enabled(lp.entry.name, lp.entry.type, lp.entry);
758758
}
759759

760760
// Check if a plugin is enabled.
@@ -961,20 +961,20 @@ class PluginManager {
961961

962962
// --- Observer notification ---
963963

964-
void notify_loaded(const std::string& name, const std::string& type) {
965-
for (auto* obs : observers_) { try { obs->on_plugin_loaded(name, type); } catch (...) {} }
964+
void notify_loaded(const std::string& name, const std::string& type, const PluginEntry& entry) {
965+
for (auto* obs : observers_) { try { obs->on_plugin_loaded(name, type, entry); } catch (...) {} }
966966
}
967-
void notify_unloaded(const std::string& name, const std::string& type) {
968-
for (auto* obs : observers_) { try { obs->on_plugin_unloaded(name, type); } catch (...) {} }
967+
void notify_unloaded(const std::string& name, const std::string& type, const PluginEntry& entry) {
968+
for (auto* obs : observers_) { try { obs->on_plugin_unloaded(name, type, entry); } catch (...) {} }
969969
}
970-
void notify_reloaded(const std::string& name, const std::string& type) {
971-
for (auto* obs : observers_) { try { obs->on_plugin_reloaded(name, type); } catch (...) {} }
970+
void notify_reloaded(const std::string& name, const std::string& type, const PluginEntry& entry) {
971+
for (auto* obs : observers_) { try { obs->on_plugin_reloaded(name, type, entry); } catch (...) {} }
972972
}
973-
void notify_enabled(const std::string& name, const std::string& type) {
974-
for (auto* obs : observers_) { try { obs->on_plugin_enabled(name, type); } catch (...) {} }
973+
void notify_enabled(const std::string& name, const std::string& type, const PluginEntry& entry) {
974+
for (auto* obs : observers_) { try { obs->on_plugin_enabled(name, type, entry); } catch (...) {} }
975975
}
976-
void notify_disabled(const std::string& name, const std::string& type) {
977-
for (auto* obs : observers_) { try { obs->on_plugin_disabled(name, type); } catch (...) {} }
976+
void notify_disabled(const std::string& name, const std::string& type, const PluginEntry& entry) {
977+
for (auto* obs : observers_) { try { obs->on_plugin_disabled(name, type, entry); } catch (...) {} }
978978
}
979979

980980
// --- Name index ---
@@ -1008,7 +1008,7 @@ class PluginManager {
10081008
}
10091009
lp.subscription_ids.clear();
10101010
lp.enabled = false;
1011-
notify_disabled(lp.entry.name, lp.entry.type);
1011+
notify_disabled(lp.entry.name, lp.entry.type, lp.entry);
10121012
}
10131013

10141014
// --- Reverse dependency graph ---
@@ -1048,25 +1048,10 @@ class PluginManager {
10481048
return result;
10491049
}
10501050

1051-
// --- Plugin shutdown ---
1052-
1053-
// Calls on_shutdown() if the plugin implements ILifecycleAware.
1054-
// Swallows exceptions — one bad plugin must not prevent others from
1055-
// shutting down (same principle as EventBus::publish).
1056-
static void shutdown_plugin(LoadedPlugin& lp) {
1057-
auto* lifecycle = dynamic_cast<ILifecycleAware*>(lp.instance.get());
1058-
if (lifecycle) {
1059-
try {
1060-
lifecycle->on_shutdown();
1061-
} catch (...) {
1062-
}
1063-
}
1064-
}
1065-
10661051
// --- Discovery ---
10671052

10681053
struct DiscoveryResult {
1069-
std::vector<PluginInfo> infos;
1054+
std::vector<DiscoveredPlugin> infos;
10701055
std::vector<std::vector<std::size_t>> levels;
10711056
};
10721057

@@ -1077,11 +1062,11 @@ class PluginManager {
10771062
const auto& entries = registry.entries();
10781063
if (entries.empty()) return {};
10791064

1080-
std::vector<PluginInfo> infos;
1065+
std::vector<DiscoveredPlugin> infos;
10811066
std::unordered_map<std::string, std::size_t> type_to_index;
10821067

10831068
for (const auto& e : entries) {
1084-
PluginInfo info;
1069+
DiscoveredPlugin info;
10851070
info.entry = e;
10861071

10871072
// Probe for dependencies and conflicts (only typed plugins)
@@ -1134,15 +1119,15 @@ class PluginManager {
11341119
// --- Dependency cascade ---
11351120

11361121
static bool has_failed_dep(
1137-
const PluginInfo& info,
1122+
const DiscoveredPlugin& info,
11381123
const std::unordered_set<std::string>& failed_types) {
11391124
for (const auto& dep : info.deps) {
11401125
if (failed_types.contains(dep)) return true;
11411126
}
11421127
return false;
11431128
}
11441129

1145-
void record_cascade_skip(const PluginInfo& info,
1130+
void record_cascade_skip(const DiscoveredPlugin& info,
11461131
std::unordered_set<std::string>& failed_types) {
11471132
failed_types.insert(info.entry.type);
11481133
load_errors_.push_back(
@@ -1167,29 +1152,6 @@ class PluginManager {
11671152
}
11681153
}
11691154

1170-
void load_and_wire(const PluginEntry& entry,
1171-
const std::vector<std::string>& deps,
1172-
const ConfigMap& config_map) {
1173-
std::shared_ptr<IPlugin> instance;
1174-
std::optional<PluginLoader<IPlugin>> loader;
1175-
1176-
if (entry.is_dynamic) {
1177-
instance = DynamicPluginAdapter::load(entry.path.string());
1178-
} else {
1179-
PluginLoader<IPlugin> pl(entry.path.string());
1180-
instance = pl.get_instance();
1181-
loader = std::move(pl);
1182-
}
1183-
1184-
locator_.add(instance);
1185-
plugins_.push_back(
1186-
{std::move(instance), std::move(loader), entry, deps});
1187-
rebuild_name_index();
1188-
wire_instance_tracked(plugins_.back(), config_map);
1189-
notify_loaded(entry.name, entry.type);
1190-
1191-
}
1192-
11931155
// Resolve, validate, and apply config for a plugin.
11941156
// Returns the final config if the plugin is configurable, or nullopt.
11951157
static std::optional<PluginConfig> resolve_plugin_config(
@@ -1219,9 +1181,33 @@ class PluginManager {
12191181
return config;
12201182
}
12211183

1184+
// Wire + track EventBus subscription IDs (for enable/disable).
1185+
//
1186+
// Relies on EventBus::next_id_ being monotonically increasing with no
1187+
// reuse: all IDs allocated between [before, after) belong to this plugin.
1188+
// This holds because PluginManager is single-threaded (or exclusively
1189+
// locked via ThreadSafe), and wire_instance may call on_init() /
1190+
// set_event_bus() which subscribe — those subscriptions correctly belong
1191+
// to this plugin. Unsubscribes during wiring don't break the invariant
1192+
// because IDs are never recycled.
1193+
void wire_instance_tracked(LoadedPlugin& lp, const ConfigMap& config_map,
1194+
bool skip_validation = false) {
1195+
auto before = event_bus_.next_subscription_id();
1196+
wire_instance(lp.instance, lp.entry, config_map, skip_validation);
1197+
auto after = event_bus_.next_subscription_id();
1198+
1199+
lp.subscription_ids.clear();
1200+
for (auto id = before; id < after; ++id) {
1201+
lp.subscription_ids.push_back(id);
1202+
}
1203+
}
1204+
1205+
protected:
1206+
// --- Extensibility points (override in subclasses) ---
1207+
12221208
// Wire all opt-in mixins on an instance.
12231209
// Order: validate+configure → inject services → inject events → custom wirers → init.
1224-
void wire_instance(const std::shared_ptr<IPlugin>& instance,
1210+
virtual void wire_instance(const std::shared_ptr<IPlugin>& instance,
12251211
const PluginEntry& entry, const ConfigMap& config_map,
12261212
bool skip_validation = false) {
12271213
auto resolved = resolve_plugin_config(
@@ -1252,25 +1238,40 @@ class PluginManager {
12521238
}
12531239
}
12541240

1255-
// Wire + track EventBus subscription IDs (for enable/disable).
1256-
//
1257-
// Relies on EventBus::next_id_ being monotonically increasing with no
1258-
// reuse: all IDs allocated between [before, after) belong to this plugin.
1259-
// This holds because PluginManager is single-threaded (or exclusively
1260-
// locked via ThreadSafe), and wire_instance may call on_init() /
1261-
// set_event_bus() which subscribe — those subscriptions correctly belong
1262-
// to this plugin. Unsubscribes during wiring don't break the invariant
1263-
// because IDs are never recycled.
1264-
void wire_instance_tracked(LoadedPlugin& lp, const ConfigMap& config_map,
1265-
bool skip_validation = false) {
1266-
auto before = event_bus_.next_subscription_id();
1267-
wire_instance(lp.instance, lp.entry, config_map, skip_validation);
1268-
auto after = event_bus_.next_subscription_id();
1241+
// Calls on_shutdown() if the plugin implements ILifecycleAware.
1242+
// Swallows exceptions — one bad plugin must not prevent others from
1243+
// shutting down (same principle as EventBus::publish).
1244+
virtual void shutdown_plugin(LoadedPlugin& lp) {
1245+
auto* lifecycle = dynamic_cast<ILifecycleAware*>(lp.instance.get());
1246+
if (lifecycle) {
1247+
try {
1248+
lifecycle->on_shutdown();
1249+
} catch (...) {
1250+
}
1251+
}
1252+
}
12691253

1270-
lp.subscription_ids.clear();
1271-
for (auto id = before; id < after; ++id) {
1272-
lp.subscription_ids.push_back(id);
1254+
// Load a plugin from disk and wire it into the manager.
1255+
virtual void load_and_wire(const PluginEntry& entry,
1256+
const std::vector<std::string>& deps,
1257+
const ConfigMap& config_map) {
1258+
std::shared_ptr<IPlugin> instance;
1259+
std::optional<PluginLoader<IPlugin>> loader;
1260+
1261+
if (entry.is_dynamic) {
1262+
instance = DynamicPluginAdapter::load(entry.path.string());
1263+
} else {
1264+
PluginLoader<IPlugin> pl(entry.path.string());
1265+
instance = pl.get_instance();
1266+
loader = std::move(pl);
12731267
}
1268+
1269+
locator_.add(instance);
1270+
plugins_.push_back(
1271+
{std::move(instance), std::move(loader), entry, deps});
1272+
rebuild_name_index();
1273+
wire_instance_tracked(plugins_.back(), config_map);
1274+
notify_loaded(entry.name, entry.type, entry);
12741275
}
12751276
};
12761277

0 commit comments

Comments
 (0)