Skip to content

feat: add GraphContext, a graph-scoped side channel for stages - #9

Merged
psyinf merged 2 commits into
mainfrom
feature/graph-context
Sep 17, 2026
Merged

psyinf merged 2 commits into
mainfrom
feature/graph-context

Conversation

@psyinf

@psyinf psyinf commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Summary

Adds GraphContext, a side channel that lets stages share data (e.g. a frame number) without changing the message type and without knowing which stage published it.

  • GraphContext (GraphContext.hpp): one value per type, keyed by the type itself (set / get / getOr / contains / erase / update / clear). Values are copied in and out under a shared_mutex, and update() is atomic.
  • Extensible: GraphContext is a polymorphic base. An application can derive its own context and recover it with as<Derived>().
  • Reaching stages: MessageFilter::setContext / context(). The graph hands over the context once, and FilterGraph, DslFilterGraph, JsonFilterGraph, AnyFilterChain, FanoutFilter and JoinFilter forward it, including to nested graphs and to fanout receivers / join paths added later.

Breaking change

AnyMessageFilter::setContext is pure virtual. Custom AnyMessageFilter implementations must implement it and forward the context to the stages they wrap. It is pure rather than a no-op on purpose: a composite that forgot to forward would otherwise fail silently.

Design notes

  • The context is graph-scoped, not per message. If a graph ever buffers or reorders messages, a value like a frame number can belong to a different message.
  • There is no default context. context() is nullptr until setContext is called, so stages must check it.
  • setContext is meant to be called before processing, not concurrently with filter().
  • Members added by a derived context are not covered by the internal lock, and as<Derived>() couples a stage to that type, so reusable stages should use set / get.

Testing

New GraphContextTests: store semantics, copy-out, erase/clear, atomic update across 8 threads, derived contexts, a stage without a context, and propagation through FilterGraph, DslFilterGraph, and fanout/join paths nested in a JsonFilterGraph. The full suite passes locally (68/68, MSVC debug with ASan).

- GraphContext: type-keyed, thread-safe blackboard (set / get / getOr /
  contains / erase / update / clear); values are copied in and out under a
  shared_mutex, update() is atomic
- polymorphic base: derive an application context and recover it with
  as<Derived>()
- MessageFilter::setContext / context(): a graph hands its context to its
  stages once; FilterGraph, DslFilterGraph, JsonFilterGraph, AnyFilterChain,
  FanoutFilter and JoinFilter forward it, including to nested graphs and to
  receivers/paths added later
- breaking: AnyMessageFilter::setContext is pure virtual
- README section and CHANGELOG entries
@psyinf
psyinf added this pull request to stack #12 September 17, 2026 17:44
context() is never null: a stage starts with its own empty context, and
every graph hands its context to its stages when it is built, not only on
an explicit setContext. So stages of a graph share one context out of the
box and no stage needs a null check.

- MessageFilter: mContext is always set; setContext(nullptr) installs a
  fresh empty context; context() returns GraphContext& (const overload
  too) and sharedContext() the shared_ptr for composites to forward
- FilterGraph, JsonFilterGraph, DslFilterGraph propagate their context
  from their constructor; AnyFilterChain owns one and does the same, so a
  chain built on its own has one context, not one per stage
- FanoutFilter / JoinFilter hand sharedContext() to late receivers/paths
- a graph overwrites the context of the stages it is built from, so a
  shared context goes to the graph, not to individual stages
@psyinf
psyinf merged commit b5d0ef6 into main Sep 17, 2026
3 checks passed
@psyinf
psyinf deleted the feature/graph-context branch September 17, 2026 18:22
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