Skip to content

Commit fa4d2f0

Browse files
committed
Merge branch 'feat/graph_refactor_2' into feat/integrate-new-memory-disposer
2 parents d9d1791 + a010057 commit fa4d2f0

20 files changed

Lines changed: 1179 additions & 251 deletions

packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <audioapi/core/types/ChannelCountMode.h>
55
#include <audioapi/core/types/ChannelInterpretation.h>
66
#include <audioapi/core/utils/Constants.h>
7+
#include <audioapi/core/utils/graph/GraphObject.hpp>
78
#include <audioapi/types/NodeOptions.h>
89
#include <audioapi/utils/AudioBuffer.hpp>
910

@@ -18,7 +19,7 @@ namespace audioapi {
1819

1920
class AudioParam;
2021

21-
class AudioNode : public std::enable_shared_from_this<AudioNode> {
22+
class AudioNode : public utils::graph::GraphObject, public std::enable_shared_from_this<AudioNode> {
2223
public:
2324
explicit AudioNode(
2425
const std::shared_ptr<BaseAudioContext> &context,
@@ -62,7 +63,15 @@ class AudioNode : public std::enable_shared_from_this<AudioNode> {
6263
return false;
6364
}
6465

65-
virtual bool canBeDestructed() const;
66+
bool canBeDestructed() const override;
67+
68+
[[nodiscard]] AudioNode *asAudioNode() override {
69+
return this;
70+
}
71+
72+
[[nodiscard]] const AudioNode *asAudioNode() const override {
73+
return this;
74+
}
6675

6776
protected:
6877
friend class AudioGraphManager;

packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <audioapi/core/types/ParamChangeEventType.h>
66
#include <audioapi/core/utils/AudioParamEventQueue.h>
77
#include <audioapi/core/utils/ParamChangeEvent.hpp>
8+
#include <audioapi/core/utils/graph/GraphObject.hpp>
89
#include <audioapi/utils/AudioBuffer.hpp>
910

1011
#include <audioapi/utils/CrossThreadEventScheduler.hpp>
@@ -15,7 +16,7 @@
1516

1617
namespace audioapi {
1718

18-
class AudioParam {
19+
class AudioParam : public utils::graph::GraphObject {
1920
public:
2021
explicit AudioParam(
2122
float defaultValue,
@@ -79,6 +80,19 @@ class AudioParam {
7980
return false;
8081
}
8182

83+
/// @brief Temporary lifecycle policy for GraphObject-based graph storage.
84+
[[nodiscard]] bool canBeDestructed() const override {
85+
return true;
86+
}
87+
88+
[[nodiscard]] AudioParam *asAudioParam() override {
89+
return this;
90+
}
91+
92+
[[nodiscard]] const AudioParam *asAudioParam() const override {
93+
return this;
94+
}
95+
8296
/// Audio-Thread only methods
8397
/// These methods are called only from the Audio rendering thread.
8498

packages/react-native-audio-api/common/cpp/audioapi/core/utils/graph/AudioGraph.hpp

Lines changed: 27 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
#pragma once
22

3-
#include <audioapi/core/AudioNode.h>
43
#include <audioapi/core/utils/graph/InputPool.hpp>
54
#include <audioapi/core/utils/graph/NodeHandle.hpp>
65

76
#include <algorithm>
87
#include <cassert>
9-
#include <concepts>
108
#include <cstdint>
119
#include <iterator>
1210
#include <memory>
@@ -16,26 +14,22 @@
1614

1715
namespace audioapi::utils::graph {
1816

19-
template <typename T>
20-
concept AudioGraphNode = std::derived_from<T, ::audioapi::AudioNode>;
21-
2217
/// @brief Cache-friendly, index-stable node storage with in-place topological sort.
2318
///
2419
/// Nodes are stored in a flat vector that is kept topologically sorted
2520
/// (sources first, sinks last). The graph supports O(V+E) compaction of
2621
/// orphaned nodes and O(1)-extra-space Kahn's toposort.
2722
///
2823
/// @note Can store at most 2^30 nodes due to bit-packed indices (~10^9).
29-
template <AudioGraphNode NodeType>
3024
class AudioGraph {
3125
// ── Node ────────────────────────────────────────────────────────────────
3226

3327
struct Node {
3428
Node() = default;
35-
explicit Node(std::shared_ptr<NodeHandle<NodeType>> handle) : handle(handle) {}
29+
explicit Node(std::shared_ptr<NodeHandle> handle) : handle(handle) {}
3630

37-
std::shared_ptr<NodeHandle<NodeType>> handle = nullptr; // owned handle bridging to HostGraph
38-
std::uint32_t input_head = InputPool::kNull; // head of input linked list in pool_
31+
std::shared_ptr<NodeHandle> handle = nullptr; // owned handle bridging to HostGraph
32+
std::uint32_t input_head = InputPool::kNull; // head of input linked list in pool_
3933

4034
std::uint32_t topo_out_degree : 31 = 0; // scratch — Kahn's out-degree counter
4135
unsigned will_be_deleted : 1 = 0; // scratch — marked for compaction removal
@@ -60,10 +54,10 @@ class AudioGraph {
6054
AudioGraph(AudioGraph &&) noexcept = default;
6155
AudioGraph &operator=(AudioGraph &&) noexcept = default;
6256

63-
/// @brief Entry returned by iter() — a reference to the audio node and a view of its inputs.
57+
/// @brief Entry returned by iter() — a reference to the graph object and a view of its inputs.
6458
template <typename InputsView>
6559
struct Entry {
66-
NodeType &audioNode;
60+
GraphObject &graphObject;
6761
InputsView inputs;
6862
};
6963

@@ -83,13 +77,13 @@ class AudioGraph {
8377

8478
/// @brief Provides an iterable view of the nodes in topological order.
8579
///
86-
/// Each entry contains a reference to the AudioNode and an immutable view
87-
/// of its inputs (as references to AudioNodes).
80+
/// Each entry contains a reference to the GraphObject and an immutable view
81+
/// of its inputs (as references to GraphObject).
8882
///
8983
/// ## Example usage:
9084
/// ```cpp
91-
/// for (auto [audioNode, inputs] : graph.iter()) {
92-
/// // process audioNode and its inputs
85+
/// for (auto [graphObject, inputs] : graph.iter()) {
86+
/// // process graphObject and its inputs
9387
/// }
9488
/// ```
9589
/// @note Lifetime of entries is bound to this graph — they are not owned.
@@ -115,14 +109,14 @@ class AudioGraph {
115109

116110
/// @brief Adds a new node. AudioGraph takes shared ownership of the handle.
117111
/// @param handle shared NodeHandle bridging to HostGraph
118-
void addNode(std::shared_ptr<NodeHandle<NodeType>> handle);
112+
void addNode(std::shared_ptr<NodeHandle> handle);
119113

120114
/// @brief Recomputes topological order (if dirty), then compacts the graph
121115
/// by removing orphaned, input-free, destructible nodes.
122116
///
123117
/// When a node is compacted out its `shared_ptr<NodeHandle>` is released
124118
/// (refcount drops 2 → 1). HostGraph detects this via `use_count() == 1`
125-
/// and destroys the ghost + AudioNode on the main thread.
119+
/// and destroys the ghost + GraphObject on the main thread.
126120
///
127121
/// Uses a two-pass approach: pass 1 marks deletions (cascading in topo
128122
/// order) and computes index remapping; pass 2 remaps inputs and shifts
@@ -155,68 +149,59 @@ class AudioGraph {
155149

156150
// ── Accessors ─────────────────────────────────────────────────────────────
157151

158-
template <AudioGraphNode NodeType>
159-
auto AudioGraph<NodeType>::operator[](std::uint32_t index) -> Node & {
152+
inline auto AudioGraph::operator[](std::uint32_t index) -> Node & {
160153
return nodes[index];
161154
}
162155

163-
template <AudioGraphNode NodeType>
164-
auto AudioGraph<NodeType>::operator[](std::uint32_t index) const -> const Node & {
156+
inline auto AudioGraph::operator[](std::uint32_t index) const -> const Node & {
165157
return nodes[index];
166158
}
167159

168-
template <AudioGraphNode NodeType>
169-
size_t AudioGraph<NodeType>::size() const {
160+
inline size_t AudioGraph::size() const {
170161
return nodes.size();
171162
}
172163

173-
template <AudioGraphNode NodeType>
174-
bool AudioGraph<NodeType>::empty() const {
164+
inline bool AudioGraph::empty() const {
175165
return nodes.empty();
176166
}
177167

178-
template <AudioGraphNode NodeType>
179-
auto AudioGraph<NodeType>::iter() {
180-
return nodes | std::views::transform([this](Node &node) {
168+
inline auto AudioGraph::iter() {
169+
return nodes |
170+
std::views::filter([](const Node &n) { return n.handle->audioNode->isProcessable(); }) |
171+
std::views::transform([this](Node &node) {
181172
return Entry{
182173
*node.handle->audioNode,
183174
pool_.view(node.input_head) |
184-
std::views::transform([this](std::uint32_t idx) -> const NodeType & {
175+
std::views::transform([this](std::uint32_t idx) -> const GraphObject & {
185176
return *nodes[idx].handle->audioNode;
186177
})};
187178
});
188179
}
189180

190-
template <AudioGraphNode NodeType>
191-
InputPool &AudioGraph<NodeType>::pool() {
181+
inline InputPool &AudioGraph::pool() {
192182
return pool_;
193183
}
194184

195-
template <AudioGraphNode NodeType>
196-
const InputPool &AudioGraph<NodeType>::pool() const {
185+
inline const InputPool &AudioGraph::pool() const {
197186
return pool_;
198187
}
199188

200-
template <AudioGraphNode NodeType>
201-
void AudioGraph<NodeType>::reserveNodes(std::uint32_t capacity) {
189+
inline void AudioGraph::reserveNodes(std::uint32_t capacity) {
202190
nodes.reserve(capacity);
203191
}
204192

205193
// ── Mutators ──────────────────────────────────────────────────────────────
206194

207-
template <AudioGraphNode NodeType>
208-
void AudioGraph<NodeType>::markDirty() {
195+
inline void AudioGraph::markDirty() {
209196
topo_order_dirty = true;
210197
}
211198

212-
template <AudioGraphNode NodeType>
213-
void AudioGraph<NodeType>::addNode(std::shared_ptr<NodeHandle<NodeType>> handle) {
199+
inline void AudioGraph::addNode(std::shared_ptr<NodeHandle> handle) {
214200
handle->index = static_cast<std::uint32_t>(nodes.size());
215201
nodes.emplace_back(std::move(handle));
216202
}
217203

218-
template <AudioGraphNode NodeType>
219-
void AudioGraph<NodeType>::process() {
204+
inline void AudioGraph::process() {
220205
if (topo_order_dirty) {
221206
topo_order_dirty = false;
222207
kahn_toposort();
@@ -293,8 +278,7 @@ void AudioGraph<NodeType>::process() {
293278

294279
// ── Kahn's toposort ───────────────────────────────────────────────────────
295280

296-
template <AudioGraphNode NodeType>
297-
void AudioGraph<NodeType>::kahn_toposort() {
281+
inline void AudioGraph::kahn_toposort() {
298282
const auto n = static_cast<std::uint32_t>(nodes.size());
299283
if (n <= 1)
300284
return;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#pragma once
2+
3+
#include <audioapi/core/utils/graph/GraphObject.hpp>
4+
5+
namespace audioapi {
6+
class AudioParam;
7+
}
8+
9+
namespace audioapi::utils::graph {
10+
11+
/// @brief Lightweight graph-only node that represents an AudioParam connection.
12+
///
13+
/// A BridgeNode sits between a source AudioNode and the owner AudioNode of a
14+
/// param, forming the path: source → bridge → owner. This lets the graph
15+
/// system detect cycles and compute correct topological ordering for param
16+
/// connections without creating real ownership dependencies.
17+
///
18+
/// BridgeNodes are:
19+
/// - **Not processable** — skipped by `AudioGraph::iter()`.
20+
/// - **Always destructible** — removed by compaction when orphaned with no inputs.
21+
/// - **Non-owning** — stores a raw `AudioParam*` whose lifetime is guaranteed
22+
/// by the owner node.
23+
class BridgeNode final : public GraphObject {
24+
public:
25+
explicit BridgeNode(AudioParam *param) : param_(param) {}
26+
27+
[[nodiscard]] bool isProcessable() const override {
28+
return false;
29+
}
30+
31+
[[nodiscard]] bool canBeDestructed() const override {
32+
return true;
33+
}
34+
35+
/// @brief Returns the param this bridge represents a connection to.
36+
[[nodiscard]] AudioParam *param() const {
37+
return param_;
38+
}
39+
40+
private:
41+
AudioParam *param_; // non-owning — lifetime guaranteed by owner node
42+
};
43+
44+
} // namespace audioapi::utils::graph

0 commit comments

Comments
 (0)