/* ============================================================================== 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: explicit PropertyExchangeCache (std::function term) : onTerminate (std::move (term)) {} 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())); onTerminate = nullptr; const auto encodingString = headerJson.getProperty ("mutualEncoding", "ASCII").toString(); if (chunk.thisChunkNum != chunk.totalNumChunks) return std::optional { std::in_place, PropertyExchangeResult::Error::partial }; 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 {}; onTerminate = nullptr; return std::optional { std::in_place, PropertyExchangeResult::Error::notify }; } void terminate() { if (auto t = std::exchange (onTerminate, nullptr)) t(); } private: std::vector headerStorage; std::vector bodyStorage; std::function onTerminate; uint16_t lastChunk = 0; }; //============================================================================== class PropertyExchangeCacheArray { public: PropertyExchangeCacheArray() = default; ErasedScopeGuard primeCacheForRequestId (std::byte id, std::function onDone, std::function onTerminate) { auto& entry = caches[(uint8_t) id]; entry = std::make_shared (std::move (onDone), std::move (onTerminate)); auto weak = std::weak_ptr (entry); return ErasedScopeGuard { [&entry, weak] { // If this fails, then the transaction finished before the ErasedScopeGuard was destroyed. if (auto locked = weak.lock()) { entry->cache.terminate(); entry = nullptr; } } }; } void addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk) { updateCache (b, [&] (PropertyExchangeCache& c) { return c.addChunk (chunk); }); } void notify (std::byte b, Span header) { updateCache (b, [&] (PropertyExchangeCache& c) { return c.notify (header); }); } bool hasTransaction (std::byte id) const { return caches[(uint8_t) id] != nullptr; } uint8_t countOngoingTransactions() const { return (uint8_t) std::count_if (caches.begin(), caches.end(), [] (auto& c) { return c != nullptr; }); } /** MSB of result is set on failure. */ std::byte findUnusedId (uint8_t maxSimultaneousTransactions) const { if (countOngoingTransactions() >= maxSimultaneousTransactions) return std::byte { 0xff }; return (std::byte) std::distance (caches.begin(), std::find (caches.begin(), caches.end(), nullptr)); } // 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 (std::function onSuccess, std::function onTerminate) : cache (std::move (onTerminate)), onFinish (std::move (onSuccess)) {} PropertyExchangeCache cache; std::function onFinish; }; template void updateCache (std::byte b, WithCache&& withCache) { if (auto& entry = caches[(uint8_t) b]) { if (const auto result = withCache (entry->cache)) { const auto tmp = std::move (entry->onFinish); entry = nullptr; NullCheckedInvocation::invoke (tmp, result->result); } } } std::array, numCaches> caches; }; //============================================================================== class InitiatorPropertyExchangeCache::Impl { public: TokenAndId primeCache (uint8_t maxSimultaneousRequests, std::function onDone, std::function onTerminate) { const auto id = array.findUnusedId (maxSimultaneousRequests); if ((id & std::byte { 0x80 }) != std::byte{}) { NullCheckedInvocation::invoke (onDone, PropertyExchangeResult { PropertyExchangeResult::Error::tooManyTransactions }); return {}; } auto token = array.primeCacheForRequestId (id, std::move (onDone), [id, term = std::move (onTerminate)] { NullCheckedInvocation::invoke (term, id); }); return { std::move (token), id }; } void addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk) { array.addChunk (b, chunk); } void notify (std::byte b, Span header) { array.notify (b, header); } int countOngoingTransactions() const { return array.countOngoingTransactions(); } bool isAwaitingResponse() const { return countOngoingTransactions() != 0; } private: PropertyExchangeCacheArray array; }; //============================================================================== InitiatorPropertyExchangeCache::InitiatorPropertyExchangeCache() : pimpl (std::make_unique()) {} InitiatorPropertyExchangeCache::InitiatorPropertyExchangeCache (InitiatorPropertyExchangeCache&&) noexcept = default; InitiatorPropertyExchangeCache& InitiatorPropertyExchangeCache::operator= (InitiatorPropertyExchangeCache&&) noexcept = default; InitiatorPropertyExchangeCache::~InitiatorPropertyExchangeCache() = default; InitiatorPropertyExchangeCache::TokenAndId InitiatorPropertyExchangeCache::primeCache (uint8_t maxSimultaneousTransactions, std::function onDone, std::function onTerminate) { return pimpl->primeCache (maxSimultaneousTransactions, std::move (onDone), std::move (onTerminate)); } void InitiatorPropertyExchangeCache::addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk) { pimpl->addChunk (b, chunk); } void InitiatorPropertyExchangeCache::notify (std::byte b, Span header) { pimpl->notify (b, header); } int InitiatorPropertyExchangeCache::countOngoingTransactions() const { return pimpl->countOngoingTransactions(); } bool InitiatorPropertyExchangeCache::isAwaitingResponse() const { return pimpl->isAwaitingResponse(); } //============================================================================== class ResponderPropertyExchangeCache::Impl { public: void primeCache (uint8_t maxSimultaneousTransactions, std::function onDone, std::byte id) { if (array.hasTransaction (id)) return; if (array.countOngoingTransactions() >= maxSimultaneousTransactions) NullCheckedInvocation::invoke (onDone, PropertyExchangeResult { PropertyExchangeResult::Error::tooManyTransactions }); else array.primeCacheForRequestId (id, std::move (onDone), nullptr).release(); } void addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk) { array.addChunk (b, chunk); } void notify (std::byte 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, std::byte id) { return pimpl->primeCache (maxSimultaneousTransactions, std::move (onDone), id); } void ResponderPropertyExchangeCache::addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk) { pimpl->addChunk (b, chunk); } void ResponderPropertyExchangeCache::notify (std::byte b, Span header) { pimpl->notify (b, header); } int ResponderPropertyExchangeCache::countOngoingTransactions() const { return pimpl->countOngoingTransactions(); } } // namespace juce::midi_ci