Skip to content

bug: support partial wiring of multi-output sources#883

Merged
gbin merged 9 commits into
masterfrom
gbin/core-multi-output-projection
Mar 3, 2026
Merged

bug: support partial wiring of multi-output sources#883
gbin merged 9 commits into
masterfrom
gbin/core-multi-output-projection

Conversation

@gbin

@gbin gbin commented Mar 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

The proc macro could not accomodate on multi output tasks a partial subscription to the set of output messages.

Related issues

  • Closes #

Changes

Ask the user to confirm all the unused connection with a line like:

(
            src: "src",
            dst: "__nc__",
            msg: "bool",
),

Testing

  • just fmt
  • just lint
  • just test
  • optional full just std-ci (if std/runtime paths are impacted)
  • optional full just nostd-ci (if embedded/no_std paths are impacted)
  • Other (please specify):

pro-tip: just with no parameters in the root defaults to just fmt, just lint, and just test.

Checklist

  • I have updated docs or examples where needed
  • I have added or updated tests where needed
  • I have considered platform impact (Linux/macOS/Windows/embedded)
  • I have considered config/logging changes (if applicable)
  • This change is not a breaking change (or I documented it below)

Breaking changes (if any)

Additional context

@gbin gbin added bug Something isn't working include in changelog labels Mar 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds runtime projection support so downstream tasks can subscribe to only a subset of a multi-output source’s messages, fixing partial wiring for multi-output tasks in the proc-macro-generated runtime.

Changes:

  • Introduces CuOutputGet and CuOutputProject to extract/project specific CuMsg<T> out of a source output pack (including tuples).
  • Updates derive macro generated execution to run the source into a “full output” buffer, then project into the connected downstream output slot.
  • Adds a compile-pass test + config validating partial subscription (i32 only) from a (i32, bool) source.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
core/cu29_runtime/src/cutask.rs Adds typed lookup + projection traits/functions enabling subset routing from multi-output sources.
core/cu29_derive/src/lib.rs Changes generated execution to process into a full output pack and project into the connected output type.
core/cu29_derive/tests/config/multi_output_source_subset_valid.ron Adds config wiring a multi-output source to a sink subscribing to only i32.
core/cu29_derive/tests/compile_pass/copper_runtime/multi_output_source_subset.rs Adds compile-pass test verifying the partial subscription compiles.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread core/cu29_runtime/src/cutask.rs Outdated
Comment on lines +112 to +143
macro_rules! impl_cu_output_get_tuple {
($($idx:tt => $name:ident),+) => {
impl<T, $($name),+> CuOutputGet<T> for ($(CuMsg<$name>,)+)
where
T: CuMsgPayload + 'static,
$($name: CuMsgPayload + 'static),+
{
fn output_msg(&self) -> &CuMsg<T> {
$(
if TypeId::of::<T>() == TypeId::of::<$name>() {
let msg = &self.$idx;
// SAFETY: TypeId equality proves T == $name.
return unsafe { &*(msg as *const CuMsg<$name> as *const CuMsg<T>) };
}
)+
panic!(
"Requested output message type '{}' is not present in source output tuple",
type_name::<T>()
);
}
}
};
}

impl_cu_output_get_tuple!(0 => T1, 1 => T2);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9);

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

The tuple CuOutputGet<T> impl is overly permissive: it makes (CuMsg<T1>, CuMsg<T2>, ...) implement CuOutputGet<T> for any T, so a missing type compiles and then panics at runtime. It also requires unsafe casting. Prefer generating per-element trait impls (e.g., implement CuOutputGet<T1> and CuOutputGet<T2> separately for the tuple, each returning &self.0 / &self.1), which removes TypeId/unsafe entirely and turns “type not present” into a compile-time error (and prevents ambiguous duplicate payload types via coherence).

