Conversation
There was a problem hiding this comment.
Pull request overview
Bro's inteligence needs to be studied
This pull request introduces the foundational infrastructure for a new graph processing system that will enable asynchronous updates, multiple outputs, cycles with delay nodes, and allocation-free audio thread operation. The changes are non-integrated, consisting of new data structures, algorithms, and comprehensive unit tests.
Changes:
- Implements new thread-safe graph system with separate HostGraph (main thread) and AudioGraph (audio thread) coordination
- Adds allocation-free InputPool for edge storage and custom allocators/disposers
- Creates comprehensive test infrastructure with allocation tracking, fuzz testing, and concurrency tests
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| AudioGraph.hpp | Implements cache-friendly topological sort and compaction with O(1) extra space |
| HostGraph.hpp | Main-thread graph mirror with cycle detection via DFS |
| Graph.hpp | Thread-safe coordinator bridging host/audio graphs via SPSC channel |
| InputPool.hpp | Free-list pool for allocation-free edge storage during audio processing |
| FatFunction.hpp | Fixed-size function wrapper avoiding heap allocation |
| NodeHandle.hpp | Shared handle bridging HostGraph and AudioGraph ownership |
| HostNode.hpp | RAII base class for host-side audio nodes |
| Disposer.hpp | Off-thread disposal system for old buffers |
| AudioNode.h/.cpp | Adds canBeDestructed() method for graph compaction control |
| Test files | Comprehensive unit, fuzz, and concurrency tests with allocation tracking |
| Build/CI files | Separate test suite with sanitizer support and Docker environment |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/react-native-audio-api/common/cpp/test/src/graph/GraphCycleDebugTest.cpp
Outdated
Show resolved
Hide resolved
packages/react-native-audio-api/common/cpp/audioapi/core/utils/graph/HostGraph.hpp
Show resolved
Hide resolved
packages/react-native-audio-api/common/cpp/test/src/graph/GraphCycleDebugTest.cpp
Outdated
Show resolved
Hide resolved
packages/react-native-audio-api/common/cpp/audioapi/core/utils/graph/Graph.hpp
Show resolved
Hide resolved
packages/react-native-audio-api/common/cpp/test/src/graph/TestGraphUtils.h
Show resolved
Hide resolved
packages/react-native-audio-api/common/cpp/test/src/graph/SandboxTest.cpp
Show resolved
Hide resolved
michalsek
left a comment
There was a problem hiding this comment.
It feels awkward to write just LGTM under this PR, so here are lyrics for Queens We are the champions. Great job! (And check the copilot comments 🙂)
I've paid my dues
Time after time
I've done my sentence
But committed no crime
And bad mistakes
I've made a few
I've had my share of sand kicked in my face
But I've come through
(We're gonna go on and on and on and on)
We are the champions, my friend
And we'll keep on fighting till the end
We are the champions
We are the champions
No time for losers
Cause we are the champions
Of the world
I've taken my bows
And my curtain calls
You brought me fame and fortune and everything that goes with it
I thank you all
But it's been no bed of roses
No pleasure cruise
I consider it a challenge before the whole human race
And I ain't gonna lose
(We're gonna go on and on and on and on)
We are the champions, my friends
And we'll keep on fighting till the end
We are the champions
We are the champions
No time for losers
Cause we are the champions of the world
We are the champions, my friends
And we'll keep on fighting till the end
We are the champions
We are the champions
No time for losers
Cause we are the champions
Of the world
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…hCycleDebugTest.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…hCycleDebugTest.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
ok there is memory leak possibly due to some time race in tests but seems like a real deal on these actions it needs to be checked, possible cause stale grow pool event in events queue is not properly destructed Edit: Done |
…ansion/react-native-audio-api into feat/graph_refactor_1
| @@ -0,0 +1,226 @@ | |||
|
|
|||
| constexpr size_t dataSize = 64 + sizeof(void*) * 3; | ||
| constexpr size_t alignment = alignof(std::max_align_t); | ||
| constexpr size_t expectedSize = (dataSize + alignment - 1) & ~(alignment - 1); |
There was a problem hiding this comment.
explain more in a code comments why dataSize is like that and expectedSize can be made more readable, we don't need fast bit tricks, since these are only tests, which do not benefit from blazingly fast code
packages/react-native-audio-api/common/cpp/audioapi/core/utils/graph/Disposer.hpp
Show resolved
Hide resolved
packages/react-native-audio-api/common/cpp/audioapi/core/utils/graph/Graph.hpp
Show resolved
Hide resolved
| Graph class is an orhestrator that provides high level API and manages internal structures like `AudioGraph`, `HostGraph`, `SPSC`, `Disposer` and pool capacity. | ||
|
|
||
| ## HostGraph | ||
| `HostGraph.hpp` contains the `HostGraph` class which is responsible for managing the graph structure and providing low level API for it. It keeps track of nodes, edges, their connections, cycles and other internal metadata. It also provides API for adding, removing and connecting nodes. Each modification returns a `AGEvent` which is a closure that can be executed to apply the same modification on corelated `AudioGraph`. This move allows us to keep `HostGraph` in a state after uptade while sending async event to Audio thread, as we know if events are applied in order, we can be sure that the state of `AudioGraph` in time frame T1 will be the same as `HostGraph` in the same time frame T1. |
There was a problem hiding this comment.
| `HostGraph.hpp` contains the `HostGraph` class which is responsible for managing the graph structure and providing low level API for it. It keeps track of nodes, edges, their connections, cycles and other internal metadata. It also provides API for adding, removing and connecting nodes. Each modification returns a `AGEvent` which is a closure that can be executed to apply the same modification on corelated `AudioGraph`. This move allows us to keep `HostGraph` in a state after uptade while sending async event to Audio thread, as we know if events are applied in order, we can be sure that the state of `AudioGraph` in time frame T1 will be the same as `HostGraph` in the same time frame T1. | |
| `HostGraph.hpp` contains the `HostGraph` class which is responsible for managing the graph structure and providing low level API for it. It keeps track of nodes, edges, their connections, cycles and other internal metadata. It also provides API for adding, removing and connecting nodes. Each modification returns a `AGEvent` which is a closure that can be executed to apply the same modification on corelated `AudioGraph`. This move allows us to keep `HostGraph` in a state after update while sending async event to Audio thread, as we know if events are applied in order, we can be sure that the state of `AudioGraph` in time frame T1 will be the same as `HostGraph` in the same time frame T1. |
| > So in worst case HostGraph can silent out edge creation if it thinks it would create a cycle, this is very rare case and false negative is acceptable compared to false negative. I don't even know if this is reachable in any realistic scenario, but it is worth to mention. | ||
| ## AudioGraph | ||
| `AudioGraph.hpp` contains the `AudioGraph` class which is responsible for processing the graph structure on the audio thread. It keeps track of nodes, edges, their inputs and has topological order. It minimazes memory usage, speed of processing and limits memory fragmentation by using `InputPool` to manage edges. It also provides API for processing the graph structure and applying events from `HostGraph`. |
There was a problem hiding this comment.
| `AudioGraph.hpp` contains the `AudioGraph` class which is responsible for processing the graph structure on the audio thread. It keeps track of nodes, edges, their inputs and has topological order. It minimazes memory usage, speed of processing and limits memory fragmentation by using `InputPool` to manage edges. It also provides API for processing the graph structure and applying events from `HostGraph`. | |
| `AudioGraph.hpp` contains the `AudioGraph` class which is responsible for processing the graph structure on the audio thread. It keeps track of nodes, edges, their inputs and has topological order. It minimizes memory usage, speed of processing and limits memory fragmentation by using `InputPool` to manage edges. It also provides API for processing the graph structure and applying events from `HostGraph`. |
| `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. | ||
|
|
||
| ## NodeHandle | ||
| `NodeHandle.hpp` contains the `NodeHandle` class which is responsible for managing node index in AudioGraph. Indexes 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. |
There was a problem hiding this comment.
| `NodeHandle.hpp` contains the `NodeHandle` class which is responsible for managing node index in AudioGraph. Indexes 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. | |
| `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. |
|
|
||
| ## Tips and tricks | ||
| As running docker tests takes forevewer it is recommended to relly on CI/CD. Tests without docker does not have ASAN and may not catch memory leaks or address realated issues. So if anny of these issues occur in CI/CD here is how you can run single test with docker to debug it locally: |
There was a problem hiding this comment.
| As running docker tests takes forevewer it is recommended to relly on CI/CD. Tests without docker does not have ASAN and may not catch memory leaks or address realated issues. So if anny of these issues occur in CI/CD here is how you can run single test with docker to debug it locally: | |
| As running docker tests takes forevewer it is recommended to relly on CI/CD. Tests without docker does not have ASAN and may not catch memory leaks or address realated issues. So if any of these issues occur in CI/CD here is how you can run single test with docker to debug it locally: |
| @@ -0,0 +1,24 @@ | |||
|
|
|||
There was a problem hiding this comment.
you can delete it
| void sendPoolGrowIfNeeded() { | ||
| auto edges = static_cast<std::uint32_t>(hostGraph.edgeCount()); | ||
| if (edges > poolCapacity_ / 2 || (poolCapacity_ == 0 && edges > 0)) { | ||
| std::uint32_t newCap = std::max(static_cast<std::uint32_t>(edges * 2), std::uint32_t{64}); |
There was a problem hiding this comment.
let's include that constraint directly in inputPool ctor
| for (Node *n : nodes) | ||
| delete n; |
There was a problem hiding this comment.
| for (Node *n : nodes) | |
| delete n; | |
| for (Node *n : nodes) { | |
| delete n; | |
| } |
| if (start == end) | ||
| return true; |
There was a problem hiding this comment.
| if (start == end) | |
| return true; | |
| if (start == end) { | |
| return true; | |
| } |
| void *operator new(std::size_t size) { | ||
| audioapi::test::AudioThreadGuard::recordAllocation(); | ||
| void *p = std::malloc(size); | ||
| if (!p) throw std::bad_alloc(); | ||
| return p; | ||
| } | ||
|
|
||
| void *operator new[](std::size_t size) { | ||
| audioapi::test::AudioThreadGuard::recordAllocation(); | ||
| void *p = std::malloc(size); | ||
| if (!p) throw std::bad_alloc(); | ||
| return p; | ||
| } | ||
|
|
There was a problem hiding this comment.
double implementation
| // Drain events (grow + graph-mutation). May allocate — unguarded. | ||
| graph_.processEvents(); |
There was a problem hiding this comment.
why it is allowed to allocate here? as I understand it is mock of audio engine render function, so allocations are forbidden.
There was a problem hiding this comment.
leftover comment will be fixed
Closes #
Base for many features that can not be implemented with current graph processing structure. This refactor does not change anything. It aims to implement new structure for graph processing which will allow:
This pull requests contains only not integrated implementations and a lot of unit tests
Introduced changes
Checklist