Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 12 additions & 43 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,63 +1,32 @@
# Robonix CI — all build/check logic lives in rust/Makefile (make ci-rust / make ci-full)
# Robonix CI — build + lint checks
name: CI

on:
push:
branches: [main, dev/**]
branches: [main, dev, dev/**]
pull_request:
branches: [main, dev/**]
branches: [main, dev, dev/**]

env:
RUST_DIR: rust

jobs:
rust:
name: Rust (make ci-rust)
check:
name: Rust (fmt + clippy + build)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
components: rustfmt, clippy

- name: make ci-rust
run: cd ${{ env.RUST_DIR }} && make ci-rust
- name: cargo fmt --check
run: cd ${{ env.RUST_DIR }} && cargo fmt --all -- --check

full:
name: Full (make ci-full)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- name: Install ROS2 Humble
run: |
sudo apt-get update
sudo apt-get install -y locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8
sudo apt-get install -y curl gnupg2 lsb-release
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
sudo apt-get update
sudo apt-get install -y ros-humble-ros-base python3-pip

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Install colcon
run: |
source /opt/ros/humble/setup.bash
pip3 install --user colcon-common-extensions
echo "$HOME/.local/bin" >> $GITHUB_PATH
export PATH="$HOME/.local/bin:$PATH"
which colcon
- name: cargo clippy
run: cd ${{ env.RUST_DIR }} && cargo clippy --workspace -- -D warnings

- name: make ci-full
run: |
source /opt/ros/humble/setup.bash
cd ${{ env.RUST_DIR }} && make ci-full
env:
PYTHON3_EXECUTABLE: /usr/bin/python3
- name: cargo build
run: cd ${{ env.RUST_DIR }} && cargo build --workspace
8 changes: 4 additions & 4 deletions docker/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ export ROBONIX_META_GRPC_ADDR="${ROBONIX_META_GRPC_ADDR:-0.0.0.0:50051}"
export ROBONIX_META_GRPC_ENDPOINT="${ROBONIX_META_GRPC_ENDPOINT:-127.0.0.1:50051}"

if ! grep -q "### ROBONIX_DOCKER_ENV ###" ~/.bashrc 2>/dev/null; then
cat <<EOF >> ~/.bashrc
cat <<'EOF' >> ~/.bashrc
### ROBONIX_DOCKER_ENV ###
source /opt/ros/humble/setup.bash
export RMW_IMPLEMENTATION=${RMW_IMPLEMENTATION}
export ROBONIX_META_GRPC_ADDR=${ROBONIX_META_GRPC_ADDR}
export ROBONIX_META_GRPC_ENDPOINT=${ROBONIX_META_GRPC_ENDPOINT}
export RMW_IMPLEMENTATION="${RMW_IMPLEMENTATION:-rmw_fastrtps_cpp}"
export ROBONIX_META_GRPC_ADDR="${ROBONIX_META_GRPC_ADDR:-0.0.0.0:50051}"
export ROBONIX_META_GRPC_ENDPOINT="${ROBONIX_META_GRPC_ENDPOINT:-127.0.0.1:50051}"
EOF
fi

Expand Down
2 changes: 1 addition & 1 deletion pyrightconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"rust/examples/proto_gen",
"rust/crates/robonix-interfaces",
"rust/examples/packages/maniskill_vla_demo/maniskill_vla_demo"
],
]
}
2 changes: 1 addition & 1 deletion rust/crates/robonix-atlas/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.build_client(true)
.compile_protos(
&[proto_dir.join("robonix_runtime.proto")],
&[proto_dir.clone()],
std::slice::from_ref(&proto_dir),
)?;

// robonix-codegen build-dependency REMOVED -- robonix-codegen syntax is deprecated.
Expand Down
21 changes: 10 additions & 11 deletions rust/crates/robonix-atlas/src/meta_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,7 @@ impl MetaRuntimeRegistry {
}

fn is_catalogued_robonix_interface(abstract_path: &str) -> bool {
ROBO_SYSTEM_INTERFACE_CATALOG
.iter()
.any(|&id| id == abstract_path)
ROBO_SYSTEM_INTERFACE_CATALOG.contains(&abstract_path)
}

/// For `grpc` / `ros2` / `mcp`, namespace must be under `robonix/...`.
Expand Down Expand Up @@ -334,6 +332,7 @@ impl MetaRuntimeRegistry {
}
}

#[allow(clippy::too_many_arguments)]
async fn register_node(
&self,
node_id: String,
Expand Down Expand Up @@ -472,14 +471,14 @@ impl MetaRuntimeRegistry {
}

fn inject_endpoint(metadata_json: &str, endpoint: &str) -> String {
if let Ok(mut meta) = serde_json::from_str::<serde_json::Value>(metadata_json) {
if let Some(obj) = meta.as_object_mut() {
obj.insert(
"endpoint".into(),
serde_json::Value::String(endpoint.to_string()),
);
return serde_json::to_string(&meta).unwrap_or_else(|_| metadata_json.to_string());
}
if let Ok(mut meta) = serde_json::from_str::<serde_json::Value>(metadata_json)
&& let Some(obj) = meta.as_object_mut()
{
obj.insert(
"endpoint".into(),
serde_json::Value::String(endpoint.to_string()),
);
return serde_json::to_string(&meta).unwrap_or_else(|_| metadata_json.to_string());
}
// If metadata_json is empty or not an object, create a minimal one.
serde_json::json!({ "endpoint": endpoint }).to_string()
Expand Down
110 changes: 104 additions & 6 deletions rust/crates/robonix-buffer/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,24 @@ pub struct BufferSpec {

impl BufferSpec {
pub fn frame_bytes(&self) -> usize {
self.width as usize
* self.height as usize
* self.channels as usize
* self.format.bytes_per_element()
(self.width as usize)
.checked_mul(self.height as usize)
.and_then(|v| v.checked_mul(self.channels as usize))
.and_then(|v| v.checked_mul(self.format.bytes_per_element()))
.expect("buffer frame size overflow")
}

pub fn total_bytes(&self) -> usize {
HEADER_SIZE + self.frame_bytes()
HEADER_SIZE
.checked_add(self.frame_bytes())
.expect("buffer total size overflow")
}

pub fn stride(&self) -> u32 {
self.width * self.channels * self.format.bytes_per_element() as u32
self.width
.checked_mul(self.channels)
.and_then(|v| v.checked_mul(self.format.bytes_per_element() as u32))
.expect("buffer stride overflow")
}
}

Expand Down Expand Up @@ -223,3 +229,95 @@ pub enum BufferState {
/// One or more consumers are reading / processing.
Processing,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn frame_bytes_normal() {
let spec = BufferSpec {
width: 1920,
height: 1080,
channels: 3,
format: BufferFormat::Rgb8,
};
assert_eq!(spec.frame_bytes(), 1920 * 1080 * 3 * 1);
}

#[test]
fn frame_bytes_float32() {
let spec = BufferSpec {
width: 640,
height: 480,
channels: 1,
format: BufferFormat::Float32,
};
assert_eq!(spec.frame_bytes(), 640 * 480 * 1 * 4);
}

#[test]
fn total_bytes_includes_header() {
let spec = BufferSpec {
width: 100,
height: 100,
channels: 3,
format: BufferFormat::Rgb8,
};
assert_eq!(spec.total_bytes(), HEADER_SIZE + 100 * 100 * 3);
}

#[test]
#[should_panic(expected = "buffer frame size overflow")]
fn frame_bytes_overflow_panics() {
let spec = BufferSpec {
width: u32::MAX,
height: u32::MAX,
channels: u32::MAX,
format: BufferFormat::Float64, // 8 bytes per element
};
// This should panic with overflow instead of silently wrapping
let _ = spec.frame_bytes();
}

#[test]
#[should_panic(expected = "buffer stride overflow")]
fn stride_overflow_panics() {
let spec = BufferSpec {
width: u32::MAX,
height: 1,
channels: u32::MAX,
format: BufferFormat::Float64,
};
let _ = spec.stride();
}

#[test]
fn stride_normal() {
let spec = BufferSpec {
width: 1920,
height: 1080,
channels: 3,
format: BufferFormat::Rgb8,
};
assert_eq!(spec.stride(), 1920 * 3 * 1);
}

#[test]
fn buffer_format_roundtrip() {
for v in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 16, 17, 18, 19, 20, 255] {
let fmt = BufferFormat::from_u32(v).expect(&format!("format {v} should be valid"));
assert_eq!(fmt as u32, v);
}
assert!(
BufferFormat::from_u32(99).is_none(),
"unknown format should return None"
);
}

#[test]
fn header_size_is_64() {
assert_eq!(HEADER_SIZE, 64);
assert_eq!(std::mem::size_of::<BufferHeader>(), HEADER_SIZE);
}
}
Loading
Loading