mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
364 lines
14 KiB
C++
364 lines
14 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE framework.
|
|
Copyright (c) Raw Material Software Limited
|
|
|
|
JUCE is an open source framework subject to commercial or open source
|
|
licensing.
|
|
|
|
By downloading, installing, or using the JUCE framework, or combining the
|
|
JUCE framework with any other source code, object code, content or any other
|
|
copyrightable work, you agree to the terms of the JUCE End User Licence
|
|
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
|
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
|
do not agree to the terms of these agreements, we will not license the JUCE
|
|
framework to you, and you must discontinue the installation or download
|
|
process and cease use of the JUCE framework.
|
|
|
|
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
|
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
|
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
|
|
|
Or:
|
|
|
|
You may also use this code under the terms of the AGPLv3:
|
|
https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
|
|
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
|
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
|
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace juce::midi_ci
|
|
{
|
|
|
|
class PropertyExchangeCache
|
|
{
|
|
public:
|
|
PropertyExchangeCache() = default;
|
|
|
|
struct OwningResult
|
|
{
|
|
explicit OwningResult (PropertyExchangeResult::Error e)
|
|
: result (e) {}
|
|
|
|
OwningResult (var header, std::vector<std::byte> body)
|
|
: backingStorage (std::move (body)),
|
|
result (header, backingStorage) {}
|
|
|
|
OwningResult (OwningResult&&) noexcept = default;
|
|
OwningResult& operator= (OwningResult&&) noexcept = default;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (OwningResult)
|
|
|
|
std::vector<std::byte> backingStorage;
|
|
PropertyExchangeResult result;
|
|
};
|
|
|
|
std::optional<OwningResult> addChunk (Message::DynamicSizePropertyExchange chunk)
|
|
{
|
|
jassert (chunk.thisChunkNum == lastChunk + 1 || chunk.thisChunkNum == 0);
|
|
lastChunk = chunk.thisChunkNum;
|
|
headerStorage.reserve (headerStorage.size() + chunk.header.size());
|
|
std::transform (chunk.header.begin(),
|
|
chunk.header.end(),
|
|
std::back_inserter (headerStorage),
|
|
[] (std::byte b) { return char (b); });
|
|
bodyStorage.insert (bodyStorage.end(), chunk.data.begin(), chunk.data.end());
|
|
|
|
if (chunk.thisChunkNum != 0 && chunk.thisChunkNum != chunk.totalNumChunks)
|
|
return {};
|
|
|
|
const auto headerJson = JSON::parse (String (headerStorage.data(), headerStorage.size()));
|
|
|
|
terminate();
|
|
const auto encodingString = headerJson.getProperty ("mutualEncoding", "ASCII").toString();
|
|
|
|
if (chunk.thisChunkNum != chunk.totalNumChunks)
|
|
return std::optional<OwningResult> { std::in_place, PropertyExchangeResult::Error::partial };
|
|
|
|
const int status = headerJson.getProperty ("status", 200);
|
|
|
|
if (status == 343)
|
|
return std::optional<OwningResult> { std::in_place, PropertyExchangeResult::Error::tooManyTransactions };
|
|
|
|
return std::optional<OwningResult> { std::in_place,
|
|
headerJson,
|
|
Encodings::decode (bodyStorage, EncodingUtils::toEncoding (encodingString.toRawUTF8()).value_or (Encoding::ascii)) };
|
|
}
|
|
|
|
std::optional<OwningResult> notify (Span<const std::byte> header)
|
|
{
|
|
const auto headerJson = JSON::parse (String (reinterpret_cast<const char*> (header.data()), header.size()));
|
|
|
|
if (! headerJson.isObject())
|
|
return {};
|
|
|
|
const auto status = headerJson.getProperty ("status", {});
|
|
|
|
if (! status.isInt() || (int) status == 100)
|
|
return {};
|
|
|
|
terminate();
|
|
return std::optional<OwningResult> { std::in_place, PropertyExchangeResult::Error::notify };
|
|
}
|
|
|
|
bool terminate()
|
|
{
|
|
return std::exchange (ongoing, false);
|
|
}
|
|
|
|
private:
|
|
std::vector<char> headerStorage;
|
|
std::vector<std::byte> bodyStorage;
|
|
uint16_t lastChunk = 0;
|
|
bool ongoing = true;
|
|
};
|
|
|
|
//==============================================================================
|
|
class PropertyExchangeCacheArray
|
|
{
|
|
public:
|
|
PropertyExchangeCacheArray() = default;
|
|
|
|
Token64 primeCacheForRequestId (uint8_t id, std::function<void (const PropertyExchangeResult&)> onDone)
|
|
{
|
|
jassert (id < caches.size());
|
|
|
|
++lastKey;
|
|
|
|
auto& entry = caches[id];
|
|
|
|
if (entry.has_value())
|
|
{
|
|
// Trying to start a new message with the same id as another in-progress message
|
|
jassertfalse;
|
|
ids.erase (entry->key);
|
|
}
|
|
|
|
const auto& item = entry.emplace (id, std::move (onDone), Token64 { lastKey });
|
|
ids.emplace (item.key, id);
|
|
return item.key;
|
|
}
|
|
|
|
bool terminate (Token64 key)
|
|
{
|
|
const auto iter = ids.find (key);
|
|
|
|
// If the key isn't found, then the transaction must have completed already
|
|
if (iter == ids.end())
|
|
return false;
|
|
|
|
// We're about to terminate this transaction, so we don't need to retain this record
|
|
auto index = iter->second;
|
|
ids.erase (iter);
|
|
|
|
auto& entry = caches[index];
|
|
|
|
// If the entry is null, something's gone wrong. The ids map should only contain elements for
|
|
// non-null cache entries.
|
|
if (! entry.has_value())
|
|
{
|
|
jassertfalse;
|
|
return false;
|
|
}
|
|
|
|
const auto result = entry->cache.terminate();
|
|
entry.reset();
|
|
return result;
|
|
}
|
|
|
|
void addChunk (RequestID b, const Message::DynamicSizePropertyExchange& chunk)
|
|
{
|
|
updateCache (b, [&] (PropertyExchangeCache& c) { return c.addChunk (chunk); });
|
|
}
|
|
|
|
void notify (RequestID b, Span<const std::byte> header)
|
|
{
|
|
updateCache (b, [&] (PropertyExchangeCache& c) { return c.notify (header); });
|
|
}
|
|
|
|
std::optional<Token64> getKeyForId (RequestID id) const
|
|
{
|
|
if (auto& c = caches[id.asInt()])
|
|
return c->key;
|
|
|
|
return {};
|
|
}
|
|
|
|
bool hasTransaction (RequestID id) const
|
|
{
|
|
return getKeyForId (id).has_value();
|
|
}
|
|
|
|
std::optional<RequestID> getIdForKey (Token64 key) const
|
|
{
|
|
const auto iter = ids.find (key);
|
|
return iter != ids.end() ? RequestID::create (iter->second) : std::nullopt;
|
|
}
|
|
|
|
auto countOngoingTransactions() const
|
|
{
|
|
jassert (ids.size() == (size_t) std::count_if (caches.begin(), caches.end(), [] (auto& c) { return c.has_value(); }));
|
|
|
|
return (int) ids.size();
|
|
}
|
|
|
|
auto getOngoingTransactions() const
|
|
{
|
|
jassert (ids.size() == (size_t) std::count_if (caches.begin(), caches.end(), [] (auto& c) { return c.has_value(); }));
|
|
|
|
std::vector<Token64> result (ids.size());
|
|
std::transform (ids.begin(), ids.end(), result.begin(), [] (const auto& p) { return Token64 { p.first }; });
|
|
return result;
|
|
}
|
|
|
|
std::optional<RequestID> findUnusedId (uint8_t maxSimultaneousTransactions) const
|
|
{
|
|
if (countOngoingTransactions() >= maxSimultaneousTransactions)
|
|
return {};
|
|
|
|
return RequestID::create ((uint8_t) std::distance (caches.begin(), std::find (caches.begin(), caches.end(), std::nullopt)));
|
|
}
|
|
|
|
// Instances must stay at the same location to ensure that references captured in the
|
|
// ErasedScopeGuard returned from primeCacheForRequestId do not dangle.
|
|
JUCE_DECLARE_NON_COPYABLE (PropertyExchangeCacheArray)
|
|
JUCE_DECLARE_NON_MOVEABLE (PropertyExchangeCacheArray)
|
|
|
|
private:
|
|
static constexpr auto numCaches = 128;
|
|
|
|
class Transaction
|
|
{
|
|
public:
|
|
Transaction (uint8_t i, std::function<void (const PropertyExchangeResult&)> onSuccess, Token64 k)
|
|
: onFinish (std::move (onSuccess)), key (k), id (i) {}
|
|
|
|
PropertyExchangeCache cache;
|
|
std::function<void (const PropertyExchangeResult&)> onFinish;
|
|
Token64 key{};
|
|
uint8_t id = 0;
|
|
};
|
|
|
|
template <typename WithCache>
|
|
void updateCache (RequestID b, WithCache&& withCache)
|
|
{
|
|
if (auto& entry = caches[b.asInt()])
|
|
{
|
|
if (const auto result = withCache (entry->cache))
|
|
{
|
|
const auto tmp = std::move (*entry);
|
|
ids.erase (tmp.key);
|
|
entry.reset();
|
|
NullCheckedInvocation::invoke (tmp.onFinish, result->result);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::array<std::optional<Transaction>, numCaches> caches;
|
|
std::map<Token64, uint8_t> ids;
|
|
uint64_t lastKey = 0;
|
|
};
|
|
|
|
//==============================================================================
|
|
class InitiatorPropertyExchangeCache::Impl
|
|
{
|
|
public:
|
|
std::optional<Token64> primeCache (uint8_t maxSimultaneousRequests,
|
|
std::function<void (const PropertyExchangeResult&)> onDone)
|
|
{
|
|
const auto id = array.findUnusedId (maxSimultaneousRequests);
|
|
|
|
return id.has_value() ? std::optional<Token64> (array.primeCacheForRequestId (id->asInt(), std::move (onDone)))
|
|
: std::nullopt;
|
|
}
|
|
|
|
bool terminate (Token64 token)
|
|
{
|
|
return array.terminate (token);
|
|
}
|
|
|
|
std::optional<Token64> getTokenForRequestId (RequestID id) const
|
|
{
|
|
return array.getKeyForId (id);
|
|
}
|
|
|
|
std::optional<RequestID> getRequestIdForToken (Token64 token) const
|
|
{
|
|
return array.getIdForKey (token);
|
|
}
|
|
|
|
void addChunk (RequestID b, const Message::DynamicSizePropertyExchange& chunk) { array.addChunk (b, chunk); }
|
|
void notify (RequestID b, Span<const std::byte> header) { array.notify (b, header); }
|
|
auto getOngoingTransactions() const { return array.getOngoingTransactions(); }
|
|
|
|
private:
|
|
PropertyExchangeCacheArray array;
|
|
};
|
|
|
|
//==============================================================================
|
|
InitiatorPropertyExchangeCache::InitiatorPropertyExchangeCache() : pimpl (std::make_unique<Impl>()) {}
|
|
InitiatorPropertyExchangeCache::InitiatorPropertyExchangeCache (InitiatorPropertyExchangeCache&&) noexcept = default;
|
|
InitiatorPropertyExchangeCache& InitiatorPropertyExchangeCache::operator= (InitiatorPropertyExchangeCache&&) noexcept = default;
|
|
InitiatorPropertyExchangeCache::~InitiatorPropertyExchangeCache() = default;
|
|
|
|
std::optional<Token64> InitiatorPropertyExchangeCache::primeCache (uint8_t maxSimultaneousTransactions,
|
|
std::function<void (const PropertyExchangeResult&)> onDone)
|
|
{
|
|
return pimpl->primeCache (maxSimultaneousTransactions, std::move (onDone));
|
|
}
|
|
|
|
bool InitiatorPropertyExchangeCache::terminate (Token64 token) { return pimpl->terminate (token); }
|
|
std::optional<Token64> InitiatorPropertyExchangeCache::getTokenForRequestId (RequestID id) const { return pimpl->getTokenForRequestId (id); }
|
|
std::optional<RequestID> InitiatorPropertyExchangeCache::getRequestIdForToken (Token64 token) const { return pimpl->getRequestIdForToken (token); }
|
|
void InitiatorPropertyExchangeCache::addChunk (RequestID b, const Message::DynamicSizePropertyExchange& chunk) { pimpl->addChunk (b, chunk); }
|
|
void InitiatorPropertyExchangeCache::notify (RequestID b, Span<const std::byte> header) { pimpl->notify (b, header); }
|
|
std::vector<Token64> InitiatorPropertyExchangeCache::getOngoingTransactions() const { return pimpl->getOngoingTransactions(); }
|
|
|
|
//==============================================================================
|
|
class ResponderPropertyExchangeCache::Impl
|
|
{
|
|
public:
|
|
void primeCache (uint8_t maxSimultaneousTransactions,
|
|
std::function<void (const PropertyExchangeResult&)> onDone,
|
|
RequestID id)
|
|
{
|
|
if (array.hasTransaction (id))
|
|
return;
|
|
|
|
if (array.countOngoingTransactions() >= maxSimultaneousTransactions)
|
|
NullCheckedInvocation::invoke (onDone, PropertyExchangeResult { PropertyExchangeResult::Error::tooManyTransactions });
|
|
else
|
|
array.primeCacheForRequestId (id.asInt(), std::move (onDone));
|
|
}
|
|
|
|
void addChunk (RequestID b, const Message::DynamicSizePropertyExchange& chunk) { array.addChunk (b, chunk); }
|
|
void notify (RequestID b, Span<const std::byte> header) { array.notify (b, header); }
|
|
int countOngoingTransactions() const { return array.countOngoingTransactions(); }
|
|
|
|
private:
|
|
PropertyExchangeCacheArray array;
|
|
};
|
|
|
|
//==============================================================================
|
|
ResponderPropertyExchangeCache::ResponderPropertyExchangeCache() : pimpl (std::make_unique<Impl>()) {}
|
|
ResponderPropertyExchangeCache::ResponderPropertyExchangeCache (ResponderPropertyExchangeCache&&) noexcept = default;
|
|
ResponderPropertyExchangeCache& ResponderPropertyExchangeCache::operator= (ResponderPropertyExchangeCache&&) noexcept = default;
|
|
ResponderPropertyExchangeCache::~ResponderPropertyExchangeCache() = default;
|
|
|
|
void ResponderPropertyExchangeCache::primeCache (uint8_t maxSimultaneousTransactions,
|
|
std::function<void (const PropertyExchangeResult&)> onDone,
|
|
RequestID id)
|
|
{
|
|
return pimpl->primeCache (maxSimultaneousTransactions, std::move (onDone), id);
|
|
}
|
|
|
|
void ResponderPropertyExchangeCache::addChunk (RequestID b, const Message::DynamicSizePropertyExchange& chunk) { pimpl->addChunk (b, chunk); }
|
|
void ResponderPropertyExchangeCache::notify (RequestID b, Span<const std::byte> header) { pimpl->notify (b, header); }
|
|
int ResponderPropertyExchangeCache::countOngoingTransactions() const { return pimpl->countOngoingTransactions(); }
|
|
|
|
} // namespace juce::midi_ci
|