Initial release
This commit is contained in:
+390
@@ -0,0 +1,390 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/hosting/hostdataexchangehandler.cpp
|
||||
// Created by : Steinberg, 06/2023
|
||||
// Description : VST Data Exchange API Host Helper
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// This file is part of a Steinberg SDK. It is subject to the license terms
|
||||
// in the LICENSE file found in the top-level directory of this distribution
|
||||
// and at www.steinberg.net/sdklicenses.
|
||||
// No part of the SDK, including this file, may be copied, modified, propagated,
|
||||
// or distributed except according to the terms contained in the LICENSE file.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "hostdataexchangehandler.h"
|
||||
#include "../utility/alignedalloc.h"
|
||||
#include "../utility/ringbuffer.h"
|
||||
#include "pluginterfaces/base/funknownimpl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct HostDataExchangeHandler::Impl
|
||||
: U::ImplementsNonDestroyable<U::Directly<IDataExchangeHandler>>
|
||||
{
|
||||
struct Block
|
||||
{
|
||||
Block () = default;
|
||||
Block (uint32 blockSize, uint32 alignment, DataExchangeBlockID id)
|
||||
: blockID (id), alignment (alignment)
|
||||
{
|
||||
data = aligned_alloc (blockSize, alignment);
|
||||
}
|
||||
Block (Block&& other) { *this = std::move (other); }
|
||||
~Block () noexcept
|
||||
{
|
||||
if (data)
|
||||
aligned_free (data, alignment);
|
||||
}
|
||||
|
||||
Block& operator= (Block&& other)
|
||||
{
|
||||
data = other.data;
|
||||
other.data = nullptr;
|
||||
blockID = other.blockID;
|
||||
other.blockID = InvalidDataExchangeBlockID;
|
||||
alignment = other.alignment;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void* data {nullptr};
|
||||
DataExchangeBlockID blockID {InvalidDataExchangeBlockID};
|
||||
uint32 alignment {0};
|
||||
};
|
||||
struct Queue
|
||||
{
|
||||
using BlockRingBuffer = OneReaderOneWriter::RingBuffer<Block>;
|
||||
// we do the assumption that the std::vector does not allocate memory when we don't push
|
||||
// more items than we reserve before
|
||||
using BlockVector = std::vector<Block>;
|
||||
|
||||
Queue (IAudioProcessor* owner, IDataExchangeReceiver* receiver,
|
||||
DataExchangeUserContextID userContext, uint32 blockSize, uint32 numBlocks,
|
||||
uint32 alignment)
|
||||
: owner (owner)
|
||||
, receiver (receiver)
|
||||
, userContext (userContext)
|
||||
, blockSize (blockSize)
|
||||
, numBlocks (numBlocks)
|
||||
{
|
||||
receiver->queueOpened (userContext, blockSize, wantBlocksOnBackgroundThread);
|
||||
freeList.resize (numBlocks);
|
||||
sendList.resize (numBlocks);
|
||||
lockList.reserve (numBlocks);
|
||||
freeListOnRTThread.reserve (numBlocks);
|
||||
for (auto idx = 0u; idx < numBlocks; ++idx)
|
||||
freeList.push (Block (blockSize, alignment, idx));
|
||||
}
|
||||
~Queue () noexcept
|
||||
{
|
||||
if (receiver)
|
||||
receiver->queueClosed (userContext);
|
||||
}
|
||||
|
||||
bool lock (DataExchangeBlock& block)
|
||||
{
|
||||
if (freeListOnRTThread.empty () == false)
|
||||
{
|
||||
auto& back = freeListOnRTThread.back ();
|
||||
block.data = back.data;
|
||||
block.size = blockSize;
|
||||
block.blockID = back.blockID;
|
||||
lockList.emplace_back (std::move (back));
|
||||
freeListOnRTThread.pop_back ();
|
||||
return true;
|
||||
}
|
||||
Block b;
|
||||
if (freeList.pop (b))
|
||||
{
|
||||
block.data = b.data;
|
||||
block.size = blockSize;
|
||||
block.blockID = b.blockID;
|
||||
lockList.emplace_back (std::move (b));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool free (DataExchangeBlockID blockID)
|
||||
{
|
||||
if (blockID >= numBlocks)
|
||||
return false;
|
||||
auto it = std::find_if (lockList.begin (), lockList.end (),
|
||||
[&] (const auto& el) { return el.blockID == blockID; });
|
||||
if (it == lockList.end ())
|
||||
return false;
|
||||
|
||||
Block b = std::move (*it);
|
||||
freeListOnRTThread.emplace_back (std::move (b));
|
||||
lockList.erase (it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool readyToSend (DataExchangeBlockID blockID)
|
||||
{
|
||||
if (blockID >= numBlocks)
|
||||
return false;
|
||||
auto it = std::find_if (lockList.begin (), lockList.end (),
|
||||
[&] (const auto& el) { return el.blockID == blockID; });
|
||||
if (it == lockList.end ())
|
||||
return false;
|
||||
|
||||
Block b = std::move (*it);
|
||||
sendList.push (std::move (b));
|
||||
lockList.erase (it);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 sendBlocks (DataExchangeQueueID queueID)
|
||||
{
|
||||
BlockVector blocks;
|
||||
Block b;
|
||||
while (sendList.pop (b))
|
||||
{
|
||||
blocks.emplace_back (std::move (b));
|
||||
}
|
||||
if (blocks.empty ())
|
||||
return 0;
|
||||
|
||||
std::vector<DataExchangeBlock> debs;
|
||||
std::for_each (blocks.begin (), blocks.end (), [&] (const auto& el) {
|
||||
DataExchangeBlock block;
|
||||
block.data = el.data;
|
||||
block.size = blockSize;
|
||||
block.blockID = el.blockID;
|
||||
debs.push_back (block);
|
||||
});
|
||||
|
||||
receiver->onDataExchangeBlocksReceived (userContext, static_cast<uint32> (debs.size ()),
|
||||
debs.data (), wantBlocksOnBackgroundThread);
|
||||
|
||||
std::for_each (blocks.begin (), blocks.end (),
|
||||
[&] (auto&& el) { freeList.push (std::move (el)); });
|
||||
return static_cast<uint32> (debs.size ());
|
||||
}
|
||||
|
||||
IAudioProcessor* owner;
|
||||
IPtr<IDataExchangeReceiver> receiver;
|
||||
DataExchangeUserContextID userContext {};
|
||||
TBool wantBlocksOnBackgroundThread {false};
|
||||
|
||||
BlockRingBuffer freeList;
|
||||
BlockVector freeListOnRTThread;
|
||||
BlockVector lockList;
|
||||
BlockRingBuffer sendList;
|
||||
|
||||
uint32 blockSize {0};
|
||||
uint32 numBlocks {0};
|
||||
};
|
||||
using QueuePtr = std::unique_ptr<Queue>;
|
||||
using QueueList = std::vector<QueuePtr>;
|
||||
|
||||
Impl (IDataExchangeHandlerHost& host, uint32 maxQueues) : host (host)
|
||||
{
|
||||
queues.resize (maxQueues);
|
||||
}
|
||||
|
||||
void setQueue (DataExchangeQueueID queueID, QueuePtr&& queue)
|
||||
{
|
||||
queuesLock.lock ();
|
||||
queues[queueID] = std::move (queue);
|
||||
if (queues[queueID]->wantBlocksOnBackgroundThread)
|
||||
++numOpenBackgroundQueues;
|
||||
else
|
||||
++numOpenMainThreadQueues;
|
||||
queuesLock.unlock ();
|
||||
host.onQueueOpened (queues[queueID]->owner, queueID,
|
||||
queues[queueID]->wantBlocksOnBackgroundThread);
|
||||
host.numberOfQueuesChanged (numOpenMainThreadQueues, numOpenBackgroundQueues);
|
||||
}
|
||||
|
||||
tresult PLUGIN_API openQueue (IAudioProcessor* owner, uint32 blockSize, uint32 numBlocks,
|
||||
uint32 alignment, DataExchangeUserContextID userContext,
|
||||
DataExchangeQueueID* outID) override
|
||||
{
|
||||
if (!host.isMainThread ())
|
||||
return kResultFalse;
|
||||
if (outID == nullptr)
|
||||
return kInvalidArgument;
|
||||
if (!host.isProcessorInactive (owner))
|
||||
return kResultFalse;
|
||||
auto receiver = host.findDataExchangeReceiver (owner);
|
||||
if (!receiver)
|
||||
return kInvalidArgument;
|
||||
if (!host.allowAllocateSize (blockSize, numBlocks, alignment))
|
||||
return kOutOfMemory;
|
||||
|
||||
for (auto queueID = 0; queueID < queues.size (); ++queueID)
|
||||
{
|
||||
if (queues[queueID] == nullptr)
|
||||
{
|
||||
auto newQueue = std::make_unique<Queue> (owner, receiver, userContext, blockSize,
|
||||
numBlocks, alignment);
|
||||
setQueue (queueID, std::move (newQueue));
|
||||
*outID = queueID;
|
||||
return kResultTrue;
|
||||
}
|
||||
}
|
||||
auto queueSize = queues.size ();
|
||||
if (host.allowQueueListResize (static_cast<uint32> (queueSize + 1)))
|
||||
{
|
||||
queues.resize (queueSize + 1);
|
||||
assert (queues.size () == queueSize + 1);
|
||||
DataExchangeQueueID queueID = static_cast<DataExchangeQueueID> (queueSize);
|
||||
auto newQueue = std::make_unique<Queue> (owner, receiver, userContext, blockSize,
|
||||
numBlocks, alignment);
|
||||
setQueue (queueID, std::move (newQueue));
|
||||
*outID = queueID;
|
||||
return kResultTrue;
|
||||
}
|
||||
return kOutOfMemory;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API closeQueue (DataExchangeQueueID queueID) override
|
||||
{
|
||||
if (!host.isMainThread ())
|
||||
return kResultFalse;
|
||||
if (queues[queueID])
|
||||
{
|
||||
if (!host.isProcessorInactive (queues[queueID]->owner))
|
||||
return kResultFalse;
|
||||
|
||||
QueuePtr q;
|
||||
queuesLock.lock ();
|
||||
std::swap (q, queues[queueID]);
|
||||
if (q->wantBlocksOnBackgroundThread)
|
||||
--numOpenBackgroundQueues;
|
||||
else
|
||||
--numOpenMainThreadQueues;
|
||||
queuesLock.unlock ();
|
||||
host.onQueueClosed (q->owner, queueID, q->wantBlocksOnBackgroundThread);
|
||||
host.numberOfQueuesChanged (numOpenMainThreadQueues, numOpenBackgroundQueues);
|
||||
q.reset ();
|
||||
return kResultTrue;
|
||||
}
|
||||
return kResultFalse;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API lockBlock (DataExchangeQueueID queueId, DataExchangeBlock* block) override
|
||||
{
|
||||
if (!block || queueId >= queues.size () || queues[queueId] == nullptr)
|
||||
return kInvalidArgument;
|
||||
if (queues[queueId]->lock (*block))
|
||||
return kResultTrue;
|
||||
return kOutOfMemory;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API freeBlock (DataExchangeQueueID queueId, DataExchangeBlockID blockID,
|
||||
TBool sendToController) override
|
||||
{
|
||||
if (queueId >= queues.size () || queues[queueId] == nullptr)
|
||||
return kInvalidArgument;
|
||||
if (sendToController)
|
||||
{
|
||||
if (queues[queueId]->readyToSend (blockID))
|
||||
{
|
||||
++numReadyToSendBlocks;
|
||||
host.newBlockReadyToBeSend (queueId);
|
||||
return kResultTrue;
|
||||
}
|
||||
return kResultFalse;
|
||||
}
|
||||
return queues[queueId]->free (blockID) ? kResultTrue : kResultFalse;
|
||||
}
|
||||
|
||||
bool sendBlocks (bool isMainThread, size_t queueID, uint32& numSendBlocks)
|
||||
{
|
||||
LockGuard guard (queuesLock);
|
||||
if (auto& queue = queues[queueID])
|
||||
{
|
||||
if (queue->wantBlocksOnBackgroundThread != static_cast<TBool> (isMainThread))
|
||||
{
|
||||
numSendBlocks = queue->sendBlocks (static_cast<DataExchangeQueueID> (queueID));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 sendBlocks (bool isMainThread, DataExchangeQueueID queueFilter)
|
||||
{
|
||||
if (queueFilter != InvalidDataExchangeQueueID)
|
||||
{
|
||||
if (queueFilter < queues.size ())
|
||||
{
|
||||
uint32 numSendBlocks;
|
||||
if (sendBlocks (isMainThread, queueFilter, numSendBlocks))
|
||||
return numSendBlocks;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
uint32 totalSendBlocks = 0;
|
||||
uint32 openQueues = numOpenBackgroundQueues + numOpenMainThreadQueues;
|
||||
for (auto queueID = 0u; queueID < queues.size (); ++queueID)
|
||||
{
|
||||
uint32 numSendBlocks;
|
||||
if (sendBlocks (isMainThread, queueID, numSendBlocks))
|
||||
{
|
||||
numReadyToSendBlocks -= numSendBlocks;
|
||||
totalSendBlocks += numSendBlocks;
|
||||
if ((--openQueues) == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return totalSendBlocks;
|
||||
}
|
||||
|
||||
IDataExchangeHandlerHost& host;
|
||||
QueueList queues;
|
||||
std::atomic<uint32> numReadyToSendBlocks {0};
|
||||
std::atomic<uint32> numOpenMainThreadQueues {0};
|
||||
std::atomic<uint32> numOpenBackgroundQueues {0};
|
||||
using Mutex = std::recursive_mutex;
|
||||
using LockGuard = std::lock_guard<Mutex>;
|
||||
Mutex queuesLock;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
HostDataExchangeHandler::HostDataExchangeHandler (IDataExchangeHandlerHost& host, uint32 maxQueues)
|
||||
{
|
||||
impl = std::make_unique<Impl> (host, maxQueues);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
HostDataExchangeHandler::~HostDataExchangeHandler () noexcept = default;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
IDataExchangeHandler* HostDataExchangeHandler::getInterface () const
|
||||
{
|
||||
return impl.get ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
uint32 HostDataExchangeHandler::sendMainThreadBlocks ()
|
||||
{
|
||||
return impl->sendBlocks (true, InvalidDataExchangeQueueID);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
uint32 HostDataExchangeHandler::sendBackgroundBlocks (DataExchangeQueueID queueId)
|
||||
{
|
||||
return impl->sendBlocks (false, queueId);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
Reference in New Issue
Block a user