react-native-audio-api 1.0.0 → 1.1.0-nightly-1df11c0-20261105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,18 @@ auto AudioGraph::operator[](std::uint32_t index) const -> const Vertex & {
16
16
  return nodes[index];
17
17
  }
18
18
 
19
+ auto AudioGraph::operator[](const NodeHandle &handle) -> Vertex & {
20
+ return nodes[handle.index_];
21
+ }
22
+
23
+ auto AudioGraph::operator[](const NodeHandle &handle) const -> const Vertex & {
24
+ return nodes[handle.index_];
25
+ }
26
+
27
+ std::uint32_t AudioGraph::indexOf(const NodeHandle &handle) const {
28
+ return handle.index_;
29
+ }
30
+
19
31
  size_t AudioGraph::size() const {
20
32
  return nodes.size();
21
33
  }
@@ -43,7 +55,7 @@ void AudioGraph::markDirty() {
43
55
  }
44
56
 
45
57
  void AudioGraph::addNode(std::shared_ptr<NodeHandle> handle) {
46
- handle->index = static_cast<std::uint32_t>(nodes.size());
58
+ handle->index_ = static_cast<std::uint32_t>(nodes.size());
47
59
  nodes.emplace_back(std::move(handle));
48
60
  }
49
61
 
@@ -120,7 +132,7 @@ void AudioGraph::sortAndCompact() {
120
132
  if (b != e) {
121
133
  nodes[b] = std::move(nodes[e]);
122
134
  }
123
- nodes[b].handle->index = b;
135
+ nodes[b].handle->index_ = b;
124
136
  b++;
125
137
  }
126
138
 
@@ -242,7 +254,7 @@ void AudioGraph::kahn_toposort() {
242
254
  // Phase 5: update handle indices & reset scratch
243
255
  for (std::uint32_t i = 0; i < n; i++) {
244
256
  if (nodes[i].handle) {
245
- nodes[i].handle->index = i;
257
+ nodes[i].handle->index_ = i;
246
258
  }
247
259
  nodes[i].target_place = -1;
248
260
  }
@@ -103,6 +103,16 @@ class AudioGraph {
103
103
  /// @brief Access node by flat-vector index (const).
104
104
  [[nodiscard]] const Vertex &operator[](std::uint32_t index) const;
105
105
 
106
+ /// @brief Access the node a handle refers to, at its current position.
107
+ [[nodiscard]] Vertex &operator[](const NodeHandle &handle);
108
+
109
+ /// @brief Access the node a handle refers to, at its current position (const).
110
+ [[nodiscard]] const Vertex &operator[](const NodeHandle &handle) const;
111
+
112
+ /// @brief Current flat-vector position of the handle's node.
113
+ /// @note Valid only until the next sortAndCompact(); never cache it across threads.
114
+ [[nodiscard]] std::uint32_t indexOf(const NodeHandle &handle) const;
115
+
106
116
  /// @brief Number of live nodes in the graph.
107
117
  [[nodiscard]] size_t size() const;
108
118
 
@@ -82,7 +82,7 @@ void Graph::advanceUnpulledNodes(std::size_t currentSampleFrame) {
82
82
  Graph::HostVertex *Graph::addNode(std::unique_ptr<GraphObject> audioNode) {
83
83
  // collectDisposedNodes();
84
84
 
85
- auto handle = std::make_shared<NodeHandle>(0, std::move(audioNode));
85
+ auto handle = std::make_shared<NodeHandle>(std::move(audioNode));
86
86
  auto [hostNode, event] = hostGraph.addNode(handle);
87
87
 
88
88
  sendNodeGrowIfNeeded();
@@ -298,8 +298,7 @@ auto HostGraph::removeNode(HostVertex *node) -> Result<AGEvent, GraphError> {
298
298
 
299
299
  node->ghost = true;
300
300
 
301
- return Ok(
302
- [h = node->handle](AudioGraph &graph, auto &) mutable { graph[h->index].orphaned = true; });
301
+ return Ok([h = node->handle](AudioGraph &graph, auto &) mutable { graph[*h].orphaned = true; });
303
302
  }
304
303
 
305
304
  auto HostGraph::addEdge(HostVertex *from, HostVertex *to) -> Result<AGEvent, GraphError> {
@@ -355,7 +354,7 @@ auto HostGraph::addEdge(HostVertex *from, HostVertex *to) -> Result<AGEvent, Gra
355
354
  disposer.dispose(toNode->exchangeInputScratch(std::move(*reservedInputs)));
356
355
  // dispose the unique_ptr
357
356
  disposer.dispose(std::move(reservedInputs));
358
- graph.pool().push(graph[hTo->index].input_head, hFrom->index);
357
+ graph.pool().push(graph[*hTo].input_head, graph.indexOf(*hFrom));
359
358
  graph.markDirty();
360
359
  });
361
360
  }
@@ -395,7 +394,7 @@ auto HostGraph::removeEdge(HostVertex *from, HostVertex *to) -> Result<AGEvent,
395
394
  AudioGraph &graph, auto &disposer) mutable {
396
395
  applyChannelNegotiations(*negotiations, disposer);
397
396
  disposer.dispose(std::move(negotiations));
398
- graph.pool().remove(graph[hTo->index].input_head, hFrom->index);
397
+ graph.pool().remove(graph[*hTo].input_head, graph.indexOf(*hFrom));
399
398
  graph.markDirty();
400
399
  });
