Skip to content

Commit e9ddfee

Browse files
committed
Merging block events; trivially copyable block event subtypes; wasm nudge
1 parent 5cc7a64 commit e9ddfee

File tree

6 files changed

+50
-33
lines changed

6 files changed

+50
-33
lines changed

js/packages/offline-renderer/__tests__/block-events.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ test("block events", async function () {
1212
});
1313

1414
// Graph
15-
core.render(...unpack(createNode("midinotein", {}, []), 2));
15+
core.render(...unpack(createNode("midinoteunpack", {}, []), 2));
1616

1717
// Ten blocks of data
1818
let inps = [];

js/packages/offline-renderer/elementary-wasm.cjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

js/packages/web-renderer/raw/elementary-wasm.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

runtime/elem/BlockEvents.h

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ struct MidiEvent {
2121
MidiEvent(uint8_t byte0, uint8_t byte1, uint8_t byte2)
2222
: message(byte0, byte1, byte2)
2323
{}
24-
25-
MidiEvent(MidiEvent const& other)
26-
: message(other.message)
27-
{}
2824
};
2925

3026
// Type-erased event structure that can hold any event type within a certain
@@ -35,7 +31,6 @@ struct BlockEvent {
3531
static constexpr size_t kMaxObjectSize = 64;
3632
alignas(std::max_align_t) char data[kMaxObjectSize];
3733
std::type_index typeIndex;
38-
void(*destructor)(void*) = nullptr;
3934

4035
template <typename T>
4136
BlockEvent(size_t t, T&& d)
@@ -44,15 +39,9 @@ struct BlockEvent {
4439
{
4540
static_assert(sizeof(T) <= kMaxObjectSize, "Type too large for BlockEvent buffer");
4641
static_assert(alignof(T) <= alignof(std::max_align_t), "Type alignment too strict");
42+
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
4743

4844
new(data) T(std::forward<T>(d));
49-
destructor = [](void* ptr) { static_cast<T*>(ptr)->~T(); };
50-
}
51-
52-
~BlockEvent() {
53-
if (destructor) {
54-
destructor(data);
55-
}
5645
}
5746

5847
template <typename T>
@@ -72,6 +61,10 @@ struct BlockEvent {
7261

7362
return nullptr;
7463
}
64+
65+
bool operator>(const BlockEvent& other) const {
66+
return time > other.time;
67+
}
7568
};
7669

7770
// A buffer of realtime BlockEvent instances
@@ -103,6 +96,26 @@ struct BlockEvents {
10396
inline void clear() {
10497
storage.clear();
10598
}
99+
100+
// Sort events by time
101+
inline void sort() {
102+
auto size = storage.size();
103+
104+
for (size_t i = 1; i < size; ++i) {
105+
auto key = storage[i]; // Element to be inserted
106+
int j = i - 1; // Start comparing with the element before
107+
108+
// Move elements of storage[0..i-1] that are greater than key
109+
// one position ahead of their current position
110+
while (j >= 0 && storage[j] > key) {
111+
storage[j + 1] = storage[j]; // Shift element to the right
112+
j--; // Move to the next element on the left
113+
}
114+
115+
// Insert the key at its correct position
116+
storage[j + 1] = key;
117+
}
118+
}
106119
};
107120

108121
} // namespace elem

runtime/elem/BlockEventsBufferPool.h

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include <utility>
99

