mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
The old API only allowed cancelling property "get" inquiries and subscription updates. However, there are use-cases for cancelling other requests too. e.g. switching between views in a JUCE app might mean that it's no longer necessary to subscribe to a particular property. Cancelling subscriptions ends up being quite involved. Different handling is needed depending on whether the subscription is cancelled before or after the responder replies to the initial request. In addition, the responder may ask the initiator to retry a subscription begin request.
355 lines
14 KiB
C++
355 lines
14 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2022 - Raw Material Software Limited
|
|
|
|
JUCE is an open source library subject to commercial or open-source
|
|
licensing.
|
|
|
|
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
|
Agreement and JUCE Privacy Policy.
|
|
|
|
End User License Agreement: www.juce.com/juce-7-licence
|
|
Privacy Policy: www.juce.com/juce-privacy-policy
|
|
|
|
Or: You may also use this code under the terms of the GPL v3 (see
|
|
www.gnu.org/licenses).
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 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
|