401
400
  }
@@ -406,8 +405,6 @@ auto HostGraph::removeAllEdges(HostVertex *from) -> Result<AGEvent, GraphError>
406
405
  return Err(GraphError::NODE_NOT_FOUND);
407
406
  }
408
407
 
409
- auto pairs = std::vector<std::pair<std::uint32_t, std::uint32_t>>();
410
- pairs.reserve(from->outputs.size());
411
408
  auto formerOutputs = std::vector<HostVertex *>();
412
409
  formerOutputs.reserve(from->outputs.size());
413
410
 
@@ -417,7 +414,6 @@ auto HostGraph::removeAllEdges(HostVertex *from) -> Result<AGEvent, GraphError>
417
414
  to->inputs.erase(itIn);
418
415
  }
419
416
  edgeCount_--;
420
- pairs.emplace_back(from->handle->index, to->handle->index);
421
417
  formerOutputs.push_back(to);
422
418
  }
423
419
  from->outputs.clear();
@@ -432,15 +428,23 @@ auto HostGraph::removeAllEdges(HostVertex *from) -> Result<AGEvent, GraphError>
432
428
  // that lost this input re-derives its layout.
433
429
  auto negotiations = collectNegotiations(++channelLayoutTerm_, formerOutputs);
434
430
 
435
- return Ok([pairs = std::move(pairs), negotiations = std::move(negotiations)](
436
- AudioGraph &graph, auto &disposer) mutable {
431
+ auto formerOutputHandles = std::vector<std::shared_ptr<NodeHandle>>();
432
+ formerOutputHandles.reserve(from->outputs.size());
433
+ std::ranges::for_each(formerOutputs, [&formerOutputHandles](const auto *to) {
434
+ formerOutputHandles.push_back(to->handle);
435
+ });
436
+
437
+ return Ok([hFrom = from->handle,
438
+ formerOutputHandles = std::move(formerOutputHandles),
439
+ negotiations = std::move(negotiations)](AudioGraph &graph, auto &disposer) mutable {
437
440
  applyChannelNegotiations(*negotiations, disposer);
438
441
  disposer.dispose(std::move(negotiations));
439
- for (const auto &[fromIdx, toIdx] : pairs) {
440
- graph.pool().remove(graph[toIdx].input_head, fromIdx);
442
+ for (const auto &hTo : formerOutputHandles) {
443
+ graph.pool().remove(graph[*hTo].input_head, graph.indexOf(*hFrom));
441
444
  }
442
445
  graph.markDirty();
443
- disposer.dispose(std::move(pairs));
446
+ // Off the audio thread, so these handle releases never run here.
447
+ disposer.dispose(std::move(formerOutputHandles));
444
448
  });
445
449
  }
446
450
 
@@ -510,7 +514,7 @@ std::optional<HostGraph::AGEvent> HostGraph::linkNodes(HostVertex *from, HostVer
510
514
  linkCount_++;
511
515
 
512
516
  return AGEvent([hFrom = from->handle, hTo = to->handle](AudioGraph &graph, auto &) mutable {
513
- graph.pool().push(graph[hFrom->index].link_head, hTo->index);
517
+ graph.pool().push(graph[*hFrom].link_head, graph.indexOf(*hTo));
514
518
  graph.markDirty();
515
519
  });
516
520
  }
@@ -4,7 +4,6 @@
4
4
 
5
5
  namespace audioapi::utils::graph {
6
6
 
7
- NodeHandle::NodeHandle(std::uint32_t index, std::unique_ptr<GraphObject> audioNode)
8
- : audioNode(std::move(audioNode)), index(index) {}
7
+ NodeHandle::NodeHandle(std::unique_ptr<GraphObject> audioNode) : audioNode(std::move(audioNode)) {}
9
8
 
10
9
  } // namespace audioapi::utils::graph
@@ -10,8 +10,8 @@ namespace audioapi::utils::graph {
10
10
  /// @brief Shared handle bridging HostGraph and AudioGraph.
11
11
  ///
12
12
  /// A single heap allocation stores both the payload node and the mutable
13
- /// index into AudioGraph's flat vector. AudioGraph updates the index during
14
- /// compaction and topological sort.
13
+ /// index into AudioGraph's flat vector. AudioGraph assigns the index in
14
+ /// addNode() and updates it during compaction and topological sort.
15
15
  ///
16
16
  /// Ownership model (shared_ptr):
17
17
  /// - Created on the main thread via std::make_shared.
@@ -21,11 +21,18 @@ namespace audioapi::utils::graph {
21
21
  /// - When AudioGraph compacts out an orphaned node it releases its shared_ptr
22
22
  /// (refcount 2 → 1). HostGraph detects use_count() == 1 and destroys the
23
23
  /// ghost + payload on the main thread.
24
+ ///
25
+ /// The position is private to AudioGraph: only the audio thread may read it,
26
+ /// through `AudioGraph::operator[](const NodeHandle &)` or `AudioGraph::indexOf`,
27
+ /// because every toposort and compaction rewrites it.
24
28
  struct NodeHandle {
25
29
  std::unique_ptr<GraphObject> audioNode; // payload graph object (may be null in tests)
26
- std::uint32_t index; // current position in AudioGraph::nodes
27
30
 
28
- NodeHandle(std::uint32_t index, std::unique_ptr<GraphObject> audioNode);
31
+ explicit NodeHandle(std::unique_ptr<GraphObject> audioNode);
32
+
33
+ private:
34
+ friend class AudioGraph;
35
+ std::uint32_t index_ = 0; // current position in AudioGraph::nodes
29
36
  };
30
37
 
31
38
  } // namespace audioapi::utils::graph
@@ -23,7 +23,7 @@ It has very limited task that needs to be performed very fast, so it is designed
23
23
  `InputPool.hpp` contains the `InputPool` class which is responsible for managing the pool of edges. It is designed to be as efficient as possible, so it uses a free list to manage the pool of edges. It also provides API for allocating and deallocating edges. It is designed to be used on the audio thread, so it is not thread safe and should be used within the `AudioGraph` class. Or its events.
24
24
 
25
25
  ## NodeHandle
26
- `NodeHandle.hpp` contains the `NodeHandle` class which is responsible for managing node index in AudioGraph. Indices are 32 bit to minimize memory usage and maximize cache efficiency. So it is only to be used in `AGEvent` closures to reference nodes in `AudioGraph` only having a handle to it.
26
+ `NodeHandle.h` contains the `NodeHandle` class which is responsible for managing node index in AudioGraph. Indices are 32 bit to minimize memory usage and maximize cache efficiency. So it is only to be used in `AGEvent` closures to reference nodes in `AudioGraph` only having a handle to it.
27
27
 
28
28
  ## Disposer
29
29
  `Disposer.hpp` contains the `Disposer` class which is responsible for managing the disposal of nodes and edges. It is designed to be used on single thread. It has a worker thread that we can offload destruction onto through SPSC queue. It is more robust then previous implementation allowing wide range of payloads to be disposed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-audio-api",
3
- "version": "1.0.0",
3
+ "version": "1.1.0-nightly-1df11c0-20261105",
4
4
  "description": "react-native-audio-api provides system for controlling audio in React Native environment compatible with Web Audio API specification",
5
5
  "bin": {
6
6
  "setup-rn-audio-api-web": "./scripts/setup-rn-audio-api-web.js"