/* ============================================================================== 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 body) : backingStorage (std::move (body)), result (header, backingStorage) {} OwningResult (OwningResult&&) noexcept = default; OwningResult& operator= (OwningResult&&) noexcept = default; JUCE_DECLARE_NON_COPYABLE (OwningResult) std::vector backingStorage; PropertyExchangeResult result; }; std::optional 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 { std::in_place, PropertyExchangeResult::Error::partial }; const int status = headerJson.getProperty ("status", 200); if (status == 343) return std::optional { std::in_place, PropertyExchangeResult::Error::tooManyTransactions }; return std::optional { std::in_place, headerJson, Encodings::decode (bodyStorage, EncodingUtils::toEncoding (encodingString.toRawUTF8()).value_or (Encoding::ascii)) }; } std::optional notify (Span header) { const auto headerJson = JSON::parse (String (reinterpret_cast (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 { std::in_place, PropertyExchangeResult::Error::notify }; } bool terminate() { return std::exchange (ongoing, false); } private: std::vector headerStorage; std::vector bodyStorage; uint16_t lastChunk = 0; bool ongoing = true; }; //============================================================================== class PropertyExchangeCacheArray { public: PropertyExchangeCacheArray() = default; Token64 primeCacheForRequestId (uint8_t id, std::function 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 header) { updateCache (b, [&] (PropertyExchangeCache& c) { return c.notify (header); }); } std::optional 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 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 result (ids.size()); std::transform (ids.begin(), ids.end(), result.begin(), [] (const auto& p) { return Token64 { p.first }; }); return result; } std::optional 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 onSuccess, Token64 k) : onFinish (std::move (onSuccess)), key (k), id (i) {} PropertyExchangeCache cache; std::function onFinish; Token64 key{}; uint8_t id = 0; }; template 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, numCaches> caches; std::map ids; uint64_t lastKey = 0; }; //============================================================================== class InitiatorPropertyExchangeCache::Impl { public: std::optional primeCache (uint8_t maxSimultaneousRequests, std::function onDone) { const auto id = array.findUnusedId (maxSimultaneousRequests); return id.has_value() ? std::optional (array.primeCacheForRequestId (id->asInt(), std::move (onDone))) : std::nullopt; } bool terminate (Token64 token) { return array.terminate (token); } std::optional getTokenForRequestId (RequestID id) const { return array.getKeyForId (id); } std::optional 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 header) { array.notify (b, header); } auto getOngoingTransactions() const { return array.getOngoingTransactions(); } private: PropertyExchangeCacheArray array; }; //============================================================================== InitiatorPropertyExchangeCache::InitiatorPropertyExchangeCache() : pimpl (std::make_unique()) {} InitiatorPropertyExchangeCache::InitiatorPropertyExchangeCache (InitiatorPropertyExchangeCache&&) noexcept = default; InitiatorPropertyExchangeCache& InitiatorPropertyExchangeCache::operator= (InitiatorPropertyExchangeCache&&) noexcept = default; InitiatorPropertyExchangeCache::~InitiatorPropertyExchangeCache() = default; std::optional InitiatorPropertyExchangeCache::primeCache (uint8_t maxSimultaneousTransactions, std::function onDone) { return pimpl->primeCache (maxSimultaneousTransactions, std::move (onDone)); } bool InitiatorPropertyExchangeCache::terminate (Token64 token) { return pimpl->terminate (token); } std::optional InitiatorPropertyExchangeCache::getTokenForRequestId (RequestID id) const { return pimpl->getTokenForRequestId (id); } std::optional 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 header) { pimpl->notify (b, header); } std::vector InitiatorPropertyExchangeCache::getOngoingTransactions() const { return pimpl->getOngoingTransactions(); } //============================================================================== class ResponderPropertyExchangeCache::Impl { public: void primeCache (uint8_t maxSimultaneousTransactions, std::function 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 header) { array.notify (b, header); } int countOngoingTransactions() const { return array.countOngoingTransactions(); } private: PropertyExchangeCacheArray array; }; //============================================================================== ResponderPropertyExchangeCache::ResponderPropertyExchangeCache() : pimpl (std::make_unique()) {} ResponderPropertyExchangeCache::ResponderPropertyExchangeCache (ResponderPropertyExchangeCache&&) noexcept = default; ResponderPropertyExchangeCache& ResponderPropertyExchangeCache::operator= (ResponderPropertyExchangeCache&&) noexcept = default; ResponderPropertyExchangeCache::~ResponderPropertyExchangeCache() = default; void ResponderPropertyExchangeCache::primeCache (uint8_t maxSimultaneousTransactions, std::function 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 header) { pimpl->notify (b, header); } int ResponderPropertyExchangeCache::countOngoingTransactions() const { return pimpl->countOngoingTransactions(); } } // namespace juce::midi_ci