tidal-link-1.2.1: link/include/ableton/link_audio/PeerGateways.hpp
/* Copyright 2025, Ableton AG, Berlin. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you would like to incorporate Link into a proprietary software application,
* please contact <link-devs@ableton.com>.
*/
#pragma once
#include <ableton/discovery/AsioTypes.hpp>
#include <ableton/link/NodeId.hpp>
#include <ableton/util/Injected.hpp>
#include <algorithm>
#include <map>
#include <memory>
#include <optional>
#include <type_traits>
#include <vector>
namespace ableton
{
namespace link_audio
{
template <typename GatewayFactory, typename IoContext>
struct PeerGateways
{
using FactoryType = typename util::Injected<GatewayFactory>::type;
using GatewayPtr = std::invoke_result_t<FactoryType, const discovery::IpAddress&>;
using GatewayMap = std::map<discovery::IpAddress, GatewayPtr>;
PeerGateways(util::Injected<GatewayFactory> factory, util::Injected<IoContext> io)
: mIo(std::move(io))
, mFactory(std::move(factory))
{
}
template <typename Handler>
void forEachGateway(Handler handler)
{
for (auto it = mGateways.begin(); it != mGateways.end(); ++it)
{
handler(it->second);
}
}
template <typename Handler>
void withGateways(Handler handler)
{
handler(mGateways.begin(), mGateways.end());
}
void clear()
{
mGateways.clear();
mFactory->gatewaysChanged();
}
template <typename Announcement>
void updateAnnouncement(const Announcement& announcement)
{
for (const auto& gateway : mGateways)
{
gateway.second->updateAnnouncement(announcement);
}
}
void updateGateways(std::vector<discovery::IpAddress> linkGateways)
{
using namespace std;
vector<discovery::IpAddress> curAddrs;
curAddrs.reserve(mGateways.size());
transform(mGateways.begin(),
mGateways.end(),
back_inserter(curAddrs),
[](const auto& gateway) { return gateway.first; });
vector<discovery::IpAddress> staleAddrs;
set_difference(curAddrs.begin(),
curAddrs.end(),
linkGateways.begin(),
linkGateways.end(),
back_inserter(staleAddrs));
for (const auto& addr : staleAddrs)
{
mGateways.erase(addr);
}
vector<discovery::IpAddress> newAddrs;
set_difference(linkGateways.begin(),
linkGateways.end(),
curAddrs.begin(),
curAddrs.end(),
back_inserter(newAddrs));
for (const auto& addr : newAddrs)
{
try
{
info(mIo->log()) << "initializing audio gateway on interface " << addr;
mGateways.emplace(addr, (*mFactory)(addr));
}
catch (const std::runtime_error& e)
{
warning(mIo->log()) << "failed to init audio gateway on interface " << addr
<< " reason: " << e.what();
}
}
if (!staleAddrs.empty() || !newAddrs.empty())
{
mFactory->gatewaysChanged();
}
}
template <typename It>
void updateSessionPeers(It peersBegin, It peersEnd)
{
forEachGateway([&](auto& gateway)
{ gateway->pruneChannelsEndpoints(peersBegin, peersEnd); });
}
void sawLinkAudioEndpoint(link::NodeId peerId,
std::optional<discovery::UdpEndpoint> endpoint,
discovery::IpAddress gateway)
{
if (auto it = mGateways.find(gateway); it != mGateways.end())
{
it->second->sawLinkAudioEndpoint(peerId, endpoint);
}
}
private:
util::Injected<IoContext> mIo;
GatewayMap mGateways;
util::Injected<GatewayFactory> mFactory;
};
} // namespace link_audio
} // namespace ableton