Methods for Parameter Passing Between CGraph Nodes Using GParam
CGraph implements parameter passing between nodes through the GParam subsystem, which provides type-safe creation, retrieval, and thread-safe access macros for sharing data across pipeline stages.
The CGraph repository (chunelfeng/cgraph) is a high-performance C++ task scheduling framework that enables complex pipeline construction. When nodes need to exchange data without tight coupling, the framework offers the GParam mechanism—a lightweight, key-based registry system that maintains type safety while allowing any node to access shared state through the GParamManager.
Understanding the GParam Architecture
The parameter passing system rests on three core components that manage the lifecycle and accessibility of shared data objects.
The GParam Base Class
All shared parameters must inherit from GParam, defined in src/GraphCtrl/GraphParam/GParam.h. This base class stores the parameter's string key, an optional back-trace for debugging, and a _param_shared_lock_ member that provides thread-safety. Depending on compiler capabilities, this lock is either a std::shared_mutex (C++17) or a std::recursive_mutex for older environments.
The GParamManager Registry
The GParamManager class, located in src/GraphCtrl/GraphParam/GParamManager.h, maintains a centralized std::unordered_map<std::string, GParamPtr> called params_map_. This registry acts as the single source of truth for all parameter instances, handling creation, lookup, and deletion operations. Every GParam object is stored under a unique string key, ensuring that nodes can reference shared data without direct pointers to each other.
The GParamManagerWrapper Mixin
To expose parameter operations to individual nodes, CGraph provides the GParamManagerWrapper class in src/GraphCtrl/GraphParam/GParamManagerWrapper.h. This mixin is incorporated into GNode through the macro CGRAPH_DECLARE_GPARAM_MANAGER_WRAPPER_WITH_MEMBER, supplying methods like createGParam, getGParam, and removeGParam. The wrapper also tracks "concerned" parameters—those actually accessed by the node—enabling pipeline introspection tools to report which keys were utilized during execution.
Three Stages of Parameter Passing
The GParam workflow follows a distinct three-stage pattern: creation, retrieval, and optional locking for concurrent access.
Stage 1: Creating Parameters with CGRAPH_CREATE_GPARAM
Nodes register new parameter types using the CGRAPH_CREATE_GPARAM(Type, key) macro, defined in src/GraphCtrl/GraphParam/GParamUtils.h. This macro expands to a call to createGParam<Type>(key), which instantiates the concrete type and registers it in the GParamManager under the specified key. The macro returns a CStatus object indicating success or failure.
// In MyWriteParamNode.h
CStatus init() override {
// Register a GParam of type MyParam under the key "param1"
return CGRAPH_CREATE_GPARAM(MyParam, "param1");
}
Stage 2: Retrieving Parameters with CGRAPH_GET_GPARAM_WITH_NO_EMPTY
Subsequent nodes (or the same node later in the lifecycle) fetch the shared instance using CGRAPH_GET_GPARAM_WITH_NO_EMPTY(Type, key). This macro invokes getGParamWithNoEmpty<Type>(key), which performs a type-safe lookup and throws an exception if the key does not exist, preventing null pointer dereferences.
// In MyReadParamNode.h
CStatus run() override {
// Fetch the same MyParam instance; throws if missing
auto* myParam = CGRAPH_GET_GPARAM_WITH_NO_EMPTY(MyParam, "param1");
CGraph::CGRAPH_ECHO("Read iValue = %d, iCount = %d",
myParam->iValue, myParam->iCount);
return CStatus();
}
Stage 3: Protecting Concurrent Access with Scoped Locks
When nodes execute in parallel and mutate shared parameters, CGraph provides scoped lock macros to ensure thread safety. The CGRAPH_PARAM_WRITE_CODE_BLOCK(param) and CGRAPH_PARAM_READ_CODE_BLOCK(param) macros, defined in src/GraphCtrl/GraphParam/GParamUtils.h, create RAII-style locks on the parameter's internal mutex.
CStatus run() override {
auto* myParam = CGRAPH_GET_GPARAM_WITH_NO_EMPTY(MyParam, "param1");
int val = 0, cnt = 0;
{
// Scoped write lock – only this block holds the exclusive lock
CGRAPH_PARAM_WRITE_CODE_BLOCK(myParam);
val = ++myParam->iValue; // modify
cnt = ++myParam->iCount; // modify
}
CGraph::CGRAPH_ECHO("[%s], iValue = %d, iCount = %d",
this->getName().c_str(), val, cnt);
return CStatus();
}
Practical Implementation Example
To implement parameter passing in a real pipeline, you must first define a custom parameter type, then create nodes that write to and read from the shared instance.
Defining a Custom Parameter
User-defined parameters inherit from CGraph::GParam and include the data fields to be shared. The tutorial example in tutorial/MyParams/MyParam.h demonstrates this pattern:
#include "GraphCtrl/GraphParam/GParam.h"
struct MyParam : public CGraph::GParam {
int iValue = 0;
int iCount = 0;
};
Write Node Implementation
The MyWriteParamNode in tutorial/MyGNode/MyWriteParamNode.h demonstrates initialization and locked mutation:
class MyWriteParamNode : public CGraph::GNode {
public:
CStatus init() override {
return CGRAPH_CREATE_GPARAM(MyParam, "param1");
}
CStatus run() override {
auto* myParam = CGRAPH_GET_GPARAM_WITH_NO_EMPTY(MyParam, "param1");
int val = 0, cnt = 0;
{
CGRAPH_PARAM_WRITE_CODE_BLOCK(myParam);
val = ++myParam->iValue;
cnt = ++myParam->iCount;
}
CGraph::CGRAPH_ECHO("[%s], iValue = %d, iCount = %d",
this->getName().c_str(), val, cnt);
return CStatus();
}
};
Read Node Implementation
The MyReadParamNode in tutorial/MyGNode/MyReadParamNode.h shows read-only access:
class MyReadParamNode : public CGraph::GNode {
public:
CStatus run() override {
auto* myParam = CGRAPH_GET_GPARAM_WITH_NO_EMPTY(MyParam, "param1");
CGraph::CGRAPH_ECHO("Read iValue = %d, iCount = %d",
myParam->iValue, myParam->iCount);
return CStatus();
}
};
Debugging and Back-Trace Support
To assist with pipeline debugging, CGraph supports back-trace recording when parameters are created. By using the CGRAPH_CREATE_GPARAM_WITH_BACKTRACE macro, the system calls GParam::addBacktrace (implemented in src/GraphCtrl/GraphParam/GParam.h lines 38-44) to store the name of the node that instantiated the parameter. This allows developers to trace the origin of shared data when inspecting pipeline state or diagnosing issues with missing keys.
Summary
- GParam is the base class for all shared parameters in CGraph, providing built-in thread-safety mechanisms via
std::shared_mutexorstd::recursive_mutex. - GParamManager maintains a centralized
params_map_registry insrc/GraphCtrl/GraphParam/GParamManager.h, ensuring a single source of truth for each parameter key. - Creation uses the
CGRAPH_CREATE_GPARAM(Type, key)macro to register type-safe instances during node initialization. - Retrieval employs
CGRAPH_GET_GPARAM_WITH_NO_EMPTY(Type, key)to fetch shared instances with null-checking guarantees. - Concurrency is handled through
CGRAPH_PARAM_WRITE_CODE_BLOCKandCGRAPH_PARAM_READ_CODE_BLOCKmacros, which provide RAII-style scoped locks on the parameter's internal mutex. - Debugging features include back-trace recording via
CGRAPH_CREATE_GPARAM_WITH_BACKTRACEto track which node created each parameter.
Frequently Asked Questions
How do I create a custom parameter type for sharing data between nodes?
Define a C++ struct or class that inherits from CGraph::GParam and include the data fields you need to share. For example, in tutorial/MyParams/MyParam.h, a MyParam struct extends GParam and contains int iValue and int iCount members. Once defined, you can use this type with the CGRAPH_CREATE_GPARAM and CGRAPH_GET_GPARAM_WITH_NO_EMPTY macros.
What happens if a node tries to retrieve a parameter that does not exist?
When using the CGRAPH_GET_GPARAM_WITH_NO_EMPTY(Type, key) macro, the underlying getGParamWithNoEmpty method will throw an exception if the specified key is not found in the GParamManager's registry. This prevents null pointer dereferences and forces developers to ensure parameters are created (typically in an init() method) before they are accessed in run().
Are GParam operations thread-safe when nodes run in parallel?
Yes, but thread safety requires explicit locking. The GParam base class contains a _param_shared_lock_ member (either std::shared_mutex or std::recursive_mutex). To protect concurrent access, use the CGRAPH_PARAM_WRITE_CODE_BLOCK(param) macro for exclusive write access or CGRAPH_PARAM_READ_CODE_BLOCK(param) for shared read access. These macros create RAII-style locks that automatically release when the scope exits.
Can I trace which node created a specific parameter for debugging purposes?
Yes, CGraph supports back-trace functionality for parameters. When creating a parameter, use the CGRAPH_CREATE_GPARAM_WITH_BACKTRACE macro instead of the standard creation macro. This records the creating node's name inside the GParam object via the addBacktrace method (found in src/GraphCtrl/GraphParam/GParam.h), allowing you to trace the origin of shared data during pipeline debugging or inspection.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →