Skip to content

test(meta): raise meta coverage to >=90% + fix ffi CallbackRegistry crash#4

Open
MaxQian888 wants to merge 35 commits into
ElementAstro:devfrom
MaxQian888:dev
Open

test(meta): raise meta coverage to >=90% + fix ffi CallbackRegistry crash#4
MaxQian888 wants to merge 35 commits into
ElementAstro:devfrom
MaxQian888:dev

Conversation

@MaxQian888

@MaxQian888 MaxQian888 commented Jun 11, 2026

Copy link
Copy Markdown

Summary

Brings the atom/meta test suite to comprehensive, 1:1-with-headers coverage and fixes two latent bugs in ffi.hpp uncovered along the way.

Tests

  • Add the previously-missing tests/meta/reflection/test_refl_field.cpp (covers FieldBase: construction/defaults, member-pointer binding, validators, and the C++23 deducing-this builders incl. derived-type preservation).
  • Expand the meta test suites so every header with runtime code reaches ≥90% deduplicated source-line coverage (33 of 34 at 92.5%–100%). Coverage was measured per-test to avoid gcov's per-instantiation line inflation, which otherwise makes template headers report 5–15× their real line count.
  • facade_any stops at its reachable ceiling (~88%): the remaining lines are defensive proxy-call catch blocks and the never-invoked less_than_impl, unreachable without fault injection.
  • Full suite: 1233 test cases, 0 failures across all 37 meta test executables.

ffi fixes

  • CallbackRegistry SIGSEGV: registerCallback stored the raw closure type in std::any while getCallback<Sig> retrieved it as std::function<Sig>; the pointer-form any_cast returned nullptr on the mismatch (it does not throw, so the surrounding catch was dead code) and the null was wrapped in a success result, so callers dereferenced null and crashed. Now the callable is wrapped in a std::function with the deduced signature (sync + async paths), and getCallback null-guards to return TypeMismatch.
  • Lazy getHandle: DynamicLibrary::getHandle now honours the load strategy like getFunction (Lazy/Immediate load on access; OnDemand still needs an explicit loadLibrary()).

Note on scope

This branch also carries 6 pre-existing branch-sync / pre-commit.ci bot commits that were already on local dev but not yet on upstream dev; they appear in the diff but are not part of the substantive change above.

🤖 Generated with Claude Code


Summary by cubic

Raises atom/meta runtime-header coverage to ≥90% and fixes an ffi callback crash. Adds hot-reloadable components via meta::DynamicLibrary, switches scripting to Python, and adopts atom::type::expected for serialization results.

  • New Features

    • Implemented hot reload: Registry::loadComponentFromFile loads modules via meta::DynamicLibrary (opt-in ATOM_COMPONENTS_ENABLE_HOT_RELOAD/hot_reload).
    • Added tests/meta/reflection/test_refl_field.cpp and split shared field logic into atom/meta/refl_field.hpp.
    • Completed the components event system with Event, EventCallback, EventCallbackId in core/types.hpp; enabled events by default; added Registry::registerComponentInstance.
  • Refactors and Fixes

    • Scripting: replaced phantom ChaiScript with real Python support and updated language detection.
    • Serialization: APIs now return atom::type::expected (SerializationOutcome/DeserializationOutcome); structured SerializationError added.
    • Type module: moved public symbols into atom::type (e.g., NonCopyable, StaticString, Trackable, PointerSentinel) and updated call sites.
    • Containers: components optionally uses atom::containers fast containers when ATOM_USE_BOOST_CONTAINERS is set.
    • FFI: CallbackRegistry stores std::function<Sig> and returns TypeMismatch on signature mismatch; DynamicLibrary::getHandle respects load strategy.
    • Consolidated “advanced_*” variants: components bindings moved to scripting/bindings; system/command’s advanced executor merged into executor with a new command/command.hpp umbrella.
    • Fixed module macros and re-enabled types/macros tests; demoted hot-path logging to trace/debug; formatted exception messages via fmt::format; removed dead components/data/type_conversion.hpp and the Abseil dependency from package.hpp.
    • Misc: added missing standard headers; aligned event data correctly; updated docs (WARP.md, components/CLAUDE.md) and bumped pre-commit-hooks to v6.0.0.

Written for commit f17f583. Summary will update on new commits.

Review in cubic

pre-commit-ci Bot and others added 8 commits August 11, 2025 18:16
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v6.0.0](pre-commit/pre-commit-hooks@v4.6.0...v6.0.0)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v6.0.0](pre-commit/pre-commit-hooks@v4.6.0...v6.0.0)
Add the previously-missing test_refl_field.cpp covering FieldBase:
construction/defaults, member-pointer binding, validators, and the C++23
deducing-this builders including derived-type preservation through
chaining.

Expand the meta test suites so every header with runtime code reaches at
least 90% deduplicated source-line coverage (measured per-test to avoid
gcov's per-instantiation line inflation). facade_any stops at its
reachable ceiling: the remaining lines are defensive proxy-call catch
blocks and the never-invoked less_than_impl, which cannot be reached
without fault injection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CallbackRegistry stored the raw closure type in std::any while
getCallback<Sig> retrieved it as std::function<Sig>. The pointer-form
any_cast then returned nullptr on the type mismatch (it does not throw,
so the surrounding catch was dead code), and getCallback wrapped that
null pointer in a success result, so callers dereferenced null and
crashed with SIGSEGV.

Store the callable wrapped in a std::function with the deduced signature
for both the sync and async paths (a private registerAsyncImpl recovers
R(Args...) so the async entry is std::function<future<R>(Args...)>), and
null-guard getCallback so a signature mismatch returns TypeMismatch.

Also make DynamicLibrary::getHandle honour the load strategy like
getFunction: Lazy and Immediate load on access, OnDemand still requires
an explicit loadLibrary().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @MaxQian888, your pull request is larger than the review limit of 150000 diff characters

pre-commit-ci Bot and others added 15 commits June 11, 2026 03:44
Survey of atom/components found namespace chaos, a dead event system
referencing undefined types, broken module macros, 6 disabled test
files, trait/JSON duplication vs atom/meta and atom/type, and CMake
debt. Spec defines 7 incremental work packages keeping the 332-test
baseline green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
14 tasks covering the 7 work packages from the design spec, each
gated on rebuild + test run against the 332-test baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fix mis-indented reset()/updateExecutionTime()/legacy getter bodies and
drop the MSVC-vs-GCC constexpr fork: atomics cannot be constexpr-reset,
so a single plain noexcept variant is correct on all compilers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dex_sequence

The 98-line manual arity-0..8 if-constexpr ladder, textually included
inside Component::def via #include, is replaced by a single
std::index_sequence generic-lambda expansion over FunctionTraits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d code

OP_*/CONDITION_*/REGISTER_OPERATOR leaked from the public header into
every consumer. registerOperators now uses std::equality_comparable /
std::totally_ordered directly; the DEF_MEMBER_FUNC* helper macros are
#undef-ed after their last expansion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-dispatch and per-variable spdlog::info calls flooded output (every
command execution logged 5+ info lines). Command/variable hot paths now
log at trace, per-component registry operations at debug; lifecycle-level
messages stay at info.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
atom::error::Exception stream-concatenates its args, so every THROW_*
call using fmt-style placeholders produced messages like
"Precondition failed for command '"'"'{}'"'"'alwaysFail". Module-local THROW
macros now wrap fmt::format (matching THROW_REGISTRY_EXCEPTION, which
already did), and atom/error macro call sites format explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…acros tests

