Skip to content

Feature/#113#114

Merged
p14c31355 merged 14 commits intodevelopfrom
feature/#113
Sep 1, 2025
Merged

Feature/#113#114
p14c31355 merged 14 commits intodevelopfrom
feature/#113

Conversation

@p14c31355
Copy link
Owner

🚀 Pull Request

Overview

Change details

  • New feature
  • Refactoring
  • Bug fix
  • CI / Build settings correction
  • Documentation update

Build / Test Results

$ cargo check     #
$ cargo test      #

Target board with confirmed operation

  • ATmega328p
  • ESP32
  • STM32
  • Linux mock
  • Other: ___

@p14c31355 p14c31355 self-assigned this Sep 1, 2025
@p14c31355 p14c31355 added the enhancement New feature or request label Sep 1, 2025
@p14c31355 p14c31355 linked an issue Sep 1, 2025 that may be closed by this pull request
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @p14c31355, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on enhancing the explorer module by introducing new macros that simplify the definition and initialization of command sequences and explorers, making the API more user-friendly. A key improvement for embedded environments is the optimization of the topological sort algorithm, which now avoids dynamic memory allocations by utilizing fixed-size arrays. These changes collectively aim to improve code maintainability, performance, and resource management within the exploration framework.

Highlights

  • New Macro Definitions: Introduced nodes!, new_explorer!, factorial_sort!, pruning_sort!, and get_one_sort! macros to simplify API usage and enable compile-time constant evaluation for Explorer initialization and command execution flows.
  • Performance Optimization for Embedded Systems: Refactored the topological sort algorithm in get_one_sort (formerly get_one_topological_sort_buf) to replace heapless::Vec with fixed-size arrays for graph representation and queue management. This significantly reduces dynamic memory allocations, improving performance and predictability on embedded targets.
  • API Refinements and Consistency: Renamed execute_and_log_command to exec_logger and updated Explorer struct to use nodes instead of sequence for better clarity. Explorer runner functions were also updated to integrate with the new macros and internalize I2C address scanning, streamlining their usage.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the explorer logic to use compile-time constants and macros for defining command nodes, which is a great improvement for embedded systems. It also optimizes the topological sort algorithm. My review focuses on a few critical issues that will prevent compilation, such as unbound lifetimes and incorrect macro definitions. I've also pointed out some areas for improving code clarity and consistency, like renaming variables and functions, and standardizing error logging. Overall, these are solid improvements, and with a few fixes, this will be a great addition.

