Skip to content

Commit cf477e9

Browse files
committed
Add description of inject traits
1 parent 95a7ea0 commit cf477e9

1 file changed

Lines changed: 73 additions & 5 deletions

File tree

node-graph/rfcs/fine-grained-context-caching.md

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,81 @@ The `ExtractAll` trait can be used to create a new Context based on the previous
5454
async fn transform(ctx: impl Ctx + ExtractAll, ...) {...}
5555
```
5656

57+
## Context Feature Injection
58+
59+
Some nodes need to provide context features for their downstream dependencies (in the function call stack building phase). This is accomplished through `Inject*` traits that complement the `Extract*` traits:
60+
61+
```rust
62+
// A node that injects index information for downstream map operations
63+
#[node_macro::node(category("Iteration"))]
64+
fn map_with_index<T>(
65+
ctx: impl Ctx + InjectIndex,
66+
collection: Vec<T>,
67+
mapper: impl Node<T, Output = U>
68+
) -> Vec<U> {
69+
collection.iter().enumerate().map(|(index, item)| {
70+
// This node injects the current index into the context
71+
// for the mapper node to extract via ExtractIndex
72+
let ctx_with_index = ctx.with_injected_index(index);
73+
mapper.eval_with_context(ctx_with_index, item)
74+
}).collect()
75+
}
76+
77+
// Downstream nodes can extract the injected index
78+
#[node_macro::node(category("Utility"))]
79+
fn use_index(ctx: impl Ctx + ExtractIndex, value: f64) -> f64 {
80+
let index = ctx.index().unwrap_or(0);
81+
value * (index as f64)
82+
}
83+
```
84+
85+
### Injection Hierarchy and Precedence
86+
When a node both extracts and injects the same feature:
87+
- **Extract-then-Inject**: Node extracts from upstream, processes, then injects modified version downstream
88+
- **Inject-Override**: Injected features take precedence over upstream extracted features
89+
- **Injection Scope**: Injected features are only available to immediate downstream nodes in the evaluation chain
90+
5791
# Reference-level explanation
5892
[reference-level-explanation]: #reference-level-explanation
5993

60-
The different `Extract*` traits are exported by the node macro and could thus be included as part of the document node definition to inform the compiler about features extracted in every node. Note that the `ExtractAll` will be ignored in this analysis. Any usages of partial context data are propagated downstream and all nodes are identified in which the number of extracted features changes between the upstream and downstream. At these locations a context modification needs to be inserted which "zeros" the data no longer used in the upstream part of the graph.
61-
Note that the number of features extracted can usually only increase except for cases where a node decides to inject data into the call chain.
62-
This will be the case when building lambda expressions, the node driving the lambda evaluation (e.g. a map or a fold node) would insert data such as the index into the call chain.
63-
We might consider adding a special `Inject*` annotation in the future to indicate that the downstream of this node does not need to provide the feature even though the upstream does need it.
94+
The different `Extract*` and `Inject*` traits are exported by the node macro and are included as part of the document node definition to inform the compiler about features extracted and injected by every node. Note that the `ExtractAll` will be ignored in this analysis.
95+
96+
## Context Nullification Analysis
97+
98+
The compiler determines where to insert context nullification nodes through branch analysis:
99+
100+
1. **Extract Requirement Tracking**: For each branch in the graph, track the extract requirements all the way back to their corresponding inject nodes. Every extracted feature must have a corresponding inject node somewhere upstream, otherwise this is a compile error.
101+
102+
2. **Branch Convergence Analysis**: When two branches with different extract requirements meet (at a node that takes multiple inputs), one or both branches can have their context nullified to remove features only needed in the other branch.
103+
104+
3. **Post-Injection Nullification**: After an inject node, the extract needs of downstream nodes are satisfied for that inject type. At this point we can check if all the features that the inject node provides are actually used downstream, and if not, nullify them immediately.
105+
106+
4. **Injection Scope Optimization**: After every inject node, analyze whether all injected features are actually consumed by downstream nodes. Unused injected features can be nullified right at the injection point.
107+
108+
## Inject* Trait System
109+
110+
The injection system provides these complementary traits to Extract*:
111+
112+
```rust
113+
pub trait InjectFootprint {
114+
fn with_injected_footprint(&self, footprint: Footprint) -> Context;
115+
}
116+
117+
pub trait InjectTime {
118+
fn with_injected_time(&self, time: f64) -> Context;
119+
}
120+
121+
pub trait InjectIndex {
122+
fn with_injected_index(&self, index: usize) -> Context;
123+
}
124+
125+
pub trait InjectVarArgs {
126+
fn with_injected_varargs(&self, args: &[DynBox]) -> Context;
127+
}
128+
```
129+
130+
Note that "downstream" in this context refers to nodes that are called later in the function call stack building phase, which is inverted compared to the usual data flow direction.
131+
64132
This can be implemented as a compiler pass similar to the compose node insertion.
65133

66134
# Drawbacks
@@ -84,4 +152,4 @@ This is expected to have the biggest impact on real time applications such as an
84152
# Future possibilities
85153
[future-possibilities]: #future-possibilities
86154

87-
Adding `Inject*` annotation to complement the `Extract*` ones to provide even more fine grained control over caching.
155+
~Adding `Inject*` annotation to complement the `Extract*` ones to provide even more fine grained control over caching.~

0 commit comments

Comments
 (0)