1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-02-01 03:10:06 +00:00
JUCE/modules/juce_midi_ci/ci/juce_CIPropertyExchangeCache.cpp
2023-10-31 11:46:49 +00:00

291 lines
12 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:
explicit PropertyExchangeCache (std::function<void()> term)
: onTerminate (std::move (term)) {}
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()));
onTerminate = nullptr;
const auto encodingString = headerJson.getProperty ("mutualEncoding", "ASCII").toString();
if (chunk.thisChunkNum != chunk.totalNumChunks)
return std::optional<OwningResult> { std::in_place, PropertyExchangeResult::Error::partial };
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 {};
onTerminate = nullptr;
return std::optional<OwningResult> { std::in_place, PropertyExchangeResult::Error::notify };
}
void terminate()
{
if (auto t = std::exchange (onTerminate, nullptr))
t();
}
private:
std::vector<char> headerStorage;
std::vector<std::byte> bodyStorage;
std::function<void()> onTerminate;
uint16_t lastChunk = 0;
};
//==============================================================================
class PropertyExchangeCacheArray
{
public:
PropertyExchangeCacheArray() = default;
ErasedScopeGuard primeCacheForRequestId (std::byte id,
std::function<void (const PropertyExchangeResult&)> onDone,
std::function<void()> onTerminate)
{
auto& entry = caches[(uint8_t) id];
entry = std::make_shared<Transaction> (std::move (onDone), std::move (onTerminate));
auto weak = std::weak_ptr<Transaction> (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<const std::byte> 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<void (const PropertyExchangeResult&)> onSuccess,
std::function<void()> onTerminate)
: cache (std::move (onTerminate)), onFinish (std::move (onSuccess)) {}
PropertyExchangeCache cache;
std::function<void (const PropertyExchangeResult&)> onFinish;
};
template <typename WithCache>
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<std::shared_ptr<Transaction>, numCaches> caches;
};
//==============================================================================
class InitiatorPropertyExchangeCache::Impl
{
public:
TokenAndId primeCache (uint8_t maxSimultaneousRequests,
std::function<void (const PropertyExchangeResult&)> onDone,
std::function<void (std::byte)> 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<const std::byte> 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<Impl>()) {}
InitiatorPropertyExchangeCache::InitiatorPropertyExchangeCache (InitiatorPropertyExchangeCache&&) noexcept = default;
InitiatorPropertyExchangeCache& InitiatorPropertyExchangeCache::operator= (InitiatorPropertyExchangeCache&&) noexcept = default;
InitiatorPropertyExchangeCache::~InitiatorPropertyExchangeCache() = default;
InitiatorPropertyExchangeCache::TokenAndId InitiatorPropertyExchangeCache::primeCache (uint8_t maxSimultaneousTransactions,
std::function<void (const PropertyExchangeResult&)> onDone,
std::function<void (std::byte)> 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<const std::byte> 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<void (const PropertyExchangeResult&)> 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<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,
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<const std::byte> header) { pimpl->notify (b, header); }
int ResponderPropertyExchangeCache::countOngoingTransactions() const { return pimpl->countOngoingTransactions(); }
} // namespace juce::midi_ci