Comment on lines 10 to 13
($explorer:expr, $i2c:expr, $serial:expr, $prefix:expr, $init_sequence:expr) => {
factorial_explorer::<
_, _, N, INIT_SEQUENCE_LEN, CMD_BUFFER_SIZE
>($explorer, $i2c, $serial, $prefix, $init_sequence)
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The const generic parameters N, INIT_SEQUENCE_LEN, and CMD_BUFFER_SIZE are used here but are not captured from the macro's arguments. This will cause a compilation error as they are undefined in the macro's expansion context. These values should be passed as arguments to the macro.

    ($explorer:expr, $i2c:expr, $serial:expr, $prefix:expr, $init_sequence:expr, $N:expr, $INIT_LEN:expr, $CMD_BUF:expr) => {
        factorial_explorer::<_, _, {$N}, {$INIT_LEN}, {$CMD_BUF}>($explorer, $i2c, $serial, $prefix, $init_sequence)
    }

Comment on lines 95 to 98
($explorer:expr, $i2c:expr, $serial:expr, $prefix:expr, $init_sequence:expr) => {
pruning_explorer::<
_, _, N, INIT_SEQUENCE_LEN, CMD_BUFFER_SIZE
>($explorer, $i2c, $serial, $prefix, $init_sequence)
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The const generic parameters N, INIT_SEQUENCE_LEN, and CMD_BUFFER_SIZE are used here but are not captured from the macro's arguments. This will cause a compilation error as they are undefined in the macro's expansion context. These values should be passed as arguments to the macro.

    ($explorer:expr, $i2c:expr, $serial:expr, $prefix:expr, $init_sequence:expr, $N:expr, $INIT_LEN:expr, $CMD_BUF:expr) => {
        pruning_explorer::<_, _, {$N}, {$INIT_LEN}, {$CMD_BUF}>($explorer, $i2c, $serial, $prefix, $init_sequence)
    }

Comment on lines 226 to 231
macro_rules! get_one_sort {
($explorer:expr, $i2c:expr, $serial:expr, $prefix:expr) => {
one_topological_explorer::<
_, _, INIT_SEQUENCE_LEN, INIT_SEQUENCE_LEN, CMD_BUFFER_SIZE
>($explorer, $i2c, $serial, $prefix)
};
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

This macro has two issues. First, like the other new macros, it uses uncaptured const generic parameters (INIT_SEQUENCE_LEN, CMD_BUFFER_SIZE). Second, it incorrectly uses INIT_SEQUENCE_LEN for the N const generic parameter of one_topological_explorer. N should be the number of nodes in the explorer, not the length of the init sequence. This will likely lead to incorrect behavior or compilation errors.

macro_rules! get_one_sort {
    ($explorer:expr, $i2c:expr, $serial:expr, $prefix:expr, $N:expr, $INIT_LEN:expr, $CMD_BUF:expr) => {
        one_topological_explorer::<_, _, {$N}, {$INIT_LEN}, {$CMD_BUF}>($explorer, $i2c, $serial, $prefix)
    };
}

}

pub fn execute_and_log_command<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(
pub fn exec_logger<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The function execute_and_log_command was renamed to exec_logger. While shorter, exec_logger is less descriptive and its purpose is not immediately clear. Consider a more descriptive name that reflects its action, such as execute_command_and_log, or reverting to the original name. Clear naming is important for maintainability.

Suggested change
pub fn exec_logger<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(
pub fn execute_command_and_log<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(

Comment on lines 120 to 121
let mut target_addr = match crate::scanner::scan_i2c(i2c, serial, prefix) {
Ok(addr) => addr,
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The variable found_addrs (plural) was renamed to target_addr (singular), but it holds a heapless::Vec of addresses from scan_i2c, which can contain multiple addresses. This is misleading. Consider renaming it to target_addrs (plural) for clarity. Also, in the match arm, addr should be addrs to reflect that it's a collection.

    let mut target_addrs = match crate::scanner::scan_i2c(i2c, serial, prefix) {
        Ok(addrs) => addrs,

let target_addr = match crate::scanner::scan_i2c(i2c, serial, prefix) {
Ok(addr) => addr,
Err(e) => {
util::prevent_garbled(serial, format_args!("[error] Failed to scan I2C: {e}"));
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The error logging here uses format_args!("[error] Failed to scan I2C: {e}"), which uses the Display format for the error. However, other similar error logs in this file use the Debug format ({e:?}). The Debug format for ErrorKind is more informative. For consistency and better diagnostics, please use the Debug format here as well.

            util::prevent_garbled(serial, format_args!("[error] Failed to scan I2C: {e:?}"));

p14c31355 and others added 7 commits September 1, 2025 21:22
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@p14c31355
Copy link
Owner Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several valuable improvements, including macros for easier API usage and significant performance optimizations in the topological sort algorithm. The changes make the library more ergonomic and efficient for embedded environments. I've identified a critical bug in the cycle detection logic that needs to be addressed, along with a couple of minor suggestions to improve code clarity and consistency.

}

pub fn execute_and_log_command<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(
pub fn exec_logger<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The function name exec_logger is a bit ambiguous. A more descriptive name like execute_and_log_node or exec_and_log_cmd would better reflect its purpose of executing a command node and logging the result. The previous name execute_and_log_command was also clear.

Suggested change
pub fn exec_logger<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(
pub fn execute_and_log_node<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(

p14c31355 and others added 2 commits September 1, 2025 21:48
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@p14c31355
Copy link
Owner Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces significant and valuable refactoring. The move to 'static lifetimes for command nodes via the new nodes! macro simplifies the API and improves efficiency. The topological sort implementation has been impressively optimized using bitmasks and stack-based data structures, which is a great fit for embedded systems. My review focuses on adding a critical safety check to prevent a panic with the new optimizations and improving function names for better long-term maintainability.

Comment on lines +368 to +373
pub fn get_one_sort(
&self,
_writer: &mut impl core::fmt::Write,
failed_nodes: &[bool; N],
) -> Result<(heapless::Vec<&'a [u8], N>, heapless::Vec<u8, N>), ExplorerError> {
let len = self.sequence.len();
let mut in_degree: heapless::Vec<u8, N> = heapless::Vec::new();
in_degree
.resize(len, 0)
.map_err(|_| ExplorerError::BufferOverflow)?;
let mut adj_list_rev: heapless::Vec<heapless::Vec<u8, N>, N> = heapless::Vec::new();
adj_list_rev
.resize(len, heapless::Vec::new())
.map_err(|_| ExplorerError::BufferOverflow)?;

for (i, node) in self.sequence.iter().enumerate() {
) -> Result<(heapless::Vec<&'static [u8], N>, heapless::Vec<u8, N>), ExplorerError> {
let len = self.nodes.len();
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This function has a couple of issues:

  1. (High severity) It uses a u128 bitmask for the adjacency list, which limits N to 128. If N > 128, the bit shift on line 395 will panic. A compile-time assert! should be added to enforce this limit, similar to what's done in PermutationIter::new.
  2. (Medium severity) The name get_one_sort is less descriptive than the original get_one_topological_sort_buf. A more descriptive name like get_one_topological_sort would improve clarity.

The suggestion below addresses both points.

    pub fn get_one_topological_sort(
        &self,
        _writer: &mut impl core::fmt::Write,
        failed_nodes: &[bool; N],
    ) -> Result<(heapless::Vec<&'static [u8], N>, heapless::Vec<u8, N>), ExplorerError> {
        const {
            assert!(
                N <= 128,
                "get_one_topological_sort uses a u128 bitmask, so N cannot exceed 128"
            );
        };
        let len = self.nodes.len();

}

pub fn execute_and_log_command<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(
pub fn exec_log_cmd<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The function execute_and_log_command has been renamed to exec_log_cmd. While shorter, the new name is less descriptive and could harm readability and maintainability, especially for new contributors. I'd recommend sticking with the more explicit name execute_and_log_command for clarity.

Suggested change
pub fn exec_log_cmd<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(
pub fn execute_and_log_command<I2C, E, W, const MAX_BYTES_PER_CMD: usize>(

@p14c31355
Copy link
Owner Author

@gemini-code-assist
What exactly do you mean by a bug in the cycle detection logic?
Also, it would be great if you could provide a code block that fixes it.

@gemini-code-assist
Copy link
Contributor

Hello p14c31355,

Thank you for reaching out! I apologize if my previous comment about a "bug in the cycle detection logic" was unclear.

To clarify, the get_one_sort function (which was get_one_topological_sort_buf in the previous version) does correctly detect the presence of a dependency cycle using Kahn's algorithm. When visited_count != len at the end of the algorithm, it accurately indicates that not all nodes could be processed, implying a cycle exists.

The "bug" I was likely referring to was not in the detection itself, but rather in the diagnostic information provided when a cycle is found. Currently, the function simply returns ExplorerError::DependencyCycle, which doesn't give specific details about which nodes are involved in the cycle. This can make debugging and understanding the root cause of the cycle more challenging.

To improve this, I recommend logging the indices of the nodes that are part of, or reachable from, the detected cycle. These are typically the nodes whose in-degree remains greater than zero after the topological sort queue has been exhausted. This would provide valuable context for debugging.

Here's a code suggestion to add this diagnostic logging within the get_one_sort function:

            // Cycle detected
            writeln!(
                _writer,
                "[error] Dependency cycle detected. Nodes involved (or reachable from cycle):"
            )
            .ok();
            for i in 0..len {
                // A node is part of a cycle if its in-degree is still > 0 after the topological sort
                // and it wasn't already marked as failed.
                if in_degree[i] > 0 && !failed_nodes[i] {
                    writeln!(_writer, "  - Node index: {}", i).ok();
                }
            }
            return Err(ExplorerError::DependencyCycle);

This change would be applied to src/explore/explorer.rs at lines 450-451 (RIGHT side of the diff), replacing the existing // Cycle detected comment and the return Err(...) statement.

Let me know if this clarifies the point and if you have any further questions!

@p14c31355
Copy link
Owner Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces significant improvements by moving the definition of command nodes and sequences to compile-time using macros. This is an excellent change for an embedded context, reducing runtime overhead and improving type safety. The new nodes!, factorial_sort!, and pruning_sort! macros enhance ergonomics. The refactoring of the explorer and runner modules also improves code clarity.

My review focuses on a critical logic issue in the new topological sort implementation that could lead to incorrect behavior. Otherwise, the changes are well-implemented.

Comment on lines 450 to 467
if visited_count != len {
// Cycle detected
writeln!(
_writer,
"[error] Dependency cycle detected. Nodes involved (or reachable from cycle):"
)
.ok();
for i in 0..len {
// A node is part of a cycle if its in-degree is still > 0 after the topological sort
// and it wasn't already marked as failed.
if in_degree[i] > 0 && !failed_nodes[i] {
writeln!(_writer, " - Node index: {i}").ok();
}
}
Err(ExplorerError::DependencyCycle)
} else {
Ok((result_sequence, result_len_per_node))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The cycle detection logic is incorrect when failed_nodes contains true values. The condition if visited_count != len does not account for nodes that are intentionally skipped. This will cause the function to incorrectly report a dependency cycle whenever any node has failed, preventing the pruning explorer from working as intended.

The number of visited nodes should be compared against the number of non-failed nodes.

        let non_failed_count = len - failed_nodes.iter().filter(|&&f| f).count();
        if visited_count != non_failed_count {
            // Cycle detected
            writeln!(
                _writer,
                "[error] Dependency cycle detected. Nodes involved (or reachable from cycle):"
            )
            .ok();
            for i in 0..len {
                // A node is part of a cycle if its in-degree is still > 0 after the topological sort
                // and it wasn't already marked as failed.
                if in_degree[i] > 0 && !failed_nodes[i] {
                    writeln!(_writer, "  - Node index: {i}").ok();
                }
            }
            Err(ExplorerError::DependencyCycle)
        } else {
            Ok((result_sequence, result_len_per_node))
        }

@p14c31355
Copy link
Owner Author

/gemini summary

@gemini-code-assist
Copy link
Contributor

Summary of Changes

This pull request introduces a set of enhancements to the exploration framework, primarily focusing on compile-time command definition and significant performance improvements in the topological sorting algorithms. These changes aim to reduce runtime overhead and simplify the process of defining and executing command sequences, making the framework more efficient and user-friendly, especially for resource-constrained environments.

Highlights

  • New Compile-Time Macros: Introduced nodes! and count_exprs! macros for defining command sequences and their properties at compile time, enhancing code clarity and efficiency.
  • Explorer Struct Refactoring: The Explorer struct was refactored to use &'static [CmdNode] for its nodes field, improving memory management and enabling the use of compile-time constant data.
  • Topological Sort Optimizations: Significant performance improvements were made to the topological sort algorithm (get_one_sort) by replacing heapless::Vec with fixed-size arrays and bitmasks for graph representation and queue management, thereby reducing dynamic memory allocations and improving execution speed.
  • New Helper Macros for Runners: New helper macros (factorial_sort!, pruning_sort!, get_one_sort!) were added to streamline the invocation of the exploration functions, simplifying their usage.
  • Improved Cycle Detection Diagnostics: The diagnostic logging for dependency cycle detection in the topological sort was enhanced to provide more specific information about the nodes involved in a detected cycle.
  • Consistent Renaming: Several functions and variables across the explorer and runner modules were consistently renamed (e.g., execute_and_log_command to exec_log_cmd, run_explorer to factorial_explorer, found_addrs to target_addrs) for improved clarity and maintainability.
Changelog
  • src/explore/explorer.rs
    • Renamed execute_and_log_command to exec_log_cmd.
    • Added nodes! and count_exprs! macros.
    • Updated Explorer struct to use &'static [CmdNode] for nodes.
    • Refactored Explorer's impl block to remove unnecessary lifetime parameter.
    • Added Explorer::new const function.
    • Renamed get_one_topological_sort_buf to get_one_sort.
    • Optimized get_one_sort to use fixed-size arrays and bitmasks for graph representation and queue.
    • Added compile-time assertion for N in PermutationIter::new and get_one_sort.
    • Improved cycle detection logging in get_one_sort.
  • src/explore/runner.rs
    • Renamed run_explorer to factorial_explorer.
    • Added factorial_sort! macro.
    • Renamed run_pruned_explorer to pruning_explorer.
    • Added pruning_sort! macro.
    • Renamed found_addrs to target_addrs for clarity.
    • Renamed run_single_sequence_explorer to one_topological_explorer.
    • Added get_one_sort! macro.
    • Modified one_topological_explorer to scan for target addresses internally.
  • src/prelude.rs
    • Exported new macros: factorial_sort, get_one_sort, pruning_sort.
    • Removed exports for old runner functions: run_explorer, run_single_sequence_explorer.
Activity
  • The author initiated multiple review requests.
  • Automated review comments from gemini-code-assist[bot] identified critical issues related to Rust lifetimes, macro parameter handling, and the correctness of the topological sort's cycle detection logic.
  • The author engaged with the bot's feedback, specifically requesting clarification and a code example for the cycle detection issue, which was subsequently provided and implemented.
  • Several medium-priority suggestions regarding naming conventions and error logging consistency were also provided by the bot, with some being adopted in the changes.

@p14c31355 p14c31355 merged commit 2a08a4d into develop Sep 1, 2025
This was referenced Sep 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Automatically calculate N from the user-defined number of nodes

1 participant

Comments