|
- /*
- ==============================================================================
-
- 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
|