ATOM_MODULE_INIT and ATOM_HOT_COMPONENT passed 0-arg factory lambdas
where Component::InitFunc (void(Component&)) was expected, so any
ATOM_MODULE/ATOM_EMBED_MODULE expansion failed to compile, and
ATOM_VERSION was never defined. Registry gains
registerComponentInstance() to register externally created instances
(the missing API the macros needed). The disabled types_and_macros test
file targeted macros that never existed; rewritten against the real
ones (ATOM_MODULE, ATOM_EMBED_MODULE, REGISTER_INITIALIZER,
ATOM_COMPONENT). Tests: 332 -> 344.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The event system (Component::emitEvent/on/once/off and registry-level
subscribe/trigger) referenced atom::components::Event, EventCallback
and EventCallbackId, which were defined nowhere, so the code behind
ENABLE_EVENT_SYSTEM could never compile. Define those types in
core/types.hpp, add the ATOM_COMPONENTS_ENABLE_EVENTS CMake option
(default ON), and fix two self-deadlocks the dead code hid: cleanupAll
and initializeAll trigger events while holding mutex_, so event
subscriptions now use a dedicated eventMutex_. Tests: 344 -> 349.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
data/type_conversion.hpp declared a TypeConverter/TypeRegistry template
API with zero definitions (link error on any use) and re-implemented
container/optional/smart-pointer/tuple traits that atom/meta already
provides. Nothing in the module used it (advanced_bindings only
included it), and its test was disabled for calling those non-existent
methods. Removed outright per the no-parallel-dead-code convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
absl::StrContains was the only abseil use in the whole repository;
std::string_view::find does the same job in a constexpr context.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
refactor: drop "advanced" qualifier and consolidate parallel files

Merge the "advanced_*" implementations into their base counterparts and
delete the redundant parallel files across the components, system, and
meta modules, following the convention that qualifier words like
"advanced" do not belong in names.

- components: advanced_bindings -> scripting/bindings
- system/command: fold advanced_executor into the executor; add command.hpp
- system/env: fold env_advanced into env_core/utils
- system/shortcut: advanced_shortcut -> shortcut_binding
- system/scheduling, system/process: drop dead duplicate sources
- meta: split refl_field.hpp out of refl; reorganize tests into
  per-area subdirectories and remove the old flat test files

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 193 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="atom/meta/constructor.hpp">

<violation number="1" location="atom/meta/constructor.hpp:547">
P2: Missing direct `#include <vector>` for newly introduced `std::vector` usage in `ObjectBuilder`</violation>
</file>

<file name="atom/meta/raw_name.hpp">

<violation number="1" location="atom/meta/raw_name.hpp:137">
P2: GCC member-name parser truncates overloaded operator member names like `operator()` due to forward search for `)` or `}` delimiters. The `find_first_of(")}", start)` call stops at the `)` inside `operator()` itself, returning `operator(` instead of the full name.</violation>
</file>