1010
#include "BlockEvents.h"
11+
#include "third-party/choc/choc/containers/choc_SmallVector.h"
12+
#include "third-party/choc/choc/memory/choc_ObjectPointer.h"
1113
#include "third-party/choc/choc/memory/choc_PoolAllocator.h"
1214
#include "third-party/choc/choc/platform/choc_Assert.h"
1315
#include "Types.h"
@@ -59,7 +61,7 @@ class BlockEventsBufferPool {
5961
//
6062
// If more than one inlet produces block events, this will provision a new
6163
// struct from the pool, merge the events into one, and return that.
62-
BlockEvents& consume(std::vector<InletConnection> const& inlets);
64+
choc::SmallVector<choc::ObjectPointer<BlockEvents>, 16> consume(std::vector<InletConnection> const& inlets);
6365

6466
// Reset internal state
6567
void clear();
@@ -94,17 +96,16 @@ inline BlockEvents& BlockEventsBufferPool::produce(int32_t nodeId, std::vector<O
9496
return buffer;
9597
}
9698

97-
inline BlockEvents& BlockEventsBufferPool::consume(std::vector<InletConnection> const& inlets)
99+
inline choc::SmallVector<choc::ObjectPointer<BlockEvents>, 16> BlockEventsBufferPool::consume(std::vector<InletConnection> const& inlets)
98100
{
99101
auto childIds = detail::getDistinctNodeIds(inlets);
102+
auto out = choc::SmallVector<choc::ObjectPointer<BlockEvents>, 16>();
100103

101104
// We shouldn't be trying to consume from the pool if the node has no children, instead
102105
// the rendering algorithm should pick the host events
103106
CHOC_ASSERT(childIds.size() > 0);
104107

105-
// If there's just one child we can just pluck its buffer from the map
106-
if (childIds.size() == 1) {
107-
auto childId = *childIds.begin();
108+
for (auto const& childId : childIds) {
108109
CHOC_ASSERT(m_assignments.count(childId) > 0);
109110

110111
auto& value = m_assignments.at(childId);
@@ -116,18 +117,10 @@ inline BlockEvents& BlockEventsBufferPool::consume(std::vector<InletConnection>
116117
m_assignments.erase(childId);
117118
}
118119

119-
return buffer;
120+
out.emplace_back(buffer);
120121
}
121122

122-
// Else, we take a new temporary block and sum into it
123-
//
124-
// We can just put the buffer immediately back in the free list
125-
// because this node is the only consumer of this temporary block
126-
auto& buffer = getEventsBuffer();
127-
m_freeList.push(buffer);
128-
129-
// TODO: Consume each child id and sum into `buffer` before returning
130-
return buffer;
123+
return out;
131124
}
132125

133126
inline void BlockEventsBufferPool::clear()

runtime/elem/GraphRenderSequence.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,22 @@ namespace elem
152152
// for input and output events, which would get cleared at the beginning of the op
153153
// below.
154154
auto& outputEvents = m_eventsBufferPool.produce(node->getId(), outlets);
155-
auto& inputEvents = m_eventsBufferPool.consume(inlets);
155+
auto inputEvents = m_eventsBufferPool.consume(inlets);
156156

157-
renderOps.push_back([node, &inputEvents, &outputEvents, outputChannels = std::move(outputChannels), inputChannels = std::move(inputChannels)](BlockContext<FloatType> const& rootCtx) mutable {
157+
renderOps.push_back([node, &outputEvents, inputEvents = std::move(inputEvents), outputChannels = std::move(outputChannels), inputChannels = std::move(inputChannels)](BlockContext<FloatType> const& rootCtx) mutable {
158+
BlockEvents aggregateInputEvents;
158159
outputEvents.clear();
159160

161+
// Aggregate
162+
for (auto& evts : inputEvents) {
163+
for (auto& e : evts->storage) {
164+
aggregateInputEvents.storage.push_back(e);
165+
}
166+
}
167+
168+
// Sort
169+
aggregateInputEvents.sort();
170+
160171
node->process(BlockContext<FloatType> {
161172
const_cast<const FloatType**>(inputChannels.data()),
162173
inputChannels.size(),
@@ -165,7 +176,7 @@ namespace elem
165176
rootCtx.numSamples,
166177
rootCtx.userData,
167178
rootCtx.active,
168-
inputEvents,
179+
aggregateInputEvents,
169180
outputEvents,
170181
});
171182
});

0 commit comments

Comments
 (0)