Suggested change
macro_rules! impl_cu_output_get_tuple {
($($idx:tt => $name:ident),+) => {
impl<T, $($name),+> CuOutputGet<T> for ($(CuMsg<$name>,)+)
where
T: CuMsgPayload + 'static,
$($name: CuMsgPayload + 'static),+
{
fn output_msg(&self) -> &CuMsg<T> {
$(
if TypeId::of::<T>() == TypeId::of::<$name>() {
let msg = &self.$idx;
// SAFETY: TypeId equality proves T == $name.
return unsafe { &*(msg as *const CuMsg<$name> as *const CuMsg<T>) };
}
)+
panic!(
"Requested output message type '{}' is not present in source output tuple",
type_name::<T>()
);
}
}
};
}
impl_cu_output_get_tuple!(0 => T1, 1 => T2);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8);
impl_cu_output_get_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9);
impl<T1, T2> CuOutputGet<T1> for (CuMsg<T1>, CuMsg<T2>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T1> {
&self.0
}
}
impl<T1, T2> CuOutputGet<T2> for (CuMsg<T1>, CuMsg<T2>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T2> {
&self.1
}
}
impl<T1, T2, T3> CuOutputGet<T1> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T1> {
&self.0
}
}
impl<T1, T2, T3> CuOutputGet<T2> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T2> {
&self.1
}
}
impl<T1, T2, T3> CuOutputGet<T3> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T3> {
&self.2
}
}
impl<T1, T2, T3, T4> CuOutputGet<T1> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T1> {
&self.0
}
}
impl<T1, T2, T3, T4> CuOutputGet<T2> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T2> {
&self.1
}
}
impl<T1, T2, T3, T4> CuOutputGet<T3> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T3> {
&self.2
}
}
impl<T1, T2, T3, T4> CuOutputGet<T4> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T4> {
&self.3
}
}
impl<T1, T2, T3, T4, T5> CuOutputGet<T1> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T1> {
&self.0
}
}
impl<T1, T2, T3, T4, T5> CuOutputGet<T2> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T2> {
&self.1
}
}
impl<T1, T2, T3, T4, T5> CuOutputGet<T3> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T3> {
&self.2
}
}
impl<T1, T2, T3, T4, T5> CuOutputGet<T4> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T4> {
&self.3
}
}
impl<T1, T2, T3, T4, T5> CuOutputGet<T5> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T5> {
&self.4
}
}
impl<T1, T2, T3, T4, T5, T6> CuOutputGet<T1> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T1> {
&self.0
}
}
impl<T1, T2, T3, T4, T5, T6> CuOutputGet<T2> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T2> {
&self.1
}
}
impl<T1, T2, T3, T4, T5, T6> CuOutputGet<T3> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T3> {
&self.2
}
}
impl<T1, T2, T3, T4, T5, T6> CuOutputGet<T4> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T4> {
&self.3
}
}
impl<T1, T2, T3, T4, T5, T6> CuOutputGet<T5> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T5> {
&self.4
}
}
impl<T1, T2, T3, T4, T5, T6> CuOutputGet<T6> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T6> {
&self.5
}
}
impl<T1, T2, T3, T4, T5, T6, T7> CuOutputGet<T1> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T1> {
&self.0
}
}
impl<T1, T2, T3, T4, T5, T6, T7> CuOutputGet<T2> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T2> {
&self.1
}
}
impl<T1, T2, T3, T4, T5, T6, T7> CuOutputGet<T3> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T3> {
&self.2
}
}
impl<T1, T2, T3, T4, T5, T6, T7> CuOutputGet<T4> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T4> {
&self.3
}
}
impl<T1, T2, T3, T4, T5, T6, T7> CuOutputGet<T5> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T5> {
&self.4
}
}
impl<T1, T2, T3, T4, T5, T6, T7> CuOutputGet<T6> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T6> {
&self.5
}
}
impl<T1, T2, T3, T4, T5, T6, T7> CuOutputGet<T7> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T7> {
&self.6
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8> CuOutputGet<T1> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T1> {
&self.0
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8> CuOutputGet<T2> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T2> {
&self.1
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8> CuOutputGet<T3> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T3> {
&self.2
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8> CuOutputGet<T4> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T4> {
&self.3
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8> CuOutputGet<T5> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T5> {
&self.4
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8> CuOutputGet<T6> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T6> {
&self.5
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8> CuOutputGet<T7> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T7> {
&self.6
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8> CuOutputGet<T8> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T8> {
&self.7
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8, T9> CuOutputGet<T1> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>, CuMsg<T9>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
T9: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T1> {
&self.0
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8, T9> CuOutputGet<T2> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>, CuMsg<T9>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
T9: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T2> {
&self.1
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8, T9> CuOutputGet<T3> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>, CuMsg<T9>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
T9: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T3> {
&self.2
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8, T9> CuOutputGet<T4> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>, CuMsg<T9>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
T9: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T4> {
&self.3
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8, T9> CuOutputGet<T5> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>, CuMsg<T9>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
T9: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T5> {
&self.4
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8, T9> CuOutputGet<T6> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>, CuMsg<T9>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
T9: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T6> {
&self.5
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8, T9> CuOutputGet<T7> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>, CuMsg<T9>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
T9: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T7> {
&self.6
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8, T9> CuOutputGet<T8> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>, CuMsg<T9>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
T9: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T8> {
&self.7
}
}
impl<T1, T2, T3, T4, T5, T6, T7, T8, T9> CuOutputGet<T9> for (CuMsg<T1>, CuMsg<T2>, CuMsg<T3>, CuMsg<T4>, CuMsg<T5>, CuMsg<T6>, CuMsg<T7>, CuMsg<T8>, CuMsg<T9>)
where
T1: CuMsgPayload + 'static,
T2: CuMsgPayload + 'static,
T3: CuMsgPayload + 'static,
T4: CuMsgPayload + 'static,
T5: CuMsgPayload + 'static,
T6: CuMsgPayload + 'static,
T7: CuMsgPayload + 'static,
T8: CuMsgPayload + 'static,
T9: CuMsgPayload + 'static,
{
fn output_msg(&self) -> &CuMsg<T9> {
&self.8
}
}

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

That suggestion, as written, is not implementable on stable Rust.

It triggers coherence overlap (E0119) immediately, because these two impls overlap when T1 == T2:

  • impl<T1, T2> CuOutputGet for (CuMsg, CuMsg)
  • impl<T1, T2> CuOutputGet for (CuMsg, CuMsg)

Comment thread core/cu29_runtime/src/cutask.rs Outdated
{
fn output_msg(&self) -> &CuMsg<T> {
$(
if TypeId::of::<T>() == TypeId::of::<$name>() {

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

If this runtime lookup approach stays, consider fully qualifying TypeId/type_name as core::any::TypeId and core::any::type_name (or importing them explicitly) to avoid relying on ambient imports and to keep the code no_std-friendly.

Copilot uses AI. Check for mistakes.
Comment thread core/cu29_runtime/src/cutask.rs Outdated
Comment on lines +128 to +129
"Requested output message type '{}' is not present in source output tuple",
type_name::<T>()

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

If this runtime lookup approach stays, consider fully qualifying TypeId/type_name as core::any::TypeId and core::any::type_name (or importing them explicitly) to avoid relying on ambient imports and to keep the code no_std-friendly.

Copilot uses AI. Check for mistakes.
Comment thread core/cu29_derive/src/lib.rs Outdated
step: CuComponentState::Process,
culistid: Some(clid),
});
let mut __cu_full_output = Default::default();

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

The generated code now unconditionally constructs a fresh __cu_full_output via Default::default(). This introduces an implicit (and potentially macro-error-message-unfriendly) requirement that the task’s full Output type is Default, and it also adds per-call initialization overhead. Consider either (1) making the Default requirement explicit in the generated bounds / emitting a clearer compile-time error when it’s not satisfied, or (2) reusing an existing full-output buffer (if one exists in the runtime scaffolding) to avoid repeated initialization.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this is already explicit, all mesages are default constructible WTH?

Comment thread core/cu29_derive/src/lib.rs Outdated
Comment on lines +4574 to +4576
#task_instance.process(&ctx, &mut __cu_full_output)
};
cu29::cutask::project_output(&__cu_full_output, cumsg_output);

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

The generated code now unconditionally constructs a fresh __cu_full_output via Default::default(). This introduces an implicit (and potentially macro-error-message-unfriendly) requirement that the task’s full Output type is Default, and it also adds per-call initialization overhead. Consider either (1) making the Default requirement explicit in the generated bounds / emitting a clearer compile-time error when it’s not satisfied, or (2) reusing an existing full-output buffer (if one exists in the runtime scaffolding) to avoid repeated initialization.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

same

@gbin

gbin commented Mar 2, 2026

Copy link
Copy Markdown
Collaborator Author

ok this is a little bit fucked up this entire thing and can introduce some overhead.

The problem is that you cannot ignore any output from a multi output task because it is constructed from the connections.

if you don't connect them all the output is only a subset of the actual one.

I am thinking about helping that with a proper declaration of outputs or maybe just "not connected" entry in the task.

@gbin

gbin commented Mar 2, 2026

Copy link
Copy Markdown
Collaborator Author

I completely reoverhalled this, the direction was BAD. no more overhead and added the notiong of __nc__ that you need to add to your connections to confirm you are dropping the output

@makeecat

makeecat commented Mar 3, 2026

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3b58d7491e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread core/cu29_runtime/src/config.rs
@gbin gbin merged commit b581c04 into master Mar 3, 2026
23 checks passed
@gbin gbin deleted the gbin/core-multi-output-projection branch March 3, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working include in changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants