Skip to content

Commit a0903a1

Browse files
authored
Merge pull request #192 from hhenson/cpp_v2_1
Refactor: Move all state from TimeSeriesType/Input/Output interfaces …
2 parents c01d4a8 + ab2e994 commit a0903a1

29 files changed

Lines changed: 950 additions & 849 deletions

CLAUDE.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,46 @@ rm .venv/lib/python3.12/site-packages/hgraph/_hgraph.cpython-312-darwin.so
5050
ln -s `pwd`/cmake-build-debug/cpp/src/cpp/_hgraph.cpython-312-darwin.so `pwd`/.venv/lib/python3.12/site-packages/hgraph/_hgraph.cpython-312-darwin.so
5151

5252
# 5. Verify installation
53-
uv run pytest hgraph_unit_tests/_operators/test_const.py -v
53+
HGRAPH_USE_CPP=1 uv run pytest hgraph_unit_tests/_operators/test_const.py -v
5454
```
5555

56+
## Feature Switches
57+
58+
The project has a concept of a feature switch to be able to turn on and off features. These can be defined in a number
59+
of ways, but the most useful to an agent is the environment variable.
60+
61+
The current key feature is the use of C++ for the core runtime. This is controlled by the env var ``HGRAPH_USE_CPP=1``.
62+
When set, the code will use the C++ implementation; when it is off, the python implementation is used.
63+
This is important to check when validating re-factoring as it is easy to forget to set the env var and not test the change.
64+
5665
## Testing
5766

5867
The tests are currently all in python and can be found in ``hgraph_unit_tests``.
5968

69+
```bash
70+
HGRAPH_USE_CPP=1 uv run pytest hgraph_unit_tests
71+
```
72+
6073
## Project Structure
6174

6275
The project model uses ``uv`` to manage the project. This uses the ``pyproject.toml`` file to define the project structure.
63-
The C++ build is managed by CMake, and the integration between uv and CMake is the scikit-build-core builder.
76+
The C++ build is managed by CMake, and the integration between uv and CMake is the ``scikit-build-core`` builder.
6477
We use pytest for running the python unit tests.
6578

66-
The project is index using the Context7 mcp. Use this mcp for project-specific API and documentation,
79+
The project is index using the ``Context7`` mcp. Use this mcp for project-specific API and documentation,
6780
it indexes most open source projects.
6881

82+
## Debugging / Development
83+
84+
Techniques to apply:
85+
86+
1. Tracing: The python code is generally speaking correct, and does make the unit tests pass. When there is a bug in
87+
the c++ code, putting trace code into the Python implementation and comparing it to the equivalent C++ code
88+
can help identify the differences in behavior.
89+
2. Validation: When making changes to the code, always compile the changes to make sure the code works, then run the
90+
unit tests to ensure all non-xfail / skipped tests are still working (and remember to set the env var
91+
``HGRAPH_USE_CPP=1``). You can't report success if there are failing tests.
92+
3. Checks: Always make sure the env is set up correctly, this can be a quick spot check that the symlink for the
93+
.so is in the right place and linked. Otherwise, it results in checks against an incorrect version of
94+
the code.
95+

cpp/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ set(HGRAPH_INCLUDES
167167
include/hgraph/runtime/evaluation_engine.h
168168
include/hgraph/runtime/graph_executor.h
169169
include/hgraph/runtime/record_replay.h
170+
include/hgraph/runtime/observers/evaluation_profiler.h
171+
include/hgraph/runtime/observers/evaluation_trace.h
172+
include/hgraph/runtime/observers/inspection_observer.h
173+
include/hgraph/types/base_time_series.h
170174
include/hgraph/types/constants.h
171175
include/hgraph/types/error_type.h
172176
include/hgraph/types/feature_extension.h

cpp/include/hgraph/hgraph_forward_declarations.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ namespace hgraph {
4848
using time_series_type_ptr = nanobind::ref<TimeSeriesType>;
4949

5050
struct TimeSeriesInput;
51+
struct BaseTimeSeriesInput;
5152
using time_series_input_ptr = nanobind::ref<TimeSeriesInput>;
5253

5354
struct TimeSeriesBundleInput;
@@ -57,6 +58,7 @@ namespace hgraph {
5758
using time_series_bundle_output_ptr = nanobind::ref<TimeSeriesBundleOutput>;
5859

5960
struct TimeSeriesOutput;
61+
struct BaseTimeSeriesOutput;
6062
using time_series_output_ptr = nanobind::ref<TimeSeriesOutput>;
6163

6264
struct TimeSeriesReference;
@@ -86,7 +88,7 @@ namespace hgraph {
8688
using c_string_ref = std::reference_wrapper<const std::string>;
8789

8890
template<typename T_TS>
89-
concept TimeSeriesT = std::is_same_v<T_TS, TimeSeriesInput> || std::is_same_v<T_TS, TimeSeriesOutput>;
91+
concept TimeSeriesT = std::is_base_of_v<TimeSeriesInput, T_TS> || std::is_base_of_v<TimeSeriesOutput, T_TS>;
9092

9193
struct OutputBuilder;
9294
struct InputBuilder;

cpp/include/hgraph/runtime/observers/inspection_observer.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ namespace hgraph {
118118
double _progress_interval;
119119
std::chrono::time_point<std::chrono::high_resolution_clock> _progress_last_time;
120120
bool _compute_sizes;
121-
bool _track_recent_performance;
122121

123122
std::set<std::vector<int>> _graph_subscriptions;
124123
std::set<std::vector<int>> _node_subscriptions;
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
//
2+
// Base Time Series Input/Output Shim Layer
3+
//
4+
// This file provides BaseTimeSeriesInput and BaseTimeSeriesOutput classes that sit
5+
// between the abstract TimeSeriesInput/TimeSeriesOutput interfaces and the concrete
6+
// implementations. The Base classes hold all the state and concrete behavior.
7+
//
8+
// The goal is to eventually make TimeSeriesInput/TimeSeriesOutput pure virtual interfaces,
9+
// with all implementation details moved to these Base classes.
10+
//
11+
12+
#ifndef BASE_TIME_SERIES_H
13+
#define BASE_TIME_SERIES_H
14+
15+
#include <hgraph/types/time_series_type.h>
16+
#include <unordered_set>
17+
18+
namespace hgraph {
19+
struct OutputBuilder;
20+
21+
/**
22+
* @brief Base class for TimeSeriesOutput implementations
23+
*
24+
* This class holds all the state and concrete behavior currently in TimeSeriesOutput.
25+
* All concrete output types should extend this class rather than TimeSeriesOutput directly.
26+
* Eventually, TimeSeriesOutput will become a pure virtual interface.
27+
*/
28+
struct HGRAPH_EXPORT BaseTimeSeriesOutput : TimeSeriesOutput {
29+
using ptr = nb::ref<BaseTimeSeriesOutput>;
30+
31+
explicit BaseTimeSeriesOutput(const node_ptr &parent) : _parent_ts_or_node{parent} {}
32+
explicit BaseTimeSeriesOutput(const TimeSeriesType::ptr &parent) : _parent_ts_or_node{parent} {}
33+
34+
// Implement TimeSeriesType pure virtuals
35+
[[nodiscard]] node_ptr owning_node() override;
36+
[[nodiscard]] node_ptr owning_node() const override;
37+
[[nodiscard]] graph_ptr owning_graph() override;
38+
[[nodiscard]] graph_ptr owning_graph() const override;
39+
[[nodiscard]] bool is_reference() const override;
40+
[[nodiscard]] bool has_reference() const override;
41+
void reset_parent_or_node() override;
42+
[[nodiscard]] bool has_parent_or_node() const override;
43+
[[nodiscard]] bool has_owning_node() const override;
44+
void re_parent(const node_ptr &parent) override;
45+
void re_parent(const TimeSeriesType::ptr &parent) override;
46+
47+
// Inherited interface implementations
48+
[[nodiscard]] bool modified() const override;
49+
[[nodiscard]] engine_time_t last_modified_time() const override;
50+
void mark_invalid() override;
51+
void mark_modified() override;
52+
void mark_child_modified(TimeSeriesOutput &child, engine_time_t modified_time) override;
53+
[[nodiscard]] bool valid() const override;
54+
[[nodiscard]] bool all_valid() const override;
55+
[[nodiscard]] TimeSeriesOutput::ptr parent_output() const override;
56+
[[nodiscard]] TimeSeriesOutput::ptr parent_output() override;
57+
[[nodiscard]] bool has_parent_output() const override;
58+
59+
void subscribe(Notifiable *node) override;
60+
void un_subscribe(Notifiable *node) override;
61+
void builder_release_cleanup() override;
62+
63+
bool can_apply_result(nb::object value) override;
64+
void clear() override;
65+
void invalidate() override;
66+
void mark_modified(engine_time_t modified_time) override;
67+
68+
static void register_with_nanobind(nb::module_ &m);
69+
70+
protected:
71+
// State and helpers moved from TimeSeriesType
72+
TimeSeriesType::ptr &_parent_time_series() const;
73+
TimeSeriesType::ptr &_parent_time_series();
74+
bool _has_parent_time_series() const;
75+
void _set_parent_time_series(TimeSeriesType *ts);
76+
node_ptr _owning_node() const;
77+
78+
void _notify(engine_time_t modified_time);
79+
void _reset_last_modified_time();
80+
81+
private:
82+
friend OutputBuilder;
83+
using TsOrNode = std::variant<TimeSeriesType::ptr, node_ptr>;
84+
std::optional<TsOrNode> _parent_ts_or_node{};
85+
std::unordered_set<Notifiable *> _subscribers{};
86+
engine_time_t _last_modified_time{MIN_DT};
87+
};
88+
89+
/**
90+
* @brief Base class for TimeSeriesInput implementations
91+
*
92+
* This class holds all the state and concrete behavior currently in TimeSeriesInput.
93+
* All concrete input types should extend this class rather than TimeSeriesInput directly.
94+
* Eventually, TimeSeriesInput will become a pure virtual interface.
95+
*/
96+
struct HGRAPH_EXPORT BaseTimeSeriesInput : TimeSeriesInput {
97+
using ptr = nb::ref<BaseTimeSeriesInput>;
98+
99+
explicit BaseTimeSeriesInput(const node_ptr &parent) : _parent_ts_or_node{parent} {}
100+
explicit BaseTimeSeriesInput(const TimeSeriesType::ptr &parent) : _parent_ts_or_node{parent} {}
101+
102+
// Implement TimeSeriesType pure virtuals
103+
[[nodiscard]] node_ptr owning_node() override;
104+
[[nodiscard]] node_ptr owning_node() const override;
105+
[[nodiscard]] graph_ptr owning_graph() override;
106+
[[nodiscard]] graph_ptr owning_graph() const override;
107+
[[nodiscard]] bool is_reference() const override;
108+
[[nodiscard]] bool has_reference() const override;
109+
void reset_parent_or_node() override;
110+
[[nodiscard]] bool has_parent_or_node() const override;
111+
[[nodiscard]] bool has_owning_node() const override;
112+
void re_parent(const node_ptr &parent) override;
113+
void re_parent(const TimeSeriesType::ptr &parent) override;
114+
115+
// Inherited interface implementations
116+
[[nodiscard]] TimeSeriesInput::ptr parent_input() const override;
117+
[[nodiscard]] bool has_parent_input() const override;
118+
[[nodiscard]] bool bound() const override;
119+
[[nodiscard]] bool has_peer() const override;
120+
[[nodiscard]] time_series_output_ptr output() const override;
121+
122+
bool bind_output(time_series_output_ptr output_) override;
123+
void un_bind_output(bool unbind_refs) override;
124+
125+
[[nodiscard]] bool active() const override;
126+
void make_active() override;
127+
void make_passive() override;
128+
[[nodiscard]] bool has_output() const override;
129+
130+
void builder_release_cleanup() override;
131+
132+
[[nodiscard]] nb::object py_value() const override;
133+
[[nodiscard]] nb::object py_delta_value() const override;
134+
[[nodiscard]] bool modified() const override;
135+
[[nodiscard]] bool valid() const override;
136+
[[nodiscard]] bool all_valid() const override;
137+
[[nodiscard]] engine_time_t last_modified_time() const override;
138+
[[nodiscard]] time_series_reference_output_ptr reference_output() const override;
139+
140+
[[nodiscard]] const TimeSeriesInput *get_input(size_t index) const override;
141+
[[nodiscard]] TimeSeriesInput *get_input(size_t index) override;
142+
143+
static void register_with_nanobind(nb::module_ &m);
144+
145+
protected:
146+
// State and helpers moved from TimeSeriesType
147+
TimeSeriesType::ptr &_parent_time_series() const;
148+
TimeSeriesType::ptr &_parent_time_series();
149+
bool _has_parent_time_series() const;
150+
void _set_parent_time_series(TimeSeriesType *ts);
151+
node_ptr _owning_node() const;
152+
153+
// Protected virtual methods for derived classes to override
154+
virtual bool do_bind_output(time_series_output_ptr &output_);
155+
virtual void do_un_bind_output(bool unbind_refs);
156+
157+
void notify(engine_time_t modified_time) override;
158+
virtual void notify_parent(TimeSeriesInput *child, engine_time_t modified_time);
159+
160+
void set_sample_time(engine_time_t sample_time);
161+
[[nodiscard]] engine_time_t sample_time() const;
162+
[[nodiscard]] bool sampled() const;
163+
164+
void reset_output();
165+
void set_output(time_series_output_ptr output);
166+
void set_active(bool active);
167+
168+
private:
169+
using TsOrNode = std::variant<TimeSeriesType::ptr, node_ptr>;
170+
std::optional<TsOrNode> _parent_ts_or_node{};
171+
time_series_output_ptr _output;
172+
time_series_reference_output_ptr _reference_output;
173+
bool _active{false};
174+
engine_time_t _sample_time{MIN_DT};
175+
engine_time_t _notify_time{MIN_DT};
176+
};
177+
178+
} // namespace hgraph
179+
180+
#endif // BASE_TIME_SERIES_H
181+

cpp/include/hgraph/types/ref.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#ifndef REF_H
66
#define REF_H
77

8-
#include <hgraph/types/time_series_type.h>
8+
#include <hgraph/types/base_time_series.h>
99

1010
namespace hgraph {
1111
struct HGRAPH_EXPORT TimeSeriesReference : nb::intrusive_base {
@@ -92,8 +92,8 @@ namespace hgraph {
9292
std::vector<ptr> _items;
9393
};
9494

95-
struct TimeSeriesReferenceOutput : TimeSeriesOutput {
96-
using TimeSeriesOutput::TimeSeriesOutput;
95+
struct TimeSeriesReferenceOutput : BaseTimeSeriesOutput {
96+
using BaseTimeSeriesOutput::BaseTimeSeriesOutput;
9797

9898
[[nodiscard]] bool is_same_type(const TimeSeriesType *other) const override;
9999

@@ -149,9 +149,9 @@ namespace hgraph {
149149
std::unordered_set<TimeSeriesInput::ptr> _reference_observers;
150150
};
151151

152-
struct TimeSeriesReferenceInput : TimeSeriesInput {
152+
struct TimeSeriesReferenceInput : BaseTimeSeriesInput {
153153
using ptr = nb::ref<TimeSeriesReferenceInput>;
154-
using TimeSeriesInput::TimeSeriesInput;
154+
using BaseTimeSeriesInput::BaseTimeSeriesInput;
155155

156156
[[nodiscard]] bool is_same_type(const TimeSeriesType *other) const override {
157157
return dynamic_cast<const TimeSeriesReferenceInput *>(other) != nullptr;

0 commit comments

Comments
 (0)