Note: This PR contains a large number of files. cubic only reviews up to 100 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread atom/meta/constructor.hpp
@@ -435,30 +435,13 @@ auto asyncConstructor(std::launch policy = std::launch::async) {
template <typename Class, bool ThreadSafe = true>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing direct #include <vector> for newly introduced std::vector usage in ObjectBuilder

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/meta/constructor.hpp, line 547:

<comment>Missing direct `#include <vector>` for newly introduced `std::vector` usage in `ObjectBuilder`</comment>

<file context>
@@ -559,40 +542,45 @@ auto factoryConstructor() {
-    std::function<std::shared_ptr<Class>()> m_buildFunc;
+    // Steps are applied in order at build(); storing them flat avoids the
+    // O(n^2) closure-chain copies of composing a new lambda per step.
+    std::vector<std::function<void(Class&)>> m_steps;
 
 public:
</file context>

Comment thread atom/meta/raw_name.hpp
if (auto start = value.rfind("::"); start != std::string_view::npos) {
start += 2;
auto end = name.rfind('}');
auto end = value.find_first_of(")}", start);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: GCC member-name parser truncates overloaded operator member names like operator() due to forward search for ) or } delimiters. The find_first_of(")}", start) call stops at the ) inside operator() itself, returning operator( instead of the full name.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/meta/raw_name.hpp, line 137:

<comment>GCC member-name parser truncates overloaded operator member names like `operator()` due to forward search for `)` or `}` delimiters. The `find_first_of(")}", start)` call stops at the `)` inside `operator()` itself, returning `operator(` instead of the full name.</comment>

<file context>
@@ -108,19 +127,22 @@ constexpr std::string_view extract_enum_name(std::string_view name) noexcept {
+    if (auto start = value.rfind("::"); start != std::string_view::npos) {
         start += 2;
-        auto end = name.rfind('}');
+        auto end = value.find_first_of(")}", start);
         if (end == std::string_view::npos) {
-            end = name.size();
</file context>
Suggested change
auto end = value.find_first_of(")}", start);
auto end = value.find_last_of(")}");

MaxQian888 and others added 5 commits July 11, 2026 10:12
concept.hpp used std::complex (ComplexNumber concept) without <complex>, and
difflib.cpp used std::setprecision without <iomanip>. Both were latent after
recent refactors and only surfaced when the components module / its tests pull
these translation units in. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The USE_BOOST_CONTAINERS branch in var.hpp and the ATOM_USE_BOOST_CONTAINERS
branch in dispatch.hpp referenced namespaces that do not exist
(atom::components::containers::flat_map, atom::container::string_hash_set),
so the branches failed to compile whenever the macro was defined.

Point both at the real high-performance container module (atom::containers,
atom/containers/boost_containers.hpp): flat_map / flat_set for var, and
fast_unordered_map / fast_unordered_set for dispatch. Standardize var.hpp on
the ATOM_USE_BOOST_CONTAINERS macro to match dispatch.hpp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ScriptLanguage and the engine factory advertised a ChaiScript backend that was
never implemented, while the actually-shipped Python engine had no explicit
createEngine case (only reachable via Auto). Rename the enumerator to Python,
give it a real PythonEngineFactory branch, and switch language detection to the
.py extension and Python content heuristics. Drop the unused ATOM_ENABLE_CHAISCRIPT
test toggle and update the affected assertions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…amicLibrary

Registry::loadComponentFromFile was a stub that logged "not implemented yet".
Implement it by reusing atom::meta::DynamicLibrary (atom/meta/ffi.hpp): load the
shared object, resolve and call the C entry point exported by ATOM_MODULE
(<name>_initialize_registry), retain the library handle for the component's
lifetime, and record the module version from <name>_getVersion.

Add an ATOM_COMPONENTS_ENABLE_HOT_RELOAD option (CMake + xmake, default OFF) so
the ENABLE_HOT_RELOAD path is actually buildable, link CMAKE_DL_LIBS / dl for
dlopen, and add the previously-missing <filesystem>/<future> includes guarded by
the same macro.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous doc referenced deleted files (advanced_bindings, type_conversion)
and APIs that do not exist (getInstance, setProperty, a Var value type,
Dispatcher::subscribe). Replace it with the real Component / Registry /
CommandDispatcher / VariableManager interfaces, the atom::meta + atom::type
reuse, ScriptLanguage{Lua,Python,Auto}, and the new hot-reload option.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MaxQian888 and others added 7 commits July 11, 2026 10:12
atom::type::expected stores the error as Error<E>, so reaching the underlying
value required the awkward .error().error() double access used in ~160 call
sites. Add error_value() (const& and && overloads) that returns E directly,
reusing error()'s bounds check. Non-breaking: error() and the existing double
access are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xpected

Replace the bool-success/errorMessage result structs with
expected<Payload, SerializationError>. SerializationResult /
DeserializationResult now hold only the success payload (data/metadata);
failures carry a structured SerializationError (code + message) reached via
the new expected::error_value(). ISerializer, JsonSerializer, BinarySerializer
and SerializationManager return SerializationOutcome / DeserializationOutcome.

Callers switch from result.success / result.errorMessage to
result.has_value() / result.error_value().message and reach the payload via
operator->. Tests and the serialization example are updated accordingly.

BREAKING CHANGE: serialize/deserialize now return expected<> instead of the
old result structs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
alignas(64) was attached to the EventHandler struct definition, where the
attribute is ignored (GCC warned), so the intended "separate cache line" for
the event-system data never took effect. Move the alignment onto the first
event-system data member (m_EventHandlers_) so it actually starts on its own
cache line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ding

The InitializeAndCleanup and Reinitialize tests were commented out because
they reused the name "Component1"; since Registry::instance() is a singleton
and addInitializer skips already-registered names, they contaminated each
other. Give each a unique name and update the lambdas to the current
InitFunc(Component&) signature.

Add error-path coverage for the new Registry::loadComponentFromFile (missing
file and non-library file both fail), guarded by ENABLE_HOT_RELOAD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Several type-module symbols (NonCopyable, StaticString, Trackable,
PointerSentinel, and siblings) previously lived in the global namespace,
polluting every consumer's namespace and risking name clashes. They now
live under atom::type like the rest of the module.

Consumers across algorithm, async, components, connection, meta, and
utils are updated to qualify the symbols (or add a using-declaration)
so they keep compiling. The module CLAUDE.md and header list are updated
to match.

BREAKING CHANGE: type-module symbols that were in the global namespace
(NonCopyable, StaticString, Trackable, PointerSentinel, and siblings)
now live in namespace atom::type. External code must qualify them as
atom::type::<name> or add a using-declaration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-header test_*.hpp suites are only reachable through
test_header_only.cpp, but most were missing from that aggregation and so
silently never compiled. Wire all 21 header-only suites in, and split
rtype into its own translation unit -- its `using namespace atom::meta`
makes the unqualified `detail` ambiguous inside the shared TU where an
earlier `using namespace atom::type` is still active.

Aggregating every suite into one TU overruns the COFF 2^16 section limit
on MinGW (spurious "undefined reference to .refptr.*" link errors), so
enable big-object output (-Wa,-mbig-obj on Windows, /bigobj on MSVC).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every example under example/type was corrupted: comment text was glued
onto the following statement (e.g. "// demo handlingclass Foo {"), so
none of them compiled. Rewrite each as a focused, compiling demonstration
of its type. The net result is far smaller and actually runnable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

27 issues found across 127 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="atom/type/robin_hood.hpp">

<violation number="1" location="atom/type/robin_hood.hpp:376">
P1: The write lock added to `insert()` only serializes that single method, leaving every other public access to `table_` unprotected. Both overloads of `find`, `at`, all iterator accessors, `clear`, and `max_load_factor(float)` read or mutate the underlying storage without acquiring `lock_read()` or `lock_write()`. In a concurrent workload, a `find` on one thread can race with an `insert` + `rehash` on another because `rehash` move-assigns `table_` while `find` is still indexing into the old vector. The `reader_lock` and `mutex` threading policies are therefore broken for any pattern beyond single-threaded `insert`-only use. A consistent locking policy should be applied across every public method that touches `table_`, or the policy enum should be removed if thread safety is not intended.</violation>

<violation number="2" location="atom/type/robin_hood.hpp:418">
P0: Default-key lookups can return the wrong element because the new empty-slot-skipping iterator is constructed for a bucket that `find` incorrectly accepts as a match. Empty slots have `dist == 0` and a default-constructed key. Because `find` starts with `dist == 0`, the termination check `table_[idx].dist < dist` is false for an empty ideal bucket, so `find` proceeds to `key_equal_`. If the searched key happens to be the default value, `key_equal_` succeeds and `find` returns an iterator to the empty bucket. The new iterator constructor then calls `skip_empty()`, advancing past the empty slot to the next occupied entry. This means `find(Key{})` can return an iterator to a completely different key, and `at(Key{})` can return the wrong value without throwing. The same issue exists in the const overload.</violation>
</file>

<file name="atom/type/concurrent_map.hpp">

<violation number="1" location="atom/type/concurrent_map.hpp:38">
P2: `ConcurrentMapError` hard-codes `ATOM_FILE_NAME`, `ATOM_FILE_LINE`, and `ATOM_FUNC_NAME` inside its constructor definition, so these macros expand at the class declaration site rather than at each `throw ConcurrentMapError(...)` expression. Every thrown instance therefore records the same fixed file/line/function regardless of which operation failed, defeating the framework exception metadata.

The codebase already has a correct pattern for this in `concurrent_vector.hpp`: a `THROW_CONCURRENT_VECTOR_ERROR` macro passes the macros at the throw site. Consider adding an equivalent `THROW_CONCURRENT_MAP_ERROR(...)` macro and using it at all throw sites instead of `throw ConcurrentMapError(...)`.</violation>

<violation number="2" location="atom/type/concurrent_map.hpp:871">
P0: Calling `adjust_thread_pool_size` from a task running on this pool's own worker thread causes a self-join: `std::thread::join()` throws `std::system_error(resource_deadlock_would_occur)` when invoked on the current thread. The exception leaves `stop_pool = true`, skips the restart block, and permanently disables the executor. Future `submit` calls will all reject with "Thread pool is stopped". Consider rejecting (or deferring) resize requests that originate from a worker thread, or skip `join()` for the calling thread and account for it in the restart logic.</violation>
</file>

<file name="atom/components/data/serialization.cpp">

<violation number="1" location="atom/components/data/serialization.cpp:73">
P2: The `DeserializeFailed` code returned here conflates all JSON deserialization exceptions, including malformed JSON (`nlohmann::json::parse_error`) and type mismatches (`nlohmann::json::type_error`), with genuine component reconstruction failures. The header's own taxonomy defines `InvalidData` for "Input data was malformed or truncated", so JSON parse/type errors should be reported as `InvalidData` so callers can differentiate corrupt input from internal decoding errors. Adding a preceding catch clause for `nlohmann::json::exception` would align the JSON serializer with the documented taxonomy and with the Binary serializer's choice of `InvalidData` for analogous errors.</violation>

<violation number="2" location="atom/components/data/serialization.cpp:334">
P2: Failed serialization and deserialization attempts are no longer counted in the aggregate timing statistics. Previously, `totalSerializationTime` and `totalDeserializationTime` included the elapsed time of every attempt (successful or failed). After the move to `SerializationOutcome`, the error paths return a `SerializationError` with no duration field, and the manager only adds timing inside `outcome.has_value()`. Since `totalSerializations`/`totalDeserializations` are still incremented unconditionally, derived metrics like average time become artificially low and failure-performance analysis is lost.

Consider measuring elapsed time in `SerializationManager` around the serializer call and always adding it to the total, or including a duration field in `SerializationError` so it can be propagated from the serializer on failure.</violation>
</file>

<file name="atom/components/CMakeLists.txt">

<violation number="1" location="atom/components/CMakeLists.txt:78">
P1: The hot-reload `watchComponentChanges` stop path can deadlock. `watchComponentChanges(false)` in `atom/components/core/registry.cpp` acquires a `std::unique_lock` at function scope while the async worker can try to acquire a `shared_lock` on the same mutex between its `while` check and lock acquisition. If the unique lock is held when the worker tries to get a shared lock, the worker blocks forever, and the caller then blocks forever on `fileWatcherFuture_.wait()` without ever releasing the unique lock. Consider unlocking the mutex before waiting on the future: release `unique_lock` before calling `fileWatcherFuture_.wait()`.</violation>
</file>

<file name="atom/type/concurrent_set.hpp">

<violation number="1" location="atom/type/concurrent_set.hpp:38">
P2: The `ConcurrentSetException` hierarchy hard-codes `ATOM_FILE_NAME`, `ATOM_FILE_LINE`, and `ATOM_FUNC_NAME` inside the class constructor definitions. These macros expand at the constructor's source location (inside `concurrent_set.hpp`) rather than at each actual throw site. Consequently, `getFile()`, `getLine()`, and `getFunction()` always return the same fixed location for every operation failure, defeating the framework's diagnostic metadata. Since the PR intent is to integrate with `atom::error::Exception` for framework error handling, the captured location should reflect the failing operation, not the exception class definition.</violation>

<violation number="2" location="atom/type/concurrent_set.hpp:1118">
P1: The `transaction` method snapshots `data` under a shared lock, releases it while running operations, and then restores the stale snapshot on rollback. This silently overwrites any concurrent modifications made by other threads between the snapshot and the rollback, violating the claimed atomicity. The rollback should either use change-based undo, acquire an exclusive lock for the entire operation window (avoiding the recursive-lock deadlock some other way), or the method should document that it does not provide isolation.</violation>
</file>

<file name="example/type/monadic_error_handling_example.cpp">

<violation number="1" location="example/type/monadic_error_handling_example.cpp:4">
P2: The file-level docstring claims this example demonstrates `and_then / map / or_else` chains, but the code never calls `or_else`. An example whose purpose is to document the API should match the operations it says it demonstrates. Either add an `or_else` recovery path to the chain or remove `or_else` from the header description.</violation>

<violation number="2" location="example/type/monadic_error_handling_example.cpp:22">
P2: The `parse` helper silently accepts numeric-prefix input (e.g., `"4oops"` parses as `4`) because `std::stoi` stops at the first non-numeric character when no consumed-length argument is supplied. This contradicts the example's purpose of demonstrating error-handling short-circuits with `expected`. The adjacent `example/type/expected.cpp` already validates the consumed length with `consumed != text.size()`, confirming this is the intended pattern. Pass a `pos` argument to `std::stoi` and return an error when `pos != s.size()`.</violation>
</file>

<file name="atom/type/flatmap.hpp">

<violation number="1" location="atom/type/flatmap.hpp:205">
P0: The newly-reachable large-container lookup path uses `std::lower_bound` on the internal `std::vector`, but the vector is maintained in insertion order (elements are appended by `insert_or_assign` and `operator[]`), not sorted by the comparator. Binary search on an unsorted range produces incorrect results: existing keys can be missed, leading to duplicate insertions and false negatives from `find`/`at`/`contains`. The previous `std::execution::par_unseq` overload prevented this branch from compiling on conforming implementations; removing the execution policy makes the bug reachable. Consider removing this threshold branch so all non-Boost lookups use linear (or SIMD) search, or alternatively ensure the vector stays sorted on insertion if `lower_bound` is intended.</violation>
</file>

<file name="atom/type/no_offset_ptr.hpp">

<violation number="1" location="atom/type/no_offset_ptr.hpp:248">
P1: The `reset` method advertises a strong exception guarantee in its comment, but when `T` is move-constructible with a throwing move constructor, `destroy()` runs before the placement-new move, so if the move throws the old value is already lost and the pointer is left empty. This also regresses behavior for types where direct `T(args...)` would succeed but moving throws: the old code could reset them, the new code cannot.

Consider switching the branch guard from `std::is_move_constructible_v<T>` to `std::is_nothrow_move_constructible_v<T>` so that only types whose move is guaranteed not to throw use the two-phase strategy, and types with a potentially-throwing move fall back to the basic-guarantee in-place path.</violation>
</file>

<file name="example/type/concurrent_vector.cpp">

<violation number="1" location="example/type/concurrent_vector.cpp:17">
P2: The single-argument constructor usage `ConcurrentVector<int> v(2);` is misdocumented as "2 worker threads". Evidence from the old example code shows the two-argument constructor is `(initial_capacity, thread_count)` and the single-argument overload is used as capacity (e.g. `conc_vec(benchmark_size)`). This means `v(2)` creates a capacity-2 vector with the default thread count, not a two-worker pool as the comment claims. Pass both arguments explicitly (e.g. `ConcurrentVector<int> v(0, 2);`) or update the comment to match the actual behavior.</violation>
</file>

<file name="atom/components/CLAUDE.md">

<violation number="1" location="atom/components/CLAUDE.md:149">
P2: The `ATOM_MODULE` example references `MyComponent`, which is never defined in this rewritten document. The preceding examples all use the `Counter` class introduced earlier. This makes the registration snippet inconsistent with the surrounding documentation and uncompilable if copied. Consider using `Counter` here instead, or explicitly defining `MyComponent` if a distinct example type is intended.</violation>
</file>

<file name="atom/type/weak_ptr.hpp">

<violation number="1" location="atom/type/weak_ptr.hpp:655">
P1: `tryLockPeriodic` is documented and named as a fixed-interval retry helper, but it delegates to `tryLockWithRetry`, which performs exponential backoff (multiplying the interval by `2^attempt` after each failure). Callers expecting regular periodic polling will instead get increasingly long delays. Consider either implementing a true fixed-interval loop inside `tryLockPeriodic`, or renaming and re-documenting the method to reflect the exponential-backoff behavior.</violation>

<violation number="2" location="atom/type/weak_ptr.hpp:842">
P2: `batchOperation` switched from the standard library feature-test macro `__cpp_lib_parallel_algorithm` to a project-specific `ATOM_USE_PARALLEL_ALGORITHMS` macro, but that macro is not defined anywhere in the build system or source code. This means builds that previously compiled the `std::for_each(std::execution::par_unseq, ...)` branch whenever the standard library supported parallel algorithms will now silently fall back to sequential processing for large batches. Either restore the feature-test macro (matching `small_vector.hpp` and `bit.hpp`), define `ATOM_USE_PARALLEL_ALGORITHMS` consistently in the build system, or align all headers on the same guard.</violation>
</file>

<file name="atom/components/xmake.lua">

<violation number="1" location="atom/components/xmake.lua:120">
P2: Enabling `hot_reload` defines `ENABLE_HOT_RELOAD=1`, which activates a watcher loop in `atom/components/core/registry.cpp` that calls `std::this_thread::sleep_for`. Neither `registry.cpp` nor `registry.hpp` directly includes `<thread>`; they rely on `<future>` (included conditionally) transitively providing it, which is not portable. Other files in the project that use the same API already include `<thread>` explicitly (e.g., `scripting_api.cpp`). To make the hot-reload build reliable across toolchains, add `#include <thread>` to `registry.cpp` or `registry.hpp`.</violation>
</file>

<file name="atom/type/concurrent_vector.hpp">

<violation number="1" location="atom/type/concurrent_vector.hpp:1021">
P2: Templating `submit_task` to accept move-only callables removed the previous guard against empty `std::function` tasks. When an empty `std::function` is passed, a `packaged_task` is queued and later executed by the worker. `packaged_task::operator()` internally catches the resulting `std::bad_function_call` and stores it in its inaccessible shared state rather than propagating it, so the worker’s own exception-capture logic never fires and the caller never learns of the failure. Consider restoring an emptiness check for `std::function`-like inputs (e.g. with `if constexpr (std::is_same_v<std::decay_t<F>, std::function<void()>>)`) or restricting `submit_task` to internal use if direct public calls with empty callables are not supported.</violation>
</file>

<file name="atom/type/rtype.hpp">

<violation number="1" location="atom/type/rtype.hpp:28">
P1: The new `StringSequence` concept allows deserializing into `std::vector<std::string_view>` because `StringType` includes `std::string_view`. `JsonValue::as_string()` returns a `const std::string&` backed by the JSON parser's internal storage. When that storage is destroyed (e.g., the temporary parsed object goes out of scope), the stored `string_view`s become dangling, leading to use-after-free. Consider narrowing `StringSequence` to exclude non-owning string types such as `std::string_view`.</violation>

<violation number="2" location="atom/type/rtype.hpp:144">
P1: The `from_json` and `from_yaml` deserializers cast untrusted parsed `double` values directly to the reflected member type with `static_cast<MemberType>(it->second.as_number())`. Because `Number<MemberType>` matches all arithmetic types (including unsigned and narrow integrals), this introduces undefined behavior when the parsed value is out of range for the destination type, negative for an unsigned member, NaN, or infinity. The optional validator runs after the cast, so it cannot guard against the UB. Range-checking or clamping the double before casting would make deserialization safe.</violation>
</file>

<file name="atom/type/uint.hpp">

<violation number="1" location="atom/type/uint.hpp:30">
P2: The switch from `throw std::out_of_range(...)` to `THROW_OUT_OF_RANGE(...)` changes the exception type from `std::out_of_range` to `atom::error::OutOfRange`, which does **not** derive from `std::out_of_range`. Callers catching `std::out_of_range` will silently miss the exception. The doc comments (`@throws std::out_of_range`) are now stale too. Either update the documentation to `@throws atom::error::OutOfRange` and note the contract change, or keep the standard exception to preserve backward compatibility.</violation>
</file>

<file name="atom/components/core/registry.cpp">

<violation number="1" location="atom/components/core/registry.cpp:523">
P1: The component name derived from `fs::path(path).stem()` will include the conventional Unix `lib` prefix (e.g., `libfoo` for `libfoo.so`), but the expected exported symbols use the bare module name (e.g., `foo_initialize_registry`). This causes symbol lookups to fail on Linux/macOS for conventionally-named shared libraries.

Consider stripping the `lib` prefix from the stem when it is present, or using a platform-aware extraction so the symbol name matches the module's actual export:</violation>

<violation number="2" location="atom/components/core/registry.cpp:558">
P1: The new library loading code in `loadComponentFromFile` correctly opens a `DynamicLibrary` and stores it in `loadedLibraries_[name]`, but the file-change handler `watchComponentChanges` only calls `reinitializeComponent` when a change is detected. `reinitializeComponent` re-runs the cached initializer and cleanup functions without ever unloading the old shared object or loading the updated binary from disk. As a result, the "hot reload" path executes the old library's code after a file change rather than the new one. To support actual hot reloading, the watcher should reload the shared object (updating `loadedLibraries_[name]`) and re-resolve the entry points before reinitializing the component.</violation>

<violation number="3" location="atom/components/core/registry.cpp:562">
P1: Components loaded from a file won't be watched for hot-reload changes because `loadComponentFromFile` never records the source path in `ComponentInfo::configPath`. The file watcher checks that field and silently skips components when it's empty, so any library loaded here will never trigger a reload. Recording `path` in `info.configPath` inside the post-load critical section fixes the gap.</violation>
</file>

<file name="atom/type/iter.hpp">

<violation number="1" location="atom/type/iter.hpp:531">
P1: `ZipIterator` now risks undefined behavior when the primary (first) range is longer than any non-primary range. `operator++()` increments every iterator unconditionally, and `operator*()` dereferences every iterator unconditionally, yet `operator==()` only checks the first iterator against its end. If a later range exhausts first, the next increment and dereference of that iterator are UB. The previous all-tuple comparison also failed for unequal-length ranges, but it caused an infinite loop rather than a memory-safety issue.

Consider either (a) tracking end iterators so termination can stop at the shortest range, or (b) adding runtime assertions in `operator++`/`operator*` when `ENABLE_DEBUG` is defined so callers detect precondition violations early. At minimum, the class docs should state that the first passed range must be the shortest.</violation>
</file>

<file name="atom/type/optional.hpp">

<violation number="1" location="atom/type/optional.hpp:403">
P1: The rvalue-ref-qualified `value_or` now calls `storage_.reset()` after moving the contained value, which destroys the standard library–compatible semantics of this accessor. `std::optional::value_or` and this class's own `value() &&` / `operator*() &&` all return the moved value without disengaging the optional. After this change, code that calls `std::move(opt).value_or(x)` and later checks `opt.has_value()` will unexpectedly see it empty. Remove the `storage_.reset()` line to restore the previous behavior, or introduce a separate named operation if destructive consumption is intentional.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread atom/type/robin_hood.hpp
@@ -378,7 +415,7 @@ class unordered_flat_map {
}
if (table_[idx].dist == dist &&
key_equal_(table_[idx].data.first, key)) {
return iterator(table_.begin() + idx);
return iterator(table_.begin() + idx, table_.end());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Default-key lookups can return the wrong element because the new empty-slot-skipping iterator is constructed for a bucket that find incorrectly accepts as a match. Empty slots have dist == 0 and a default-constructed key. Because find starts with dist == 0, the termination check table_[idx].dist < dist is false for an empty ideal bucket, so find proceeds to key_equal_. If the searched key happens to be the default value, key_equal_ succeeds and find returns an iterator to the empty bucket. The new iterator constructor then calls skip_empty(), advancing past the empty slot to the next occupied entry. This means find(Key{}) can return an iterator to a completely different key, and at(Key{}) can return the wrong value without throwing. The same issue exists in the const overload.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/robin_hood.hpp, line 418:

<comment>Default-key lookups can return the wrong element because the new empty-slot-skipping iterator is constructed for a bucket that `find` incorrectly accepts as a match. Empty slots have `dist == 0` and a default-constructed key. Because `find` starts with `dist == 0`, the termination check `table_[idx].dist < dist` is false for an empty ideal bucket, so `find` proceeds to `key_equal_`. If the searched key happens to be the default value, `key_equal_` succeeds and `find` returns an iterator to the empty bucket. The new iterator constructor then calls `skip_empty()`, advancing past the empty slot to the next occupied entry. This means `find(Key{})` can return an iterator to a completely different key, and `at(Key{})` can return the wrong value without throwing. The same issue exists in the const overload.</comment>

<file context>
@@ -378,7 +415,7 @@ class unordered_flat_map {
             if (table_[idx].dist == dist &&
                 key_equal_(table_[idx].data.first, key)) {
-                return iterator(table_.begin() + idx);
+                return iterator(table_.begin() + idx, table_.end());
             }
             idx = (idx + 1) & mask;
</file context>

}
}
pool_cv.notify_all();
for (auto& t : thread_pool) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Calling adjust_thread_pool_size from a task running on this pool's own worker thread causes a self-join: std::thread::join() throws std::system_error(resource_deadlock_would_occur) when invoked on the current thread. The exception leaves stop_pool = true, skips the restart block, and permanently disables the executor. Future submit calls will all reject with "Thread pool is stopped". Consider rejecting (or deferring) resize requests that originate from a worker thread, or skip join() for the calling thread and account for it in the restart logic.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/concurrent_map.hpp, line 871:

<comment>Calling `adjust_thread_pool_size` from a task running on this pool's own worker thread causes a self-join: `std::thread::join()` throws `std::system_error(resource_deadlock_would_occur)` when invoked on the current thread. The exception leaves `stop_pool = true`, skips the restart block, and permanently disables the executor. Future `submit` calls will all reject with "Thread pool is stopped". Consider rejecting (or deferring) resize requests that originate from a worker thread, or skip `join()` for the calling thread and account for it in the restart logic.</comment>

<file context>
@@ -784,50 +852,40 @@ class concurrent_map {
-                    }
+            }
+            pool_cv.notify_all();
+            for (auto& t : thread_pool) {
+                if (t && t->joinable()) {
+                    t->join();
</file context>

Comment thread atom/type/flatmap.hpp
data_.end(), key,
// NOTE: std::lower_bound has no parallel execution-policy overload
// (unlike sort/find); it is already O(log n) binary search.
auto it = std::lower_bound(data_.begin(), data_.end(), key,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: The newly-reachable large-container lookup path uses std::lower_bound on the internal std::vector, but the vector is maintained in insertion order (elements are appended by insert_or_assign and operator[]), not sorted by the comparator. Binary search on an unsorted range produces incorrect results: existing keys can be missed, leading to duplicate insertions and false negatives from find/at/contains. The previous std::execution::par_unseq overload prevented this branch from compiling on conforming implementations; removing the execution policy makes the bug reachable. Consider removing this threshold branch so all non-Boost lookups use linear (or SIMD) search, or alternatively ensure the vector stays sorted on insertion if lower_bound is intended.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/flatmap.hpp, line 205:

<comment>The newly-reachable large-container lookup path uses `std::lower_bound` on the internal `std::vector`, but the vector is maintained in insertion order (elements are appended by `insert_or_assign` and `operator[]`), not sorted by the comparator. Binary search on an unsorted range produces incorrect results: existing keys can be missed, leading to duplicate insertions and false negatives from `find`/`at`/`contains`. The previous `std::execution::par_unseq` overload prevented this branch from compiling on conforming implementations; removing the execution policy makes the bug reachable. Consider removing this threshold branch so all non-Boost lookups use linear (or SIMD) search, or alternatively ensure the vector stays sorted on insertion if `lower_bound` is intended.</comment>

<file context>
@@ -199,8 +200,9 @@ class FlatMap {
-                                       data_.end(), key,
+            // NOTE: std::lower_bound has no parallel execution-policy overload
+            // (unlike sort/find); it is already O(log n) binary search.
+            auto it = std::lower_bound(data_.begin(), data_.end(), key,
                                        [this](const auto& pair, const auto& k) {
                                            return comp_(pair.first, k);
</file context>

Comment thread atom/type/robin_hood.hpp
// Serialize writers per the threading policy. rehash() does not take a
// lock itself, so calling it here does not re-enter (the mutex is
// non-recursive).
lock_write();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The write lock added to insert() only serializes that single method, leaving every other public access to table_ unprotected. Both overloads of find, at, all iterator accessors, clear, and max_load_factor(float) read or mutate the underlying storage without acquiring lock_read() or lock_write(). In a concurrent workload, a find on one thread can race with an insert + rehash on another because rehash move-assigns table_ while find is still indexing into the old vector. The reader_lock and mutex threading policies are therefore broken for any pattern beyond single-threaded insert-only use. A consistent locking policy should be applied across every public method that touches table_, or the policy enum should be removed if thread safety is not intended.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/robin_hood.hpp, line 376:

<comment>The write lock added to `insert()` only serializes that single method, leaving every other public access to `table_` unprotected. Both overloads of `find`, `at`, all iterator accessors, `clear`, and `max_load_factor(float)` read or mutate the underlying storage without acquiring `lock_read()` or `lock_write()`. In a concurrent workload, a `find` on one thread can race with an `insert` + `rehash` on another because `rehash` move-assigns `table_` while `find` is still indexing into the old vector. The `reader_lock` and `mutex` threading policies are therefore broken for any pattern beyond single-threaded `insert`-only use. A consistent locking policy should be applied across every public method that touches `table_`, or the policy enum should be removed if thread safety is not intended.</comment>

<file context>
@@ -339,6 +370,12 @@ class unordered_flat_map {
+        // Serialize writers per the threading policy. rehash() does not take a
+        // lock itself, so calling it here does not re-enter (the mutex is
+        // non-recursive).
+        lock_write();
+        WriteUnlock guard{this};
+
</file context>


# Hot reload: load components from shared libraries at runtime
# (Registry::loadComponentFromFile / watchComponentChanges).
option(ATOM_COMPONENTS_ENABLE_HOT_RELOAD

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The hot-reload watchComponentChanges stop path can deadlock. watchComponentChanges(false) in atom/components/core/registry.cpp acquires a std::unique_lock at function scope while the async worker can try to acquire a shared_lock on the same mutex between its while check and lock acquisition. If the unique lock is held when the worker tries to get a shared lock, the worker blocks forever, and the caller then blocks forever on fileWatcherFuture_.wait() without ever releasing the unique lock. Consider unlocking the mutex before waiting on the future: release unique_lock before calling fileWatcherFuture_.wait().

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/components/CMakeLists.txt, line 78:

<comment>The hot-reload `watchComponentChanges` stop path can deadlock. `watchComponentChanges(false)` in `atom/components/core/registry.cpp` acquires a `std::unique_lock` at function scope while the async worker can try to acquire a `shared_lock` on the same mutex between its `while` check and lock acquisition. If the unique lock is held when the worker tries to get a shared lock, the worker blocks forever, and the caller then blocks forever on `fileWatcherFuture_.wait()` without ever releasing the unique lock. Consider unlocking the mutex before waiting on the future: release `unique_lock` before calling `fileWatcherFuture_.wait()`.</comment>

<file context>
@@ -73,6 +73,11 @@ option(ATOM_ENABLE_PYTHON "Enable Python scripting support" OFF)
 
+# Hot reload: load components from shared libraries at runtime
+# (Registry::loadComponentFromFile / watchComponentChanges).
+option(ATOM_COMPONENTS_ENABLE_HOT_RELOAD
+       "Enable runtime loading of components from shared libraries" OFF)
+
</file context>

Comment thread atom/components/CLAUDE.md
lifecycle.initializeAll(); // Call onInitialize()
lifecycle.updateAll(0.016f); // Call onUpdate() with delta time
lifecycle.shutdownAll(); // Call onShutdown()
ATOM_MODULE(my_module, [] { return std::make_shared<MyComponent>("my_module"); });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The ATOM_MODULE example references MyComponent, which is never defined in this rewritten document. The preceding examples all use the Counter class introduced earlier. This makes the registration snippet inconsistent with the surrounding documentation and uncompilable if copied. Consider using Counter here instead, or explicitly defining MyComponent if a distinct example type is intended.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/components/CLAUDE.md, line 149:

<comment>The `ATOM_MODULE` example references `MyComponent`, which is never defined in this rewritten document. The preceding examples all use the `Counter` class introduced earlier. This makes the registration snippet inconsistent with the surrounding documentation and uncompilable if copied. Consider using `Counter` here instead, or explicitly defining `MyComponent` if a distinct example type is intended.</comment>

<file context>
@@ -14,611 +14,284 @@
-lifecycle.initializeAll();  // Call onInitialize()
-lifecycle.updateAll(0.016f); // Call onUpdate() with delta time
-lifecycle.shutdownAll();   // Call onShutdown()
+ATOM_MODULE(my_module, [] { return std::make_shared<MyComponent>("my_module"); });
+// exports C entry points: my_module_initialize_registry / _cleanup_registry /
+// _getInstance / _getVersion — used by Registry::loadComponentFromFile.
</file context>
Suggested change
ATOM_MODULE(my_module, [] { return std::make_shared<MyComponent>("my_module"); });
ATOM_MODULE(my_module, [] { return std::make_shared<Counter>("my_module"); });

Comment thread atom/type/weak_ptr.hpp
size_t successCount = 0;

#ifdef __cpp_lib_parallel_algorithm
#ifdef ATOM_USE_PARALLEL_ALGORITHMS

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: batchOperation switched from the standard library feature-test macro __cpp_lib_parallel_algorithm to a project-specific ATOM_USE_PARALLEL_ALGORITHMS macro, but that macro is not defined anywhere in the build system or source code. This means builds that previously compiled the std::for_each(std::execution::par_unseq, ...) branch whenever the standard library supported parallel algorithms will now silently fall back to sequential processing for large batches. Either restore the feature-test macro (matching small_vector.hpp and bit.hpp), define ATOM_USE_PARALLEL_ALGORITHMS consistently in the build system, or align all headers on the same guard.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/weak_ptr.hpp, line 842:

<comment>`batchOperation` switched from the standard library feature-test macro `__cpp_lib_parallel_algorithm` to a project-specific `ATOM_USE_PARALLEL_ALGORITHMS` macro, but that macro is not defined anywhere in the build system or source code. This means builds that previously compiled the `std::for_each(std::execution::par_unseq, ...)` branch whenever the standard library supported parallel algorithms will now silently fall back to sequential processing for large batches. Either restore the feature-test macro (matching `small_vector.hpp` and `bit.hpp`), define `ATOM_USE_PARALLEL_ALGORITHMS` consistently in the build system, or align all headers on the same guard.</comment>

<file context>
@@ -746,12 +834,12 @@ template <typename T>
     size_t successCount = 0;
 
-#ifdef __cpp_lib_parallel_algorithm
+#ifdef ATOM_USE_PARALLEL_ALGORITHMS
     if (parallelThreshold > 0 && weakPtrs.size() >= parallelThreshold) {
         std::atomic<size_t> atomicCount{0};
</file context>
Suggested change
#ifdef ATOM_USE_PARALLEL_ALGORITHMS
#ifdef __cpp_lib_parallel_algorithm

Comment thread atom/components/xmake.lua
add_defines("ATOM_ENABLE_PYTHON=1")
end

if has_config("hot_reload") then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Enabling hot_reload defines ENABLE_HOT_RELOAD=1, which activates a watcher loop in atom/components/core/registry.cpp that calls std::this_thread::sleep_for. Neither registry.cpp nor registry.hpp directly includes <thread>; they rely on <future> (included conditionally) transitively providing it, which is not portable. Other files in the project that use the same API already include <thread> explicitly (e.g., scripting_api.cpp). To make the hot-reload build reliable across toolchains, add #include <thread> to registry.cpp or registry.hpp.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/components/xmake.lua, line 120:

<comment>Enabling `hot_reload` defines `ENABLE_HOT_RELOAD=1`, which activates a watcher loop in `atom/components/core/registry.cpp` that calls `std::this_thread::sleep_for`. Neither `registry.cpp` nor `registry.hpp` directly includes `<thread>`; they rely on `<future>` (included conditionally) transitively providing it, which is not portable. Other files in the project that use the same API already include `<thread>` explicitly (e.g., `scripting_api.cpp`). To make the hot-reload build reliable across toolchains, add `#include <thread>` to `registry.cpp` or `registry.hpp`.</comment>

<file context>
@@ -111,6 +117,10 @@ target("atom-component")
         add_defines("ATOM_ENABLE_PYTHON=1")
     end
 
+    if has_config("hot_reload") then
+        add_defines("ENABLE_HOT_RELOAD=1")
+    end
</file context>

// std::promise). A std::function parameter would reject them since
// std::function requires its target to be copy-constructible.
template <typename F>
void submit_task(F&& task) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Templating submit_task to accept move-only callables removed the previous guard against empty std::function tasks. When an empty std::function is passed, a packaged_task is queued and later executed by the worker. packaged_task::operator() internally catches the resulting std::bad_function_call and stores it in its inaccessible shared state rather than propagating it, so the worker’s own exception-capture logic never fires and the caller never learns of the failure. Consider restoring an emptiness check for std::function-like inputs (e.g. with if constexpr (std::is_same_v<std::decay_t<F>, std::function<void()>>)) or restricting submit_task to internal use if direct public calls with empty callables are not supported.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/concurrent_vector.hpp, line 1021:

<comment>Templating `submit_task` to accept move-only callables removed the previous guard against empty `std::function` tasks. When an empty `std::function` is passed, a `packaged_task` is queued and later executed by the worker. `packaged_task::operator()` internally catches the resulting `std::bad_function_call` and stores it in its inaccessible shared state rather than propagating it, so the worker’s own exception-capture logic never fires and the caller never learns of the failure. Consider restoring an emptiness check for `std::function`-like inputs (e.g. with `if constexpr (std::is_same_v<std::decay_t<F>, std::function<void()>>)`) or restricting `submit_task` to internal use if direct public calls with empty callables are not supported.</comment>

<file context>
@@ -983,12 +1014,12 @@ class concurrent_vector {
+    // std::promise). A std::function parameter would reject them since
+    // std::function requires its target to be copy-constructible.
+    template <typename F>
+    void submit_task(F&& task) {
+        std::packaged_task<void()> packaged(std::forward<F>(task));
         {
</file context>

Comment thread atom/type/uint.hpp
@@ -24,7 +27,7 @@ constexpr uint32_t MAX_UINT32 = 0xFFFFFFFF;
*/
constexpr auto operator""_u8(unsigned long long value) -> uint8_t {
if (value > MAX_UINT8) { // uint8_t maximum value is 0xFF
throw std::out_of_range("Value exceeds uint8_t range");
THROW_OUT_OF_RANGE("Value exceeds uint8_t range");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The switch from throw std::out_of_range(...) to THROW_OUT_OF_RANGE(...) changes the exception type from std::out_of_range to atom::error::OutOfRange, which does not derive from std::out_of_range. Callers catching std::out_of_range will silently miss the exception. The doc comments (@throws std::out_of_range) are now stale too. Either update the documentation to @throws atom::error::OutOfRange and note the contract change, or keep the standard exception to preserve backward compatibility.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/uint.hpp, line 30:

<comment>The switch from `throw std::out_of_range(...)` to `THROW_OUT_OF_RANGE(...)` changes the exception type from `std::out_of_range` to `atom::error::OutOfRange`, which does **not** derive from `std::out_of_range`. Callers catching `std::out_of_range` will silently miss the exception. The doc comments (`@throws std::out_of_range`) are now stale too. Either update the documentation to `@throws atom::error::OutOfRange` and note the contract change, or keep the standard exception to preserve backward compatibility.</comment>

<file context>
@@ -24,7 +27,7 @@ constexpr uint32_t MAX_UINT32 = 0xFFFFFFFF;
 constexpr auto operator""_u8(unsigned long long value) -> uint8_t {
     if (value > MAX_UINT8) {  // uint8_t maximum value is 0xFF
-        throw std::out_of_range("Value exceeds uint8_t range");
+        THROW_OUT_OF_RANGE("Value exceeds uint8_t range");
     }
     return static_cast<uint8_t>(value);
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant