Skip to content

Commit c6cd828

Browse files
committed
AsyncStreams: Wrap Strings / Buffers in AsyncBufferView though type erasure
Strings/Buffers are copied (or moved) to the inline storage provided by AsyncBufferView and destroyed when no longer needed.
1 parent 107c23b commit c6cd828

File tree

3 files changed

+79
-7
lines changed

3 files changed

+79
-7
lines changed

Libraries/AsyncStreams/AsyncStreams.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ void AsyncBuffersPool::unrefBuffer(AsyncBufferView::ID bufferID)
2828
{
2929
case AsyncBufferView::Type::Writable: buffer->writableData = buffer->originalWritableData; break;
3030
case AsyncBufferView::Type::ReadOnly: *buffer = {}; break;
31+
case AsyncBufferView::Type::Growable: *buffer = {}; break;
3132
case AsyncBufferView::Type::Empty: Assert::unreachable(); break;
3233
}
3334
}
@@ -41,6 +42,14 @@ Result AsyncBuffersPool::getReadableData(AsyncBufferView::ID bufferID, Span<cons
4142
{
4243
case AsyncBufferView::Type::Writable: data = buffer->writableData; break;
4344
case AsyncBufferView::Type::ReadOnly: data = buffer->readonlyData; break;
45+
case AsyncBufferView::Type::Growable: {
46+
AsyncBufferView::GrowableStorage storage;
47+
48+
auto da = buffer->getGrowableBuffer(storage, true)->getDirectAccess();
49+
data = {static_cast<char*>(da.data), da.sizeInBytes};
50+
(void)buffer->getGrowableBuffer(storage, false); // destruct
51+
break;
52+
}
4453
case AsyncBufferView::Type::Empty: Assert::unreachable(); break;
4554
}
4655
return Result(true);
@@ -77,6 +86,16 @@ Result AsyncBuffersPool::requestNewBuffer(size_t minimumSizeInBytes, AsyncBuffer
7786
{
7887
case AsyncBufferView::Type::Writable: buffer.originalWritableData = buffer.writableData; break;
7988
case AsyncBufferView::Type::ReadOnly: buffer.originalReadonlyData = buffer.readonlyData; break;
89+
case AsyncBufferView::Type::Growable: {
90+
AsyncBufferView::GrowableStorage storage;
91+
92+
auto da = buffer.getGrowableBuffer(storage, true)->getDirectAccess();
93+
94+
buffer.writableData = {static_cast<char*>(da.data), da.sizeInBytes};
95+
buffer.originalWritableData = {static_cast<char*>(da.data), da.sizeInBytes};
96+
(void)buffer.getGrowableBuffer(storage, false); // destruct
97+
break;
98+
}
8099
case AsyncBufferView::Type::Empty: SC_ASSERT_RELEASE(false); break;
81100
}
82101
bufferID = AsyncBufferView::ID(static_cast<AsyncBufferView::ID::NumericType>(&buffer - buffers.begin()));
@@ -105,6 +124,22 @@ void AsyncBuffersPool::setNewBufferSize(AsyncBufferView::ID bufferID, size_t new
105124
buffer->readonlyData = {buffer->readonlyData.data(), newSizeInBytes};
106125
}
107126
break;
127+
case AsyncBufferView::Type::Growable: {
128+
AsyncBufferView::GrowableStorage storage;
129+
130+
IGrowableBuffer* growable = buffer->getGrowableBuffer(storage, true);
131+
if (growable->resizeWithoutInitializing(newSizeInBytes))
132+
{
133+
auto da = growable->getDirectAccess();
134+
135+
buffer->writableData = {static_cast<char*>(da.data), da.sizeInBytes};
136+
buffer->originalWritableData = {static_cast<char*>(da.data), da.sizeInBytes};
137+
}
138+
(void)buffer->getGrowableBuffer(storage, false); // destruct
139+
break;
140+
}
141+
142+
break;
108143
case AsyncBufferView::Type::Empty: SC_ASSERT_RELEASE(false); break;
109144
}
110145
}

Libraries/AsyncStreams/AsyncStreams.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// SPDX-License-Identifier: MIT
33
#pragma once
44

5+
#include "../Foundation/AlignedStorage.h"
56
#include "../Foundation/Function.h"
7+
#include "../Foundation/Internal/IGrowableBuffer.h"
68
#include "../Foundation/Result.h"
79
#include "../Foundation/Span.h"
810
#include "Internal/CircularQueue.h"
@@ -61,12 +63,39 @@ struct AsyncBufferView
6163
Empty,
6264
Writable,
6365
ReadOnly,
66+
Growable,
6467
};
6568

6669
AsyncBufferView() { type = Type::Empty; }
6770
AsyncBufferView(Span<char> data) : writableData(data) { type = Type::Writable; }
6871
AsyncBufferView(Span<const char> data) : readonlyData(data) { type = Type::ReadOnly; }
6972

