The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

356 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce::midi_ci
  19. {
  20. class PropertyExchangeCache
  21. {
  22. public:
  23. PropertyExchangeCache() = default;
  24. struct OwningResult
  25. {
  26. explicit OwningResult (PropertyExchangeResult::Error e)
  27. : result (e) {}
  28. OwningResult (var header, std::vector<std::byte> body)
  29. : backingStorage (std::move (body)),
  30. result (header, backingStorage) {}
  31. OwningResult (OwningResult&&) noexcept = default;
  32. OwningResult& operator= (OwningResult&&) noexcept = default;
  33. JUCE_DECLARE_NON_COPYABLE (OwningResult)
  34. std::vector<std::byte> backingStorage;
  35. PropertyExchangeResult result;
  36. };
  37. std::optional<OwningResult> addChunk (Message::DynamicSizePropertyExchange chunk)
  38. {
  39. jassert (chunk.thisChunkNum == lastChunk + 1 || chunk.thisChunkNum == 0);
  40. lastChunk = chunk.thisChunkNum;
  41. headerStorage.reserve (headerStorage.size() + chunk.header.size());
  42. std::transform (chunk.header.begin(),
  43. chunk.header.end(),
  44. std::back_inserter (headerStorage),
  45. [] (std::byte b) { return char (b); });
  46. bodyStorage.insert (bodyStorage.end(), chunk.data.begin(), chunk.data.end());
  47. if (chunk.thisChunkNum != 0 && chunk.thisChunkNum != chunk.totalNumChunks)
  48. return {};
  49. const auto headerJson = JSON::parse (String (headerStorage.data(), headerStorage.size()));
  50. terminate();
  51. const auto encodingString = headerJson.getProperty ("mutualEncoding", "ASCII").toString();
  52. if (chunk.thisChunkNum != chunk.totalNumChunks)
  53. return std::optional<OwningResult> { std::in_place, PropertyExchangeResult::Error::partial };
  54. const int status = headerJson.getProperty ("status", 200);
  55. if (status == 343)
  56. return std::optional<OwningResult> { std::in_place, PropertyExchangeResult::Error::tooManyTransactions };
  57. return std::optional<OwningResult> { std::in_place,
  58. headerJson,
  59. Encodings::decode (bodyStorage, EncodingUtils::toEncoding (encodingString.toRawUTF8()).value_or (Encoding::ascii)) };
  60. }
  61. std::optional<OwningResult> notify (Span<const std::byte> header)
  62. {
  63. const auto headerJson = JSON::parse (String (reinterpret_cast<const char*> (header.data()), header.size()));
  64. if (! headerJson.isObject())
  65. return {};
  66. const auto status = headerJson.getProperty ("status", {});
  67. if (! status.isInt() || (int) status == 100)
  68. return {};
  69. terminate();
  70. return std::optional<OwningResult> { std::in_place, PropertyExchangeResult::Error::notify };
  71. }
  72. bool terminate()
  73. {
  74. return std::exchange (ongoing, false);
  75. }
  76. private:
  77. std::vector<char> headerStorage;
  78. std::vector<std::byte> bodyStorage;
  79. uint16_t lastChunk = 0;
  80. bool ongoing = true;
  81. };
  82. //==============================================================================
  83. class PropertyExchangeCacheArray
  84. {
  85. public:
  86. PropertyExchangeCacheArray() = default;
  87. Token64 primeCacheForRequestId (uint8_t id, std::function<void (const PropertyExchangeResult&)> onDone)
  88. {
  89. jassert (id < caches.size());
  90. ++lastKey;
  91. auto& entry = caches[id];
  92. if (entry.has_value())
  93. {
  94. // Trying to start a new message with the same id as another in-progress message
  95. jassertfalse;
  96. ids.erase (entry->key);
  97. }
  98. const auto& item = entry.emplace (id, std::move (onDone), Token64 { lastKey });
  99. ids.emplace (item.key, id);
  100. return item.key;
  101. }
  102. bool terminate (Token64 key)
  103. {
  104. const auto iter = ids.find (key);
  105. // If the key isn't found, then the transaction must have completed already
  106. if (iter == ids.end())
  107. return false;
  108. // We're about to terminate this transaction, so we don't need to retain this record
  109. auto index = iter->second;
  110. ids.erase (iter);
  111. auto& entry = caches[index];
  112. // If the entry is null, something's gone wrong. The ids map should only contain elements for
  113. // non-null cache entries.
  114. if (! entry.has_value())
  115. {
  116. jassertfalse;
  117. return false;
  118. }
  119. const auto result = entry->cache.terminate();
  120. entry.reset();
  121. return result;
  122. }
  123. void addChunk (RequestID b, const Message::DynamicSizePropertyExchange& chunk)
  124. {
  125. updateCache (b, [&] (PropertyExchangeCache& c) { return c.addChunk (chunk); });
  126. }
  127. void notify (RequestID b, Span<const std::byte> header)
  128. {
  129. updateCache (b, [&] (PropertyExchangeCache& c) { return c.notify (header); });
  130. }
  131. std::optional<Token64> getKeyForId (RequestID id) const
  132. {
  133. if (auto& c = caches[id.asInt()])
  134. return c->key;
  135. return {};
  136. }
  137. bool hasTransaction (RequestID id) const
  138. {
  139. return getKeyForId (id).has_value();
  140. }
  141. std::optional<RequestID> getIdForKey (Token64 key) const
  142. {
  143. const auto iter = ids.find (key);
  144. return iter != ids.end() ? RequestID::create (iter->second) : std::nullopt;
  145. }
  146. auto countOngoingTransactions() const
  147. {
  148. jassert (ids.size() == (size_t) std::count_if (caches.begin(), caches.end(), [] (auto& c) { return c.has_value(); }));
  149. return (int) ids.size();
  150. }
  151. auto getOngoingTransactions() const
  152. {
  153. jassert (ids.size() == (size_t) std::count_if (caches.begin(), caches.end(), [] (auto& c) { return c.has_value(); }));
  154. std::vector<Token64> result (ids.size());
  155. std::transform (ids.begin(), ids.end(), result.begin(), [] (const auto& p) { return Token64 { p.first }; });
  156. return result;
  157. }
  158. std::optional<RequestID> findUnusedId (uint8_t maxSimultaneousTransactions) const
  159. {
  160. if (countOngoingTransactions() >= maxSimultaneousTransactions)
  161. return {};
  162. return RequestID::create ((uint8_t) std::distance (caches.begin(), std::find (caches.begin(), caches.end(), std::nullopt)));
  163. }
  164. // Instances must stay at the same location to ensure that references captured in the
  165. // ErasedScopeGuard returned from primeCacheForRequestId do not dangle.
  166. JUCE_DECLARE_NON_COPYABLE (PropertyExchangeCacheArray)
  167. JUCE_DECLARE_NON_MOVEABLE (PropertyExchangeCacheArray)
  168. private:
  169. static constexpr auto numCaches = 128;
  170. class Transaction
  171. {
  172. public:
  173. Transaction (uint8_t i, std::function<void (const PropertyExchangeResult&)> onSuccess, Token64 k)
  174. : onFinish (std::move (onSuccess)), key (k), id (i) {}
  175. PropertyExchangeCache cache;
  176. std::function<void (const PropertyExchangeResult&)> onFinish;
  177. Token64 key{};
  178. uint8_t id = 0;
  179. };
  180. template <typename WithCache>
  181. void updateCache (RequestID b, WithCache&& withCache)
  182. {
  183. if (auto& entry = caches[b.asInt()])
  184. {
  185. if (const auto result = withCache (entry->cache))
  186. {
  187. const auto tmp = std::move (*entry);
  188. ids.erase (tmp.key);
  189. entry.reset();
  190. NullCheckedInvocation::invoke (tmp.onFinish, result->result);
  191. }
  192. }
  193. }
  194. std::array<std::optional<Transaction>, numCaches> caches;
  195. std::map<Token64, uint8_t> ids;
  196. uint64_t lastKey = 0;
  197. };
  198. //==============================================================================
  199. class InitiatorPropertyExchangeCache::Impl
  200. {
  201. public:
  202. std::optional<Token64> primeCache (uint8_t maxSimultaneousRequests,
  203. std::function<void (const PropertyExchangeResult&)> onDone)
  204. {
  205. const auto id = array.findUnusedId (maxSimultaneousRequests);
  206. return id.has_value() ? std::optional<Token64> (array.primeCacheForRequestId (id->asInt(), std::move (onDone)))
  207. : std::nullopt;
  208. }
  209. bool terminate (Token64 token)
  210. {
  211. return array.terminate (token);
  212. }
  213. std::optional<Token64> getTokenForRequestId (RequestID id) const
  214. {
  215. return array.getKeyForId (id);
  216. }
  217. std::optional<RequestID> getRequestIdForToken (Token64 token) const
  218. {
  219. return array.getIdForKey (token);
  220. }
  221. void addChunk (RequestID b, const Message::DynamicSizePropertyExchange& chunk) { array.addChunk (b, chunk); }
  222. void notify (RequestID b, Span<const std::byte> header) { array.notify (b, header); }
  223. auto getOngoingTransactions() const { return array.getOngoingTransactions(); }
  224. private:
  225. PropertyExchangeCacheArray array;
  226. };
  227. //==============================================================================
  228. InitiatorPropertyExchangeCache::InitiatorPropertyExchangeCache() : pimpl (std::make_unique<Impl>()) {}
  229. InitiatorPropertyExchangeCache::InitiatorPropertyExchangeCache (InitiatorPropertyExchangeCache&&) noexcept = default;
  230. InitiatorPropertyExchangeCache& InitiatorPropertyExchangeCache::operator= (InitiatorPropertyExchangeCache&&) noexcept = default;
  231. InitiatorPropertyExchangeCache::~InitiatorPropertyExchangeCache() = default;
  232. std::optional<Token64> InitiatorPropertyExchangeCache::primeCache (uint8_t maxSimultaneousTransactions,
  233. std::function<void (const PropertyExchangeResult&)> onDone)
  234. {
  235. return pimpl->primeCache (maxSimultaneousTransactions, std::move (onDone));
  236. }
  237. bool InitiatorPropertyExchangeCache::terminate (Token64 token) { return pimpl->terminate (token); }
  238. std::optional<Token64> InitiatorPropertyExchangeCache::getTokenForRequestId (RequestID id) const { return pimpl->getTokenForRequestId (id); }
  239. std::optional<RequestID> InitiatorPropertyExchangeCache::getRequestIdForToken (Token64 token) const { return pimpl->getRequestIdForToken (token); }
  240. void InitiatorPropertyExchangeCache::addChunk (RequestID b, const Message::DynamicSizePropertyExchange& chunk) { pimpl->addChunk (b, chunk); }
  241. void InitiatorPropertyExchangeCache::notify (RequestID b, Span<const std::byte> header) { pimpl->notify (b, header); }
  242. std::vector<Token64> InitiatorPropertyExchangeCache::getOngoingTransactions() const { return pimpl->getOngoingTransactions(); }
  243. //==============================================================================
  244. class ResponderPropertyExchangeCache::Impl
  245. {
  246. public:
  247. void primeCache (uint8_t maxSimultaneousTransactions,
  248. std::function<void (const PropertyExchangeResult&)> onDone,
  249. RequestID id)
  250. {
  251. if (array.hasTransaction (id))
  252. return;
  253. if (array.countOngoingTransactions() >= maxSimultaneousTransactions)
  254. NullCheckedInvocation::invoke (onDone, PropertyExchangeResult { PropertyExchangeResult::Error::tooManyTransactions });
  255. else
  256. array.primeCacheForRequestId (id.asInt(), std::move (onDone));
  257. }
  258. void addChunk (RequestID b, const Message::DynamicSizePropertyExchange& chunk) { array.addChunk (b, chunk); }
  259. void notify (RequestID b, Span<const std::byte> header) { array.notify (b, header); }
  260. int countOngoingTransactions() const { return array.countOngoingTransactions(); }
  261. private:
  262. PropertyExchangeCacheArray array;
  263. };
  264. //==============================================================================
  265. ResponderPropertyExchangeCache::ResponderPropertyExchangeCache() : pimpl (std::make_unique<Impl>()) {}
  266. ResponderPropertyExchangeCache::ResponderPropertyExchangeCache (ResponderPropertyExchangeCache&&) noexcept = default;
  267. ResponderPropertyExchangeCache& ResponderPropertyExchangeCache::operator= (ResponderPropertyExchangeCache&&) noexcept = default;
  268. ResponderPropertyExchangeCache::~ResponderPropertyExchangeCache() = default;
  269. void ResponderPropertyExchangeCache::primeCache (uint8_t maxSimultaneousTransactions,
  270. std::function<void (const PropertyExchangeResult&)> onDone,
  271. RequestID id)
  272. {
  273. return pimpl->primeCache (maxSimultaneousTransactions, std::move (onDone), id);
  274. }
  275. void ResponderPropertyExchangeCache::addChunk (RequestID b, const Message::DynamicSizePropertyExchange& chunk) { pimpl->addChunk (b, chunk); }
  276. void ResponderPropertyExchangeCache::notify (RequestID b, Span<const std::byte> header) { pimpl->notify (b, header); }
  277. int ResponderPropertyExchangeCache::countOngoingTransactions() const { return pimpl->countOngoingTransactions(); }
  278. } // namespace juce::midi_ci