73+
/// @brief Saves a copy (or a moved instance) of a String / Buffer (or anything that works with GrowableBuffer<T>)
74+
/// inside an AsyncBufferView in order to access its data later, as long as its size fits inside the inline storage.
75+
/// Destroying the AsyncBufferView will also destroy the copied / moved instance.
76+
template <typename T>
77+
AsyncBufferView(T&& t) // universal reference, it can capture both lvalue and rvalue
78+
{
79+
type = Type::Growable;
80+
// Here we're type-erasing T in our own inline storage provided by a slightly oversized Function<>
81+
// that it will be able to construct (and destruct) the right GrowableBuffer<T> from just a piece of storage
82+
// and return a pointer to the corresponding IGrowableBuffer* interface
83+
getGrowableBuffer = [t = forward<T>(t)](GrowableStorage& storage, bool construct) mutable -> IGrowableBuffer*
84+
{
85+
using Type = typename TypeTraits::RemoveReference<T>::type;
86+
if (construct)
87+
{
88+
placementNew(storage.reinterpret_as<GrowableBuffer<Type>>(), t);
89+
return &storage.reinterpret_as<GrowableBuffer<Type>>();
90+
}
91+
else
92+
{
93+
dtor(storage.reinterpret_as<GrowableBuffer<Type>>());
94+
return nullptr;
95+
}
96+
};
97+
}
98+
7099
template <int N>
71100
AsyncBufferView(const char (&literal)[N])
72101
{
@@ -77,6 +106,12 @@ struct AsyncBufferView
77106
Type getType() const { return type; }
78107

79108
private:
109+
static constexpr int TypeErasedCaptureSize = sizeof(void*) * 3; // This is enough to hold String / Buffer by copy
110+
static constexpr int TypeErasedGrowableSize = sizeof(void*) * 6;
111+
112+
using GrowableStorage = AlignedStorage<TypeErasedGrowableSize>;
113+
Function<IGrowableBuffer*(GrowableStorage&, bool), TypeErasedCaptureSize> getGrowableBuffer;
114+
80115
union
81116
{
82117
Span<char> writableData;

Tests/Libraries/AsyncStreams/AsyncStreamsTest.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -323,19 +323,21 @@ void SC::AsyncStreamsTest::writableStream()
323323
};
324324
int numDrain = 0;
325325
(void)writable.eventDrain.addListener([&numDrain] { numDrain++; });
326-
SC_TEST_EXPECT(writable.write("1")); // Executes asyncWrites and queue slot is freed immediately
326+
327+
// When passing String(...) the writable takes ownership of the String destroying it after the write
328+
SC_TEST_EXPECT(writable.write(String("1"))); // Executes asyncWrites and queue slot is freed immediately
327329
SC_TEST_EXPECT(context.numAsyncWrites == 1);
328-
SC_TEST_EXPECT(writable.write("2")); // queued, uses first write slot
329-
SC_TEST_EXPECT(writable.write("3")); // queued, uses second write slot
330-
SC_TEST_EXPECT(not writable.write("4")); // no more write queue slots
330+
SC_TEST_EXPECT(writable.write("2")); // queued, uses first write slot
331+
SC_TEST_EXPECT(writable.write(String("3"))); // queued, uses second write slot
332+
SC_TEST_EXPECT(not writable.write("4")); // no more write queue slots
331333
SC_TEST_EXPECT(context.numAsyncWrites == 1);
332334
writable.finishedWriting(context.bufferID, {}, Result(true)); // writes 2
333335
SC_TEST_EXPECT(context.concatenated == "12");
334336
SC_TEST_EXPECT(numDrain == 0);
335337
SC_TEST_EXPECT(context.numAsyncWrites == 2);
336338
SC_TEST_EXPECT(writable.write("4"));
337339
SC_TEST_EXPECT(context.numAsyncWrites == 2);
338-
SC_TEST_EXPECT(not writable.write("5"));
340+
SC_TEST_EXPECT(not writable.write(String("5")));
339341
writable.finishedWriting(context.bufferID, {}, Result(true)); // writes 3
340342
SC_TEST_EXPECT(context.concatenated == "123");
341343
SC_TEST_EXPECT(numDrain == 0);
@@ -348,10 +350,10 @@ void SC::AsyncStreamsTest::writableStream()
348350
SC_TEST_EXPECT(context.numAsyncWrites == 4);
349351
SC_TEST_EXPECT(writable.write("5"));
350352
SC_TEST_EXPECT(context.numAsyncWrites == 5);
351-
SC_TEST_EXPECT(writable.write("6"));
353+
SC_TEST_EXPECT(writable.write(String("6")));
352354
SC_TEST_EXPECT(context.numAsyncWrites == 5);
353355
SC_TEST_EXPECT(writable.write("7"));
354-
SC_TEST_EXPECT(not writable.write("8"));
356+
SC_TEST_EXPECT(not writable.write(String("8")));
355357
writable.finishedWriting(context.bufferID, {}, Result(true));
356358
SC_TEST_EXPECT(context.concatenated == "123456");
357359
SC_TEST_EXPECT(context.numAsyncWrites == 6);

0 commit comments

Comments
 (0)