196 changes: 128 additions & 68 deletions link/ableton/Link.hpp
Expand Up @@ -32,13 +32,20 @@ namespace ableton
/*! @class Link
* @brief Class that represents a participant in a Link session.
*
* @discussion Each Link instance has its own beat timeline that
* starts running from beat 0 at the initial tempo when
* constructed. A Link instance is initially disabled after
* construction, which means that it will not communicate on the
* network. Once enabled, a Link instance initiates network
* communication in an effort to discover other peers. When peers are
* discovered, they immediately become part of a shared Link session.
* @discussion Each Link instance has its own session state which
* represents a beat timeline and a transport start/stop state. The
* timeline starts running from beat 0 at the initial tempo when
* constructed. The timeline always advances at a speed defined by
* its current tempo, even if transport is stopped. Synchronizing to the
* transport start/stop state of Link is optional for every peer.
* The transport start/stop state is only shared with other peers when
* start/stop synchronization is enabled.
*
* A Link instance is initially disabled after construction, which
* means that it will not communicate on the network. Once enabled,
* a Link instance initiates network communication in an effort to
* discover other peers. When peers are discovered, they immediately
* become part of a shared Link session.
*
* Each method of the Link type documents its thread-safety and
* realtime-safety properties. When a method is marked thread-safe,
Expand All @@ -47,31 +54,31 @@ namespace ableton
* it does not block and is appropriate for use in the thread that
* performs audio IO.
*
* Link provides one Timeline capture/commit method pair for use in the
* audio thread and one for all other application contexts. In
* general, modifying the Link timeline should be done in the audio
* Link provides one session state capture/commit method pair for use
* in the audio thread and one for all other application contexts. In
* general, modifying the session state should be done in the audio
* thread for the most accurate timing results. The ability to modify
* the Link timeline from application threads should only be used in
* the session state from application threads should only be used in
* cases where an application's audio thread is not actively running
* or if it doesn't generate audio at all. Modifying the Link
* timeline from both the audio thread and an application thread
* concurrently is not advised and will potentially lead to
* unexpected behavior.
* or if it doesn't generate audio at all. Modifying the Link session
* state from both the audio thread and an application thread
* concurrently is not advised and will potentially lead to unexpected
* behavior.
*/
template <typename Clock>
class BasicLink
class Link
{
public:
class Timeline;
using Clock = link::platform::Clock;
class SessionState;

/*! @brief Construct with an initial tempo. */
BasicLink(double bpm);
Link(double bpm);

/*! @brief Link instances cannot be copied or moved */
BasicLink(const BasicLink<Clock>&) = delete;
BasicLink& operator=(const BasicLink<Clock>&) = delete;
BasicLink(BasicLink<Clock>&&) = delete;
BasicLink& operator=(BasicLink<Clock>&&) = delete;
Link(const Link&) = delete;
Link& operator=(const Link&) = delete;
Link(Link&&) = delete;
Link& operator=(Link&&) = delete;

/*! @brief Is Link currently enabled?
* Thread-safe: yes
Expand All @@ -85,6 +92,18 @@ class BasicLink
*/
void enable(bool bEnable);

/*! @brief: Is start/stop synchronization enabled?
* Thread-safe: yes
* Realtime-safe: no
*/
bool isStartStopSyncEnabled() const;

/*! @brief: Enable start/stop synchronization.
* Thread-safe: yes
* Realtime-safe: no
*/
void enableStartStopSync(bool bEnable);

/*! @brief How many peers are currently connected in a Link session?
* Thread-safe: yes
* Realtime-safe: yes
Expand Down Expand Up @@ -116,6 +135,19 @@ class BasicLink
template <typename Callback>
void setTempoCallback(Callback callback);

/*! brief: Register a callback to be notified when the state of
* start/stop isPlaying changes.
* Thread-safe: yes
* Realtime-safe: no
*
* @discussion The callback is invoked on a Link-managed thread.
*
* @param callback The callback signature is:
* void (bool isPlaying)
*/
template <typename Callback>
void setStartStopCallback(Callback callback);

/*! @brief The clock used by Link.
* Thread-safe: yes
* Realtime-safe: yes
Expand All @@ -130,69 +162,81 @@ class BasicLink
*/
Clock clock() const;

/*! @brief Capture the current Link timeline from the audio thread.
/*! @brief Capture the current Link Session State from the audio thread.
* Thread-safe: no
* Realtime-safe: yes
*
* @discussion This method should ONLY be called in the audio thread
* and must not be accessed from any other threads. The returned
* Timeline stores a snapshot of the current Link state, so it
* object stores a snapshot of the current Link Session State, so it
* should be captured and used in a local scope. Storing the
* Timeline for later use in a different context is not advised
* because it will provide an outdated view on the Link state.
* Session State for later use in a different context is not advised
* because it will provide an outdated view.
*/
Timeline captureAudioTimeline() const;
SessionState captureAudioSessionState() const;

/*! @brief Commit the given timeline to the Link session from the
/*! @brief Commit the given Session State to the Link session from the
* audio thread.
* Thread-safe: no
* Realtime-safe: yes
*
* @discussion This method should ONLY be called in the audio
* thread. The given timeline will replace the current Link
* timeline. Modifications to the session based on the new timeline
* will be communicated to other peers in the session.
* thread. The given Session State will replace the current Link
* state. Modifications will be communicated to other peers in the
* session.
*/
void commitAudioTimeline(Timeline timeline);
void commitAudioSessionState(SessionState state);

/*! @brief Capture the current Link timeline from an application
/*! @brief Capture the current Link Session State from an application
* thread.
* Thread-safe: yes
* Realtime-safe: no
*
* @discussion Provides a mechanism for capturing the Link timeline
* from an application thread (other than the audio thread). The
* returned Timeline stores a snapshot of the current Link state,
* so it should be captured and used in a local scope. Storing the
* Timeline for later use in a different context is not advised
* because it will provide an outdated view on the Link state.
* @discussion Provides a mechanism for capturing the Link Session
* State from an application thread (other than the audio thread).
* The returned Session State stores a snapshot of the current Link
* state, so it should be captured and used in a local scope.
* Storing the it for later use in a different context is not
* advised because it will provide an outdated view.
*/
Timeline captureAppTimeline() const;
SessionState captureAppSessionState() const;

/*! @brief Commit the given timeline to the Link session from an
/*! @brief Commit the given Session State to the Link session from an
* application thread.
* Thread-safe: yes
* Realtime-safe: no
*
* @discussion The given timeline will replace the current Link
* timeline. Modifications to the session based on the new timeline
* will be communicated to other peers in the session.
* @discussion The given Session State will replace the current Link
* Session State. Modifications of the Session State will be
* communicated to other peers in the session.
*/
void commitAppTimeline(Timeline timeline);
void commitAppSessionState(SessionState state);

/*! @class Timeline
* @brief Representation of a mapping between time and beats for
* varying quanta.
/*! @class SessionState
* @brief Representation of a timeline and the start/stop state
*
* @discussion A SessionState object is intended for use in a local scope within
* a single thread - none of its methods are thread-safe. All of its methods are
* non-blocking, so it is safe to use from a realtime thread.
* It provides functions to observe and manipulate the timeline and start/stop
* state.
*
* @discussion A Timeline object is intended for use in a local
* scope within a single thread - none of its methods are
* thread-safe. All of its methods are non-blocking, so it is safe
* to use from a realtime thread.
* The timeline is a representation of a mapping between time and beats for varying
* quanta.
* The start/stop state represents the user intention to start or stop transport at
* a specific time. Start stop synchronization is an optional feature that allows to
* share the user request to start or stop transport between a subgroup of peers in
* a Link session. When observing a change of start/stop state, audio playback of a
* peer should be started or stopped the same way it would have happened if the user
* had requested that change at the according time locally. The start/stop state can
* only be changed by the user. This means that the current local start/stop state
* persists when joining or leaving a Link session. After joining a Link session
* start/stop change requests will be communicated to all connected peers.
*/
class Timeline
class SessionState
{
public:
Timeline(const link::Timeline timeline, const bool bRespectQuantum);
SessionState(const link::ApiState state, const bool bRespectQuantum);

/*! @brief: The tempo of the timeline, in bpm */
double tempo() const;
Expand Down Expand Up @@ -286,29 +330,45 @@ class BasicLink
*/
void forceBeatAtTime(double beat, std::chrono::microseconds time, double quantum);

private:
friend BasicLink<Clock>;
/*! @brief: Set if transport should be playing or stopped, taking effect
* at the given time.
*/
void setIsPlaying(bool isPlaying, std::chrono::microseconds time);

/*! @brief: Is transport playing? */
bool isPlaying() const;

/*! @brief: Get the time at which a transport start/stop occurs */
std::chrono::microseconds timeForIsPlaying() const;

link::Timeline mOriginalTimeline;
/*! @brief: Convenience function to attempt to map the given beat to the time
* when transport is starting to play in context of the given quantum.
* This function evaluates to a no-op if isPlaying() equals false.
*/
void requestBeatAtStartPlayingTime(double beat, double quantum);

/*! @brief: Convenience function to start or stop transport at a given time and
* attempt to map the given beat to this time in context of the given quantum.
*/
void setIsPlayingAndRequestBeatAtTime(
bool isPlaying, std::chrono::microseconds time, double beat, double quantum);

private:
friend Link;
link::ApiState mOriginalState;
link::ApiState mState;
bool mbRespectQuantum;
link::Timeline mTimeline;
};

private:
using Controller = ableton::link::Controller<link::PeerCountCallback,
link::TempoCallback,
Clock,
link::platform::IoContext>;

std::mutex mCallbackMutex;
link::PeerCountCallback mPeerCountCallback;
link::TempoCallback mTempoCallback;
link::StartStopStateCallback mStartStopCallback;
Clock mClock;
Controller mController;
link::platform::Controller mController;
};

using Link = BasicLink<link::platform::Clock>;

} // ableton
} // namespace ableton

#include <ableton/Link.ipp>
189 changes: 125 additions & 64 deletions link/ableton/Link.ipp
Expand Up @@ -23,11 +23,40 @@

namespace ableton
{
namespace detail
{

inline Link::SessionState toSessionState(
const link::ClientState& state, const bool isConnected)
{
const auto time = state.timeline.fromBeats(state.startStopState.beats);
const auto startStopState =
link::ApiStartStopState{state.startStopState.isPlaying, time};
return {{state.timeline, startStopState}, isConnected};
}

template <typename Clock>
inline BasicLink<Clock>::BasicLink(const double bpm)
inline link::IncomingClientState toIncomingClientState(const link::ApiState& state,
const link::ApiState& originalState,
const std::chrono::microseconds timestamp)
{
const auto timeline = originalState.timeline != state.timeline
? link::OptionalTimeline{state.timeline}
: link::OptionalTimeline{};
const auto startStopState =
originalState.startStopState != state.startStopState
? link::OptionalStartStopState{{state.startStopState.isPlaying,
state.timeline.toBeats(state.startStopState.time), timestamp}}
: link::OptionalStartStopState{};
return {timeline, startStopState, timestamp};
}

} // namespace detail

inline Link::Link(const double bpm)
: mPeerCountCallback([](std::size_t) {})
, mTempoCallback([](link::Tempo) {})
, mStartStopCallback([](bool) {})
, mClock{}
, mController(link::Tempo(bpm),
[this](const std::size_t peers) {
std::lock_guard<std::mutex> lock(mCallbackMutex);
Expand All @@ -37,134 +66,134 @@ inline BasicLink<Clock>::BasicLink(const double bpm)
std::lock_guard<std::mutex> lock(mCallbackMutex);
mTempoCallback(tempo);
},
[this](const bool isPlaying) {
std::lock_guard<std::mutex> lock(mCallbackMutex);
mStartStopCallback(isPlaying);
},
mClock,
util::injectVal(link::platform::IoContext{}))
{
}

template <typename Clock>
inline bool BasicLink<Clock>::isEnabled() const
inline bool Link::isEnabled() const
{
return mController.isEnabled();
}

template <typename Clock>
inline void BasicLink<Clock>::enable(const bool bEnable)
inline void Link::enable(const bool bEnable)
{
mController.enable(bEnable);
}

template <typename Clock>
inline std::size_t BasicLink<Clock>::numPeers() const
inline bool Link::isStartStopSyncEnabled() const
{
return mController.isStartStopSyncEnabled();
}

inline void Link::enableStartStopSync(bool bEnable)
{
mController.enableStartStopSync(bEnable);
}

inline std::size_t Link::numPeers() const
{
return mController.numPeers();
}

template <typename Clock>
template <typename Callback>
void BasicLink<Clock>::setNumPeersCallback(Callback callback)
void Link::setNumPeersCallback(Callback callback)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mPeerCountCallback = [callback](const std::size_t numPeers) { callback(numPeers); };
}

template <typename Clock>
template <typename Callback>
void BasicLink<Clock>::setTempoCallback(Callback callback)
void Link::setTempoCallback(Callback callback)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mTempoCallback = [callback](const link::Tempo tempo) { callback(tempo.bpm()); };
}

template <typename Clock>
inline Clock BasicLink<Clock>::clock() const
template <typename Callback>
void Link::setStartStopCallback(Callback callback)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mStartStopCallback = callback;
}

inline Link::Clock Link::clock() const
{
return mClock;
}

template <typename Clock>
inline typename BasicLink<Clock>::Timeline BasicLink<Clock>::captureAudioTimeline() const
inline Link::SessionState Link::captureAudioSessionState() const
{
return BasicLink<Clock>::Timeline{mController.timelineRtSafe(), numPeers() > 0};
return detail::toSessionState(mController.clientStateRtSafe(), numPeers() > 0);
}

template <typename Clock>
inline void BasicLink<Clock>::commitAudioTimeline(const Timeline timeline)
inline void Link::commitAudioSessionState(const Link::SessionState state)
{
if (timeline.mOriginalTimeline != timeline.mTimeline)
{
mController.setTimelineRtSafe(timeline.mTimeline, mClock.micros());
}
mController.setClientStateRtSafe(
detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros()));
}

template <typename Clock>
inline typename BasicLink<Clock>::Timeline BasicLink<Clock>::captureAppTimeline() const
inline Link::SessionState Link::captureAppSessionState() const
{
return Timeline{mController.timeline(), numPeers() > 0};
return detail::toSessionState(mController.clientState(), numPeers() > 0);
}

template <typename Clock>
inline void BasicLink<Clock>::commitAppTimeline(const Timeline timeline)
inline void Link::commitAppSessionState(const Link::SessionState state)
{
if (timeline.mOriginalTimeline != timeline.mTimeline)
{
mController.setTimeline(timeline.mTimeline, mClock.micros());
}
mController.setClientState(
detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros()));
}

////////////////////
// Link::Timeline //
////////////////////
// Link::SessionState

template <typename Clock>
inline BasicLink<Clock>::Timeline::Timeline(
const link::Timeline timeline, const bool bRespectQuantum)
: mOriginalTimeline(timeline)
inline Link::SessionState::SessionState(
const link::ApiState state, const bool bRespectQuantum)
: mOriginalState(state)
, mState(state)
, mbRespectQuantum(bRespectQuantum)
, mTimeline(timeline)
{
}

template <typename Clock>
inline double BasicLink<Clock>::Timeline::tempo() const
inline double Link::SessionState::tempo() const
{
return mTimeline.tempo.bpm();
return mState.timeline.tempo.bpm();
}

template <typename Clock>
inline void BasicLink<Clock>::Timeline::setTempo(
inline void Link::SessionState::setTempo(
const double bpm, const std::chrono::microseconds atTime)
{
const auto desiredTl =
link::clampTempo(link::Timeline{link::Tempo(bpm), mTimeline.toBeats(atTime), atTime});
mTimeline.tempo = desiredTl.tempo;
mTimeline.timeOrigin = desiredTl.fromBeats(mTimeline.beatOrigin);
const auto desiredTl = link::clampTempo(
link::Timeline{link::Tempo(bpm), mState.timeline.toBeats(atTime), atTime});
mState.timeline.tempo = desiredTl.tempo;
mState.timeline.timeOrigin = desiredTl.fromBeats(mState.timeline.beatOrigin);
}

template <typename Clock>
inline double BasicLink<Clock>::Timeline::beatAtTime(
inline double Link::SessionState::beatAtTime(
const std::chrono::microseconds time, const double quantum) const
{
return link::toPhaseEncodedBeats(mTimeline, time, link::Beats{quantum}).floating();
return link::toPhaseEncodedBeats(mState.timeline, time, link::Beats{quantum})
.floating();
}

template <typename Clock>
inline double BasicLink<Clock>::Timeline::phaseAtTime(
inline double Link::SessionState::phaseAtTime(
const std::chrono::microseconds time, const double quantum) const
{
return link::phase(link::Beats{beatAtTime(time, quantum)}, link::Beats{quantum})
.floating();
}

template <typename Clock>
inline std::chrono::microseconds BasicLink<Clock>::Timeline::timeAtBeat(
inline std::chrono::microseconds Link::SessionState::timeAtBeat(
const double beat, const double quantum) const
{
return link::fromPhaseEncodedBeats(mTimeline, link::Beats{beat}, link::Beats{quantum});
return link::fromPhaseEncodedBeats(
mState.timeline, link::Beats{beat}, link::Beats{quantum});
}

template <typename Clock>
inline void BasicLink<Clock>::Timeline::requestBeatAtTime(
inline void Link::SessionState::requestBeatAtTime(
const double beat, std::chrono::microseconds time, const double quantum)
{
if (mbRespectQuantum)
Expand All @@ -177,18 +206,50 @@ inline void BasicLink<Clock>::Timeline::requestBeatAtTime(
forceBeatAtTime(beat, time, quantum);
}

template <typename Clock>
inline void BasicLink<Clock>::Timeline::forceBeatAtTime(
inline void Link::SessionState::forceBeatAtTime(
const double beat, const std::chrono::microseconds time, const double quantum)
{
// There are two components to the beat adjustment: a phase shift
// and a beat magnitude adjustment.
const auto curBeatAtTime = link::Beats{beatAtTime(time, quantum)};
const auto closestInPhase =
link::closestPhaseMatch(curBeatAtTime, link::Beats{beat}, link::Beats{quantum});
mTimeline = shiftClientTimeline(mTimeline, closestInPhase - curBeatAtTime);
mState.timeline = shiftClientTimeline(mState.timeline, closestInPhase - curBeatAtTime);
// Now adjust the magnitude
mTimeline.beatOrigin = mTimeline.beatOrigin + (link::Beats{beat} - closestInPhase);
mState.timeline.beatOrigin =
mState.timeline.beatOrigin + (link::Beats{beat} - closestInPhase);
}

inline void Link::SessionState::setIsPlaying(
const bool isPlaying, const std::chrono::microseconds time)
{
mState.startStopState = {isPlaying, time};
}

inline bool Link::SessionState::isPlaying() const
{
return mState.startStopState.isPlaying;
}

inline std::chrono::microseconds Link::SessionState::timeForIsPlaying() const
{
return mState.startStopState.time;
}

inline void Link::SessionState::requestBeatAtStartPlayingTime(
const double beat, const double quantum)
{
if (isPlaying())
{
requestBeatAtTime(beat, mState.startStopState.time, quantum);
}
}

inline void Link::SessionState::setIsPlayingAndRequestBeatAtTime(
bool isPlaying, std::chrono::microseconds time, double beat, double quantum)
{
mState.startStopState = {isPlaying, time};
requestBeatAtStartPlayingTime(beat, quantum);
}

} // ableton
} // namespace ableton
4 changes: 2 additions & 2 deletions link/ableton/discovery/IpV4Interface.hpp
Expand Up @@ -19,7 +19,7 @@

#pragma once

#include <ableton/platforms/asio/AsioService.hpp>
#include <ableton/platforms/asio/AsioWrapper.hpp>
#include <ableton/util/Injected.hpp>

namespace ableton
Expand All @@ -29,7 +29,7 @@ namespace discovery

inline asio::ip::udp::endpoint multicastEndpoint()
{
return {asio::ip::address::from_string("224.76.78.75"), 20808};
return {asio::ip::address_v4::from_string("224.76.78.75"), 20808};
}

// Type tags for dispatching between unicast and multicast packets
Expand Down
110 changes: 80 additions & 30 deletions link/ableton/discovery/NetworkByteStreamSerializable.hpp
Expand Up @@ -20,9 +20,9 @@
#pragma once

#include <ableton/platforms/asio/AsioWrapper.hpp>
#if LINK_PLATFORM_MACOSX
#if defined(LINK_PLATFORM_MACOSX)
#include <ableton/platforms/darwin/Darwin.hpp>
#elif LINK_PLATFORM_LINUX
#elif defined(LINK_PLATFORM_LINUX)
#include <ableton/platforms/linux/Linux.hpp>
#endif

Expand All @@ -32,10 +32,10 @@
#include <utility>
#include <vector>

#if LINK_PLATFORM_WINDOWS
#include <WS2tcpip.h>
#include <WinSock2.h>
#include <Windows.h>
#if defined(LINK_PLATFORM_WINDOWS)
#include <ws2tcpip.h>
#include <winsock2.h>
#include <windows.h>
#endif

namespace ableton
Expand Down Expand Up @@ -80,7 +80,8 @@ struct Deserialize

// Default size implementation. Works for primitive types.

template <typename T>
template <typename T,
typename std::enable_if<std::is_fundamental<T>::value>::type* = nullptr>
std::uint32_t sizeInByteStream(T)
{
return sizeof(T);
Expand Down Expand Up @@ -235,29 +236,53 @@ struct Deserialize<int64_t>
}
};

// overloads for std::chrono durations
template <typename Rep, typename Ratio>
std::uint32_t sizeInByteStream(const std::chrono::duration<Rep, Ratio> dur)
// bool
inline std::uint32_t sizeInByteStream(bool)
{
return sizeof(uint8_t);
}

template <typename It>
It toNetworkByteStream(bool bl, It out)
{
return toNetworkByteStream(static_cast<uint8_t>(bl), std::move(out));
}

template <>
struct Deserialize<bool>
{
template <typename It>
static std::pair<bool, It> fromNetworkByteStream(It begin, It end)
{
auto result =
Deserialize<uint8_t>::fromNetworkByteStream(std::move(begin), std::move(end));
return std::make_pair(result.first != 0, result.second);
}
};

// std::chrono::microseconds
inline std::uint32_t sizeInByteStream(const std::chrono::microseconds micros)
{
return sizeInByteStream(dur.count());
return sizeInByteStream(micros.count());
}

template <typename Rep, typename Ratio, typename It>
It toNetworkByteStream(const std::chrono::duration<Rep, Ratio> dur, It out)
template <typename It>
It toNetworkByteStream(const std::chrono::microseconds micros, It out)
{
return toNetworkByteStream(dur.count(), std::move(out));
static_assert(sizeof(int64_t) == sizeof(std::chrono::microseconds::rep),
"The size of microseconds::rep must matche the size of int64_t.");
return toNetworkByteStream(static_cast<int64_t>(micros.count()), std::move(out));
}

template <typename Rep, typename Ratio>
struct Deserialize<std::chrono::duration<Rep, Ratio>>
template <>
struct Deserialize<std::chrono::microseconds>
{
template <typename It>
static std::pair<std::chrono::duration<Rep, Ratio>, It> fromNetworkByteStream(
It begin, It end)
static std::pair<std::chrono::microseconds, It> fromNetworkByteStream(It begin, It end)
{
using namespace std;
auto result = Deserialize<Rep>::fromNetworkByteStream(move(begin), move(end));
return make_pair(std::chrono::duration<Rep, Ratio>{result.first}, result.second);
auto result = Deserialize<int64_t>::fromNetworkByteStream(move(begin), move(end));
return make_pair(chrono::microseconds{result.first}, result.second);
}
};

Expand Down Expand Up @@ -305,7 +330,7 @@ BytesIt deserializeContainer(BytesIt bytesBegin,
return bytesBegin;
}

} // detail
} // namespace detail

// Need specific overloads for each container type, but use above
// utilities for common implementation
Expand Down Expand Up @@ -341,12 +366,13 @@ struct Deserialize<std::array<T, Size>>
template <typename T, typename Alloc>
std::uint32_t sizeInByteStream(const std::vector<T, Alloc>& vec)
{
return detail::containerSizeInByteStream(vec);
return sizeof(uint32_t) + detail::containerSizeInByteStream(vec);
}

template <typename T, typename Alloc, typename It>
It toNetworkByteStream(const std::vector<T, Alloc>& vec, It out)
{
out = toNetworkByteStream(static_cast<uint32_t>(vec.size()), out);
return detail::containerToNetworkByteStream(vec, std::move(out));
}

Expand All @@ -358,17 +384,42 @@ struct Deserialize<std::vector<T, Alloc>>
It bytesBegin, It bytesEnd)
{
using namespace std;
auto result_size =
Deserialize<uint32_t>::fromNetworkByteStream(move(bytesBegin), bytesEnd);
vector<T, Alloc> result;
// Use the number of bytes remaining in the stream as the upper
// bound on the number of elements that could be deserialized
// since we don't have a better heuristic.
auto resultIt = detail::deserializeContainer<T>(move(bytesBegin), move(bytesEnd),
back_inserter(result), static_cast<uint32_t>(distance(bytesBegin, bytesEnd)));

auto resultIt = detail::deserializeContainer<T>(
move(result_size.second), move(bytesEnd), back_inserter(result), result_size.first);
return make_pair(move(result), move(resultIt));
}
};

// 2-tuple
template <typename X, typename Y>
std::uint32_t sizeInByteStream(const std::tuple<X, Y>& tup)
{
return sizeInByteStream(std::get<0>(tup)) + sizeInByteStream(std::get<1>(tup));
}

template <typename X, typename Y, typename It>
It toNetworkByteStream(const std::tuple<X, Y>& tup, It out)
{
return toNetworkByteStream(
std::get<1>(tup), toNetworkByteStream(std::get<0>(tup), std::move(out)));
}

template <typename X, typename Y>
struct Deserialize<std::tuple<X, Y>>
{
template <typename It>
static std::pair<std::tuple<X, Y>, It> fromNetworkByteStream(It begin, It end)
{
using namespace std;
auto xres = Deserialize<X>::fromNetworkByteStream(begin, end);
auto yres = Deserialize<Y>::fromNetworkByteStream(xres.second, end);
return make_pair(make_tuple(move(xres.first), move(yres.first)), move(yres.second));
}
};

// 3-tuple
template <typename X, typename Y, typename Z>
std::uint32_t sizeInByteStream(const std::tuple<X, Y, Z>& tup)
Expand All @@ -395,8 +446,7 @@ struct Deserialize<std::tuple<X, Y, Z>>
auto xres = Deserialize<X>::fromNetworkByteStream(begin, end);
auto yres = Deserialize<Y>::fromNetworkByteStream(xres.second, end);
auto zres = Deserialize<Z>::fromNetworkByteStream(yres.second, end);
return make_pair(
std::tuple<X, Y, Z>{move(xres.first), move(yres.first), move(zres.first)},
return make_pair(make_tuple(move(xres.first), move(yres.first), move(zres.first)),
move(zres.second));
}
};
Expand Down
1 change: 1 addition & 0 deletions link/ableton/discovery/Payload.hpp
Expand Up @@ -21,6 +21,7 @@

#include <ableton/discovery/NetworkByteStreamSerializable.hpp>
#include <functional>
#include <sstream>
#include <unordered_map>

namespace ableton
Expand Down
10 changes: 4 additions & 6 deletions link/ableton/discovery/PeerGateway.hpp
Expand Up @@ -21,7 +21,6 @@

#include <ableton/discovery/UdpMessenger.hpp>
#include <ableton/discovery/v1/Messages.hpp>
#include <ableton/platforms/asio/AsioService.hpp>
#include <ableton/util/SafeAsyncHandler.hpp>
#include <memory>

Expand Down Expand Up @@ -216,11 +215,10 @@ PeerGateway<Messenger, PeerObserver, IoContext> makePeerGateway(

// IpV4 gateway types
template <typename StateQuery, typename IoContext>
using IpV4Messenger =
UdpMessenger<IpV4Interface<typename util::Injected<IoContext>::type&,
v1::kMaxMessageSize>,
StateQuery,
IoContext>;
using IpV4Messenger = UdpMessenger<
IpV4Interface<typename util::Injected<IoContext>::type&, v1::kMaxMessageSize>,
StateQuery,
IoContext>;

template <typename PeerObserver, typename StateQuery, typename IoContext>
using IpV4Gateway =
Expand Down
139 changes: 0 additions & 139 deletions link/ableton/discovery/Socket.hpp

This file was deleted.

2 changes: 1 addition & 1 deletion link/ableton/discovery/UdpMessenger.hpp
Expand Up @@ -169,7 +169,7 @@ class UdpMessenger
void setReceiveHandler(Handler handler)
{
mPeerStateHandler = [handler](
PeerState<NodeState> state) { handler(std::move(state)); };
PeerState<NodeState> state) { handler(std::move(state)); };

mByeByeHandler = [handler](ByeBye<NodeId> byeBye) { handler(std::move(byeBye)); };
}
Expand Down
4 changes: 2 additions & 2 deletions link/ableton/discovery/test/Interface.hpp
Expand Up @@ -42,8 +42,8 @@ class Interface
template <typename Callback, typename Tag>
void receive(Callback callback, Tag tag)
{
mCallback = [callback, tag](
const asio::ip::udp::endpoint& from, const std::vector<uint8_t>& buffer) {
mCallback = [callback, tag](const asio::ip::udp::endpoint& from,
const std::vector<uint8_t>& buffer) {
callback(tag, from, begin(buffer), end(buffer));
};
}
Expand Down
51 changes: 43 additions & 8 deletions link/ableton/discovery/test/PayloadEntries.hpp
Expand Up @@ -21,6 +21,7 @@

#include <ableton/discovery/NetworkByteStreamSerializable.hpp>
#include <cstdint>
#include <tuple>
#include <utility>

namespace ableton
Expand All @@ -35,10 +36,8 @@ namespace test
// A fixed-size entry type
struct Foo
{
enum
{
key = '_foo'
};
static const std::int32_t key = '_foo';
static_assert(key == 0x5f666f6f, "Unexpected byte order");

std::int32_t fooVal;

Expand Down Expand Up @@ -66,10 +65,8 @@ struct Foo
// A variable-size entry type
struct Bar
{
enum
{
key = '_bar'
};
static const std::int32_t key = '_bar';
static_assert(key == 0x5f626172, "Unexpected byte order");

std::vector<std::uint64_t> barVals;

Expand All @@ -93,6 +90,44 @@ struct Bar
}
};

// An entry type with two vectors
struct Foobar
{
static const std::int32_t key = 'fbar';
static_assert(key == 0x66626172, "Unexpected byte order");

using FoobarVector = std::vector<std::uint64_t>;
using FoobarTuple = std::tuple<FoobarVector, FoobarVector>;

FoobarVector fooVals;
FoobarVector barVals;

friend std::uint32_t sizeInByteStream(const Foobar& foobar)
{
return discovery::sizeInByteStream(foobar.asTuple());
}

template <typename It>
friend It toNetworkByteStream(const Foobar& foobar, It out)
{
return discovery::toNetworkByteStream(foobar.asTuple(), out);
}

template <typename It>
static std::pair<Foobar, It> fromNetworkByteStream(It begin, It end)
{
const auto result =
Deserialize<FoobarTuple>::fromNetworkByteStream(std::move(begin), std::move(end));
const auto foobar = Foobar{std::get<0>(result.first), std::get<1>(result.first)};
return std::make_pair(std::move(foobar), std::move(result.second));
}

FoobarTuple asTuple() const
{
return std::make_tuple(fooVals, barVals);
}
};

} // namespace test
} // namespace discovery
} // namespace ableton
4 changes: 3 additions & 1 deletion link/ableton/discovery/test/Socket.hpp
Expand Up @@ -53,7 +53,9 @@ class Socket
void receive(Handler handler)
{
mCallback = [handler](const asio::ip::udp::endpoint& from,
const std::vector<uint8_t>& buffer) { handler(from, begin(buffer), end(buffer)); };
const std::vector<uint8_t>& buffer) {
handler(from, begin(buffer), end(buffer));
};
}

template <typename It>
Expand Down
32 changes: 6 additions & 26 deletions link/ableton/link/CircularFifo.hpp
Expand Up @@ -19,6 +19,7 @@

#pragma once

#include <ableton/link/Optional.hpp>
#include <array>
#include <atomic>
#include <cassert>
Expand All @@ -34,12 +35,6 @@ template <typename Type, std::size_t size>
class CircularFifo
{
public:
struct PoppedItem
{
Type item;
bool valid;
};

CircularFifo()
: tail(0)
, head(0)
Expand All @@ -60,32 +55,17 @@ class CircularFifo
return false;
}

PoppedItem pop()
Optional<Type> pop()
{
const auto currentHead = head.load();
if (currentHead == tail.load())
{
return {Type{}, false};
return {};
}

auto item = data[currentHead];
head.store(nextIndex(currentHead));
return {std::move(item), true};
}

PoppedItem clearAndPopLast()
{
auto hasData = false;
auto currentHead = head.load();
while (currentHead != tail.load())
{
currentHead = nextIndex(currentHead);
hasData = true;
}

auto item = data[previousIndex(currentHead)];
head.store(currentHead);
return {std::move(item), hasData};
return Optional<Type>{std::move(item)};
}

bool isEmpty() const
Expand All @@ -109,5 +89,5 @@ class CircularFifo
std::array<Type, size + 1> data;
};

} // link
} // ableton
} // namespace link
} // namespace ableton
4 changes: 2 additions & 2 deletions link/ableton/link/ClientSessionTimelines.hpp
Expand Up @@ -111,5 +111,5 @@ inline Timeline shiftClientTimeline(Timeline client, const Beats shift)
return client;
}

} // link
} // ableton
} // namespace link
} // namespace ableton
491 changes: 376 additions & 115 deletions link/ableton/link/Controller.hpp

Large diffs are not rendered by default.

11 changes: 5 additions & 6 deletions link/ableton/link/Gateway.hpp
Expand Up @@ -38,14 +38,13 @@ class Gateway
NodeState nodeState,
GhostXForm ghostXForm,
Clock clock)
// TODO: Measurement should have an IoContext injected
: mIo(std::move(io)),
mMeasurement(addr,
: mIo(std::move(io))
, mMeasurement(addr,
nodeState.sessionId,
std::move(ghostXForm),
std::move(clock),
util::injectVal(channel(mIo->log(), "gateway@" + addr.to_string()))),
mPeerGateway(discovery::makeIpV4Gateway(util::injectRef(*mIo),
util::injectVal(mIo->clone()))
, mPeerGateway(discovery::makeIpV4Gateway(util::injectRef(*mIo),
std::move(addr),
std::move(observer),
PeerState{std::move(nodeState), mMeasurement.endpoint()}))
Expand Down Expand Up @@ -84,7 +83,7 @@ class Gateway

private:
util::Injected<IoContext> mIo;
MeasurementService<Clock, typename util::Injected<IoContext>::type::Log> mMeasurement;
MeasurementService<Clock, typename std::remove_reference<IoContext>::type> mMeasurement;
discovery::
IpV4Gateway<PeerObserver, PeerState, typename util::Injected<IoContext>::type&>
mPeerGateway;
Expand Down
99 changes: 36 additions & 63 deletions link/ableton/link/Measurement.hpp
Expand Up @@ -20,13 +20,12 @@
#pragma once

#include <ableton/discovery/Payload.hpp>
#include <ableton/discovery/Socket.hpp>
#include <ableton/link/PayloadEntries.hpp>
#include <ableton/link/PeerState.hpp>
#include <ableton/link/SessionId.hpp>
#include <ableton/link/v1/Messages.hpp>
#include <ableton/platforms/asio/AsioService.hpp>
#include <ableton/util/Injected.hpp>
#include <ableton/util/SafeAsyncHandler.hpp>
#include <chrono>
#include <memory>

Expand All @@ -35,93 +34,67 @@ namespace ableton
namespace link
{

template <typename IoService, typename Clock, typename Socket, typename Log>
template <typename Clock, typename IoContext>
struct Measurement
{
using Point = std::pair<double, double>;
using Callback = std::function<void(std::vector<Point>)>;
using Micros = std::chrono::microseconds;
using Timer = typename IoService::Timer;

static const std::size_t kNumberDataPoints = 100;
static const std::size_t kNumberMeasurements = 5;

Measurement() = default;

Measurement(const PeerState& state,
Callback callback,
asio::ip::address_v4 address,
Clock clock,
util::Injected<Log> log)
: mpIo(new IoService{})
, mpImpl(std::make_shared<Impl>(*mpIo,
std::move(state),
IoContext io)
: mIo(std::move(io))
, mpImpl(std::make_shared<Impl>(std::move(state),
std::move(callback),
std::move(address),
std::move(clock),
std::move(log)))
mIo))
{
mpImpl->listen();
}

Measurement(Measurement&& rhs)
: mpIo(std::move(rhs.mpIo))
, mpImpl(std::move(rhs.mpImpl))
{
}

~Measurement()
{
postImplDestruction();
}

Measurement& operator=(Measurement&& rhs)
{
postImplDestruction();
mpIo = std::move(rhs.mpIo);
mpImpl = std::move(rhs.mpImpl);
return *this;
}

void postImplDestruction()
{
// Post destruction of the impl object into the io thread if valid
if (mpIo)
{
mpIo->post(ImplDeleter{*this});
}
}
Measurement(const Measurement&) = delete;
Measurement& operator=(Measurement&) = delete;
Measurement(const Measurement&&) = delete;
Measurement& operator=(Measurement&&) = delete;

struct Impl : std::enable_shared_from_this<Impl>
{
Impl(IoService& io,
const PeerState& state,
using Socket = typename IoContext::template Socket<v1::kMaxMessageSize>;
using Timer = typename IoContext::Timer;
using Log = typename IoContext::Log;

Impl(const PeerState& state,
Callback callback,
asio::ip::address_v4 address,
Clock clock,
util::Injected<Log> log)
: mpSocket(std::make_shared<Socket>(io))
IoContext& io)
: mSocket(io.template openUnicastSocket<v1::kMaxMessageSize>(address))
, mSessionId(state.nodeState.sessionId)
, mEndpoint(state.endpoint)
, mCallback(std::move(callback))
, mClock(std::move(clock))
, mTimer(util::injectVal(io.makeTimer()))
, mTimer(io.makeTimer())
, mMeasurementsStarted(0)
, mLog(std::move(log))
, mLog(channel(io.log(), "Measurement on gateway@" + address.to_string()))
, mSuccess(false)
{
configureUnicastSocket(*mpSocket, address);

const auto ht = HostTime{mClock.micros()};
sendPing(mEndpoint, discovery::makePayload(ht));
resetTimer();
}

void resetTimer()
{
mTimer->cancel();
mTimer->expires_from_now(std::chrono::milliseconds(50));
mTimer->async_wait([this](const typename Timer::ErrorCode e) {
mTimer.cancel();
mTimer.expires_from_now(std::chrono::milliseconds(50));
mTimer.async_wait([this](const typename Timer::ErrorCode e) {
if (!e)
{
if (mMeasurementsStarted < kNumberMeasurements)
Expand All @@ -141,7 +114,7 @@ struct Measurement

void listen()
{
mpSocket->receive(util::makeAsyncSafe(this->shared_from_this()));
mSocket.receive(util::makeAsyncSafe(this->shared_from_this()));
}

// Operator to handle incoming messages on the interface
Expand All @@ -156,7 +129,7 @@ struct Measurement

if (header.messageType == v1::kPong)
{
debug(*mLog) << "Received Pong message from " << from;
debug(mLog) << "Received Pong message from " << from;

// parse for all entries
SessionId sessionId{};
Expand All @@ -175,7 +148,7 @@ struct Measurement
}
catch (const std::runtime_error& err)
{
warning(*mLog) << "Failed parsing payload, caught exception: " << err.what();
warning(mLog) << "Failed parsing payload, caught exception: " << err.what();
listen();
return;
}
Expand Down Expand Up @@ -215,7 +188,7 @@ struct Measurement
}
else
{
debug(*mLog) << "Received invalid message from " << from;
debug(mLog) << "Received invalid message from " << from;
listen();
}
}
Expand All @@ -230,40 +203,40 @@ struct Measurement

try
{
mpSocket->send(buffer.data(), numBytes, to);
mSocket.send(buffer.data(), numBytes, to);
}
catch (const std::runtime_error& err)
{
info(*mLog) << "Failed to send Ping to " << to.address().to_string() << ": "
<< err.what();
info(mLog) << "Failed to send Ping to " << to.address().to_string() << ": "
<< err.what();
}
}

void finish()
{
mTimer->cancel();
mTimer.cancel();
mCallback(std::move(mData));
mData = {};
mSuccess = true;
debug(*mLog) << "Measuring " << mEndpoint << " done.";
debug(mLog) << "Measuring " << mEndpoint << " done.";
}

void fail()
{
mCallback(std::vector<Point>{});
mData = {};
debug(*mLog) << "Measuring " << mEndpoint << " failed.";
debug(mLog) << "Measuring " << mEndpoint << " failed.";
}

std::shared_ptr<Socket> mpSocket;
Socket mSocket;
SessionId mSessionId;
asio::ip::udp::endpoint mEndpoint;
std::vector<std::pair<double, double>> mData;
Callback mCallback;
Clock mClock;
util::Injected<typename IoService::Timer> mTimer;
Timer mTimer;
std::size_t mMeasurementsStarted;
util::Injected<Log> mLog;
Log mLog;
bool mSuccess;
};

Expand All @@ -288,7 +261,7 @@ struct Measurement
std::shared_ptr<Impl> mpImpl;
};

std::unique_ptr<IoService> mpIo;
IoContext mIo;
std::shared_ptr<Impl> mpImpl;
};

Expand Down
6 changes: 2 additions & 4 deletions link/ableton/link/MeasurementEndpointV4.hpp
Expand Up @@ -29,10 +29,8 @@ namespace link

struct MeasurementEndpointV4
{
enum
{
key = 'mep4'
};
static const std::int32_t key = 'mep4';
static_assert(key == 0x6d657034, "Unexpected byte order");

// Model the NetworkByteStreamSerializable concept
friend std::uint32_t sizeInByteStream(const MeasurementEndpointV4 mep)
Expand Down
59 changes: 23 additions & 36 deletions link/ableton/link/MeasurementService.hpp
Expand Up @@ -19,7 +19,6 @@

#pragma once

#include <ableton/discovery/Socket.hpp>
#include <ableton/link/GhostXForm.hpp>
#include <ableton/link/Kalman.hpp>
#include <ableton/link/LinearRegression.hpp>
Expand All @@ -28,48 +27,34 @@
#include <ableton/link/PingResponder.hpp>
#include <ableton/link/SessionId.hpp>
#include <ableton/link/v1/Messages.hpp>
#include <ableton/platforms/asio/AsioService.hpp>
#include <ableton/util/Log.hpp>
#include <map>
#include <memory>
#include <thread>

namespace ableton
{
namespace link
{

template <typename Clock, typename Log>
template <typename Clock, typename IoContext>
class MeasurementService
{
public:
using IoType = util::Injected<IoContext>;
using Point = std::pair<double, double>;

using MeasurementInstance = Measurement<platforms::asio::AsioService,
Clock,
discovery::Socket<v1::kMaxMessageSize>,
Log>;

using MeasurementServicePingResponder = PingResponder<platforms::asio::AsioService&,
Clock,
discovery::Socket<v1::kMaxMessageSize>,
Log>;

static const std::size_t kNumberThreads = 1;
using MeasurementInstance = Measurement<Clock, IoContext>;

MeasurementService(asio::ip::address_v4 address,
SessionId sessionId,
GhostXForm ghostXForm,
Clock clock,
util::Injected<Log> log)
IoType io)
: mClock(std::move(clock))
, mLog(std::move(log))
, mIo(std::move(io))
, mPingResponder(std::move(address),
std::move(sessionId),
std::move(ghostXForm),
util::injectRef(mIo),
mClock,
mLog)
util::injectRef(*mIo))
{
}

Expand All @@ -78,10 +63,10 @@ class MeasurementService

~MeasurementService()
{
// Clear the measurement map in the io service so that whatever
// Clear the measurement map in the IoContext so that whatever
// cleanup code executes in response to the destruction of the
// measurement objects still have access to the io service
mIo.post([this] { mMeasurementMap.clear(); });
// measurement objects still have access to the IoContext.
mIo->async([this] { mMeasurementMap.clear(); });
}

void updateNodeState(const SessionId& sessionId, const GhostXForm& xform)
Expand All @@ -100,19 +85,22 @@ class MeasurementService
{
using namespace std;

mIo.post([this, state, handler] {
mIo->async([this, state, handler] {
const auto nodeId = state.nodeState.nodeId;
auto addr = mPingResponder.endpoint().address().to_v4();
auto callback = CompletionCallback<Handler>{*this, nodeId, handler};

try
{

mMeasurementMap[nodeId] =
MeasurementInstance{state, move(callback), move(addr), mClock, mLog};
std::unique_ptr<MeasurementInstance>(new MeasurementInstance{
state, move(callback), move(addr), mClock, mIo->clone()});
}
catch (const runtime_error& err)
{
info(*mLog) << "Failed to measure. Reason: " << err.what();
info(mIo->log()) << "gateway@" + addr.to_string()
<< " Failed to measure. Reason: " << err.what();
handler(GhostXForm{});
}
});
Expand Down Expand Up @@ -142,14 +130,14 @@ class MeasurementService
using namespace std;
using std::chrono::microseconds;

// Post this to the measurement service's io service so that we
// Post this to the measurement service's IoContext so that we
// don't delete the measurement object in its stack. Capture all
// needed data separately from this, since this object may be
// gone by the time the block gets executed.
auto nodeId = mNodeId;
auto handler = mHandler;
auto& measurementMap = mService.mMeasurementMap;
mService.mIo.post([nodeId, handler, &measurementMap, data] {
auto& measurementMap = mMeasurementService.mMeasurementMap;
mMeasurementService.mIo->async([nodeId, handler, &measurementMap, data] {
const auto it = measurementMap.find(nodeId);
if (it != measurementMap.end())
{
Expand All @@ -166,20 +154,19 @@ class MeasurementService
});
}

MeasurementService& mService;
MeasurementService& mMeasurementService;
NodeId mNodeId;
Handler mHandler;
};

// Make sure the measurement map outlives the io service so that the rest of
// Make sure the measurement map outlives the IoContext so that the rest of
// the members are guaranteed to be valid when any final handlers
// are begin run.
using MeasurementMap = std::map<NodeId, MeasurementInstance>;
using MeasurementMap = std::map<NodeId, std::unique_ptr<MeasurementInstance>>;
MeasurementMap mMeasurementMap;
Clock mClock;
util::Injected<Log> mLog;
platforms::asio::AsioService mIo;
MeasurementServicePingResponder mPingResponder;
IoType mIo;
PingResponder<Clock, IoContext> mPingResponder;
};

} // namespace link
Expand Down
27 changes: 17 additions & 10 deletions link/ableton/link/NodeState.hpp
Expand Up @@ -22,6 +22,7 @@
#include <ableton/discovery/Payload.hpp>
#include <ableton/link/NodeId.hpp>
#include <ableton/link/SessionId.hpp>
#include <ableton/link/StartStopState.hpp>
#include <ableton/link/Timeline.hpp>

namespace ableton
Expand All @@ -31,7 +32,8 @@ namespace link

struct NodeState
{
using Payload = decltype(discovery::makePayload(Timeline{}, SessionMembership{}));
using Payload =
decltype(discovery::makePayload(Timeline{}, SessionMembership{}, StartStopState{}));

NodeId ident() const
{
Expand All @@ -40,29 +42,34 @@ struct NodeState

friend bool operator==(const NodeState& lhs, const NodeState& rhs)
{
return std::tie(lhs.nodeId, lhs.sessionId, lhs.timeline)
== std::tie(rhs.nodeId, rhs.sessionId, rhs.timeline);
return std::tie(lhs.nodeId, lhs.sessionId, lhs.timeline, lhs.startStopState)
== std::tie(rhs.nodeId, rhs.sessionId, rhs.timeline, rhs.startStopState);
}

friend Payload toPayload(const NodeState& state)
{
return discovery::makePayload(state.timeline, SessionMembership{state.sessionId});
return discovery::makePayload(
state.timeline, SessionMembership{state.sessionId}, state.startStopState);
}

template <typename It>
static NodeState fromPayload(NodeId id, It begin, It end)
static NodeState fromPayload(NodeId nodeId, It begin, It end)
{
using namespace std;
auto state = NodeState{move(id), {}, {}};
discovery::parsePayload<Timeline, SessionMembership>(move(begin), move(end),
[&state](Timeline tl) { state.timeline = move(tl); },
[&state](SessionMembership sm) { state.sessionId = move(sm.sessionId); });
return state;
auto nodeState = NodeState{move(nodeId), {}, {}, {}};
discovery::parsePayload<Timeline, SessionMembership, StartStopState>(move(begin),
move(end), [&nodeState](Timeline tl) { nodeState.timeline = move(tl); },
[&nodeState](SessionMembership membership) {
nodeState.sessionId = move(membership.sessionId);
},
[&nodeState](StartStopState ststst) { nodeState.startStopState = move(ststst); });
return nodeState;
}

NodeId nodeId;
SessionId sessionId;
Timeline timeline;
StartStopState startStopState;
};

} // namespace link
Expand Down
97 changes: 97 additions & 0 deletions link/ableton/link/Optional.hpp
@@ -0,0 +1,97 @@
/* Copyright 2017, 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 <cassert>
#include <utility>

namespace ableton
{
namespace link
{

// Subset of the C++ 17 std::optional API. T has to be default constructible.
template <typename T>
struct Optional
{
Optional()
: mHasValue(false)
{
}

explicit Optional(T value)
: mValue(std::move(value))
, mHasValue(true)
{
}

Optional(const Optional&) = default;

Optional(Optional&& other)
: mValue(std::move(other.mValue))
, mHasValue(other.mHasValue)
{
}

Optional& operator=(const Optional&) = default;

Optional& operator=(Optional&& other)
{
mValue = std::move(other.mValue);
mHasValue = other.mHasValue;
return *this;
}

explicit operator bool() const
{
return mHasValue;
}

const T& operator*() const
{
assert(mHasValue);
return mValue;
}

T& operator*()
{
assert(mHasValue);
return mValue;
}

const T* operator->() const
{
assert(mHasValue);
return &mValue;
}

T* operator->()
{
assert(mHasValue);
return &mValue;
}

private:
T mValue;
bool mHasValue;
};

} // namespace link
} // namespace ableton
20 changes: 7 additions & 13 deletions link/ableton/link/PayloadEntries.hpp
Expand Up @@ -30,10 +30,8 @@ namespace link

struct HostTime
{
enum
{
key = '__ht'
};
static const std::int32_t key = '__ht';
static_assert(key == 0x5f5f6874, "Unexpected byte order");

HostTime() = default;

Expand Down Expand Up @@ -66,12 +64,10 @@ struct HostTime
std::chrono::microseconds time;
};

struct GHostTime : HostTime
struct GHostTime
{
enum
{
key = '__gt'
};
static const std::int32_t key = '__gt';
static_assert(key == 0x5f5f6774, "Unexpected byte order");

GHostTime() = default;

Expand Down Expand Up @@ -106,10 +102,8 @@ struct GHostTime : HostTime

struct PrevGHostTime
{
enum
{
key = '_pgt'
};
static const std::int32_t key = '_pgt';
static_assert(key == 0x5f706774, "Unexpected byte order");

PrevGHostTime() = default;

Expand Down
5 changes: 5 additions & 0 deletions link/ableton/link/PeerState.hpp
Expand Up @@ -51,6 +51,11 @@ struct PeerState
return nodeState.timeline;
}

StartStopState startStopState() const
{
return nodeState.startStopState;
}

friend bool operator==(const PeerState& lhs, const PeerState& rhs)
{
return lhs.nodeState == rhs.nodeState && lhs.endpoint == rhs.endpoint;
Expand Down
66 changes: 54 additions & 12 deletions link/ableton/link/Peers.hpp
Expand Up @@ -33,10 +33,14 @@ namespace link
//
// SessionTimelineCallback is invoked with a session id and a timeline
// whenever a new combination of these values is seen
//
// SessionStartStopStateCallback is invoked with a session id and a startStopState
// whenever a new combination of these values is seen

template <typename IoContext,
typename SessionMembershipCallback,
typename SessionTimelineCallback>
typename SessionTimelineCallback,
typename SessionStartStopStateCallback>
class Peers
{
// non-movable private implementation type
Expand All @@ -47,9 +51,10 @@ class Peers

Peers(util::Injected<IoContext> io,
SessionMembershipCallback membership,
SessionTimelineCallback timeline)
: mpImpl(
std::make_shared<Impl>(std::move(io), std::move(membership), std::move(timeline)))
SessionTimelineCallback timeline,
SessionStartStopStateCallback startStop)
: mpImpl(std::make_shared<Impl>(
std::move(io), std::move(membership), std::move(timeline), std::move(startStop)))
{
}

Expand Down Expand Up @@ -196,10 +201,12 @@ class Peers
{
Impl(util::Injected<IoContext> io,
SessionMembershipCallback membership,
SessionTimelineCallback timeline)
SessionTimelineCallback timeline,
SessionStartStopStateCallback startStop)
: mIo(std::move(io))
, mSessionMembershipCallback(std::move(membership))
, mSessionTimelineCallback(std::move(timeline))
, mSessionStartStopStateCallback(std::move(startStop))
{
}

Expand All @@ -209,10 +216,14 @@ class Peers

const auto peerSession = peerState.sessionId();
const auto peerTimeline = peerState.timeline();
const auto peerStartStopState = peerState.startStopState();
bool isNewSessionTimeline = false;
bool isNewSessionStartStopState = false;
bool didSessionMembershipChange = false;
{
isNewSessionTimeline = !sessionTimelineExists(peerSession, peerTimeline);
isNewSessionStartStopState =
!sessionStartStopStateExists(peerSession, peerStartStopState);

auto peer = make_pair(move(peerState), move(gatewayAddr));
const auto idRange = equal_range(begin(mPeers), end(mPeers), peer, PeerIdComp{});
Expand Down Expand Up @@ -254,6 +265,15 @@ class Peers
mSessionTimelineCallback(peerSession, peerTimeline);
}

// Pass the start stop state to the Controller after it processed the timeline.
// A new timeline can cause a session Id change which will prevent processing the
// new start stop state. By handling the start stop state after the timeline we
// assure that the start stop state is processed with the correct session Id.
if (isNewSessionStartStopState)
{
mSessionStartStopStateCallback(peerSession, peerStartStopState);
}

if (didSessionMembershipChange)
{
mSessionMembershipCallback();
Expand Down Expand Up @@ -297,14 +317,29 @@ class Peers
mSessionMembershipCallback();
}

bool sessionTimelineExists(const SessionId& session, const Timeline& tl)
template <typename Predicate>
bool hasPeerWith(const SessionId& sessionId, Predicate predicate)
{
using namespace std;
return find_if(begin(mPeers), end(mPeers), [&](const Peer& peer) {
return peer.first.sessionId() == session && peer.first.timeline() == tl;
return peer.first.sessionId() == sessionId && predicate(peer.first);
}) != end(mPeers);
}

bool sessionTimelineExists(const SessionId& session, const Timeline& timeline)
{
return hasPeerWith(session,
[&](const PeerState& peerState) { return peerState.timeline() == timeline; });
}

bool sessionStartStopStateExists(
const SessionId& sessionId, const StartStopState& startStopState)
{
return hasPeerWith(sessionId, [&](const PeerState& peerState) {
return peerState.startStopState() == startStopState;
});
}

struct PeerIdComp
{
bool operator()(const Peer& lhs, const Peer& rhs) const
Expand All @@ -324,6 +359,7 @@ class Peers
util::Injected<IoContext> mIo;
SessionMembershipCallback mSessionMembershipCallback;
SessionTimelineCallback mSessionTimelineCallback;
SessionStartStopStateCallback mSessionStartStopStateCallback;
std::vector<Peer> mPeers; // sorted by peerId, unique by (peerId, addr)
};

Expand All @@ -342,13 +378,19 @@ class Peers

template <typename Io,
typename SessionMembershipCallback,
typename SessionTimelineCallback>
Peers<Io, SessionMembershipCallback, SessionTimelineCallback> makePeers(
util::Injected<Io> io,
typename SessionTimelineCallback,
typename SessionStartStopStateCallback>
Peers<Io,
SessionMembershipCallback,
SessionTimelineCallback,
SessionStartStopStateCallback>
makePeers(util::Injected<Io> io,
SessionMembershipCallback membershipCallback,
SessionTimelineCallback timelineCallback)
SessionTimelineCallback timelineCallback,
SessionStartStopStateCallback startStopStateCallback)
{
return {std::move(io), std::move(membershipCallback), std::move(timelineCallback)};
return {std::move(io), std::move(membershipCallback), std::move(timelineCallback),
std::move(startStopStateCallback)};
}

} // namespace link
Expand Down
4 changes: 2 additions & 2 deletions link/ableton/link/Phase.hpp
Expand Up @@ -96,5 +96,5 @@ inline std::chrono::microseconds fromPhaseEncodedBeats(
return tl.fromBeats(tl.beatOrigin + originOffset + quantum - inversePhaseOffset);
}

} // link
} // ableton
} // namespace link
} // namespace ableton
43 changes: 20 additions & 23 deletions link/ableton/link/PingResponder.hpp
Expand Up @@ -23,35 +23,34 @@
#include <ableton/link/PayloadEntries.hpp>
#include <ableton/link/SessionId.hpp>
#include <ableton/link/v1/Messages.hpp>
#include <ableton/platforms/asio/AsioWrapper.hpp>
#include <ableton/util/Injected.hpp>
#include <ableton/util/SafeAsyncHandler.hpp>
#include <chrono>
#include <memory>
#include <thread>

namespace ableton
{
namespace link
{

template <typename Io, typename Clock, typename Socket, typename Log>
template <typename Clock, typename IoContext>
class PingResponder
{
using IoType = util::Injected<IoContext&>;
using Socket = typename IoType::type::template Socket<v1::kMaxMessageSize>;

public:
PingResponder(asio::ip::address_v4 address,
SessionId sessionId,
GhostXForm ghostXForm,
util::Injected<Io> io,
Clock clock,
util::Injected<Log> log)
: mIo(std::move(io))
, mpImpl(std::make_shared<Impl>(*mIo,
std::move(address),
IoType io)
: mIo(io)
, mpImpl(std::make_shared<Impl>(std::move(address),
std::move(sessionId),
std::move(ghostXForm),
std::move(clock),
std::move(log)))
std::move(io)))
{
mpImpl->listen();
}
Expand All @@ -61,16 +60,16 @@ class PingResponder

~PingResponder()
{
// post the release of the impl object into the io service so that
// post the release of the impl object into the IoContext so that
// it happens in the same thread as its handlers
auto pImpl = mpImpl;
mIo->post([pImpl]() mutable { pImpl.reset(); });
mIo->async([pImpl]() mutable { pImpl.reset(); });
}

void updateNodeState(const SessionId& sessionId, const GhostXForm& xform)
{
auto pImpl = mpImpl;
mIo->post([pImpl, sessionId, xform] {
mIo->async([pImpl, sessionId, xform] {
pImpl->mSessionId = std::move(sessionId);
pImpl->mGhostXForm = std::move(xform);
});
Expand All @@ -94,19 +93,17 @@ class PingResponder
private:
struct Impl : std::enable_shared_from_this<Impl>
{
Impl(typename util::Injected<Io>::type& io,
asio::ip::address_v4 address,
Impl(asio::ip::address_v4 address,
SessionId sessionId,
GhostXForm ghostXForm,
Clock clock,
util::Injected<Log> log)
IoType io)
: mSessionId(std::move(sessionId))
, mGhostXForm(std::move(ghostXForm))
, mClock(std::move(clock))
, mLog(std::move(log))
, mSocket(io)
, mLog(channel(io->log(), "gateway@" + address.to_string()))
, mSocket(io->template openUnicastSocket<v1::kMaxMessageSize>(address))
{
configureUnicastSocket(mSocket, address);
}

void listen()
Expand All @@ -131,20 +128,20 @@ class PingResponder
sizeInByteStream(makePayload(HostTime{}, PrevGHostTime{}));
if (header.messageType == v1::kPing && payloadSize <= maxPayloadSize)
{
debug(*mLog) << "Received ping message from " << from;
debug(mLog) << " Received ping message from " << from;

try
{
reply(std::move(payloadBegin), std::move(end), from);
}
catch (const std::runtime_error& err)
{
info(*mLog) << "Failed to send pong to " << from << ". Reason: " << err.what();
info(mLog) << " Failed to send pong to " << from << ". Reason: " << err.what();
}
}
else
{
info(*mLog) << "Received invalid Message from " << from << ".";
info(mLog) << " Received invalid Message from " << from << ".";
}
listen();
}
Expand Down Expand Up @@ -173,11 +170,11 @@ class PingResponder
SessionId mSessionId;
GhostXForm mGhostXForm;
Clock mClock;
util::Injected<Log> mLog;
typename IoType::type::Log mLog;
Socket mSocket;
};

util::Injected<Io> mIo;
IoType mIo;
std::shared_ptr<Impl> mpImpl;
};

Expand Down
6 changes: 2 additions & 4 deletions link/ableton/link/SessionId.hpp
Expand Up @@ -33,10 +33,8 @@ using SessionId = NodeId;
// A payload entry indicating membership in a particular session
struct SessionMembership
{
enum
{
key = 'sess'
};
static const std::int32_t key = 'sess';
static_assert(key == 0x73657373, "Unexpected byte order");

// Model the NetworkByteStreamSerializable concept
friend std::uint32_t sizeInByteStream(const SessionMembership& sm)
Expand Down
80 changes: 80 additions & 0 deletions link/ableton/link/SessionState.hpp
@@ -0,0 +1,80 @@
/* Copyright 2017, 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/link/Optional.hpp>
#include <ableton/link/StartStopState.hpp>
#include <ableton/link/Timeline.hpp>

namespace ableton
{
namespace link
{

using OptionalTimeline = Optional<Timeline>;
using OptionalStartStopState = Optional<StartStopState>;

struct SessionState
{
Timeline timeline;
StartStopState startStopState;
GhostXForm ghostXForm;
};

struct ClientState
{
friend bool operator==(const ClientState& lhs, const ClientState& rhs)
{
return std::tie(lhs.timeline, lhs.startStopState)
== std::tie(rhs.timeline, rhs.startStopState);
}

friend bool operator!=(const ClientState& lhs, const ClientState& rhs)
{
return !(lhs == rhs);
}

Timeline timeline;
StartStopState startStopState;
};

struct RtClientState
{
Timeline timeline;
StartStopState startStopState;
std::chrono::microseconds timelineTimestamp;
std::chrono::microseconds startStopStateTimestamp;
};

struct IncomingClientState
{
OptionalTimeline timeline;
OptionalStartStopState startStopState;
std::chrono::microseconds timelineTimestamp;
};

struct ApiState
{
Timeline timeline;
ApiStartStopState startStopState;
};

} // namespace link
} // namespace ableton
5 changes: 3 additions & 2 deletions link/ableton/link/Sessions.hpp
Expand Up @@ -159,8 +159,9 @@ class Sessions
range.first->measurement = move(measurement);
// If session times too close - fall back to session id order
const auto ghostDiff = newGhost - curGhost;
if (ghostDiff > SESSION_EPS || (std::abs(ghostDiff.count()) < SESSION_EPS.count()
&& id < mCurrent.sessionId))
if (ghostDiff > SESSION_EPS
|| (std::abs(ghostDiff.count()) < SESSION_EPS.count()
&& id < mCurrent.sessionId))
{
// The new session wins, switch over to it
auto current = mCurrent;
Expand Down
123 changes: 123 additions & 0 deletions link/ableton/link/StartStopState.hpp
@@ -0,0 +1,123 @@
/* Copyright 2017, 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/NetworkByteStreamSerializable.hpp>
#include <ableton/link/Beats.hpp>
#include <chrono>
#include <tuple>

namespace ableton
{
namespace link
{

// A tuple of (isPlaying, time) that represents the playing state
// with an according timestamp in microseconds. It also serves as a
// payload entry.

struct StartStopState
{
static const std::int32_t key = 'stst';
static_assert(key == 0x73747374, "Unexpected byte order");

using StartStopStateTuple = std::tuple<bool, Beats, std::chrono::microseconds>;

StartStopState() = default;

StartStopState(
const bool aIsPlaying, const Beats aBeats, const std::chrono::microseconds aTimestamp)
: isPlaying(aIsPlaying)
, beats(aBeats)
, timestamp(aTimestamp)
{
}

friend bool operator==(const StartStopState& lhs, const StartStopState& rhs)
{
return std::tie(lhs.isPlaying, lhs.beats, lhs.timestamp)
== std::tie(rhs.isPlaying, rhs.beats, rhs.timestamp);
}

friend bool operator!=(const StartStopState& lhs, const StartStopState& rhs)
{
return !(lhs == rhs);
}

// Model the NetworkByteStreamSerializable concept
friend std::uint32_t sizeInByteStream(const StartStopState& state)
{
return discovery::sizeInByteStream(state.asTuple());
}

template <typename It>
friend It toNetworkByteStream(const StartStopState& state, It out)
{
return discovery::toNetworkByteStream(state.asTuple(), std::move(out));
}

template <typename It>
static std::pair<StartStopState, It> fromNetworkByteStream(It begin, It end)
{
using namespace std;
using namespace discovery;
auto result =
Deserialize<StartStopStateTuple>::fromNetworkByteStream(move(begin), move(end));
auto state =
StartStopState{get<0>(result.first), get<1>(result.first), get<2>(result.first)};
return make_pair(move(state), move(result.second));
}

bool isPlaying{false};
Beats beats{0.};
std::chrono::microseconds timestamp{0};

private:
StartStopStateTuple asTuple() const
{
return std::make_tuple(isPlaying, beats, timestamp);
}
};

struct ApiStartStopState
{
ApiStartStopState() = default;

ApiStartStopState(const bool aIsPlaying, const std::chrono::microseconds aTime)
: isPlaying(aIsPlaying)
, time(aTime)
{
}

friend bool operator==(const ApiStartStopState& lhs, const ApiStartStopState& rhs)
{
return std::tie(lhs.isPlaying, lhs.time) == std::tie(rhs.isPlaying, rhs.time);
}

friend bool operator!=(const ApiStartStopState& lhs, const ApiStartStopState& rhs)
{
return !(lhs == rhs);
}

bool isPlaying{false};
std::chrono::microseconds time{0};
};
} // namespace link
} // namespace ableton
6 changes: 2 additions & 4 deletions link/ableton/link/Timeline.hpp
Expand Up @@ -38,10 +38,8 @@ namespace link

struct Timeline
{
enum
{
key = 'tmln'
};
static const std::int32_t key = 'tmln';
static_assert(key == 0x746d6c6e, "Unexpected byte order");

Beats toBeats(const std::chrono::microseconds time) const
{
Expand Down
25 changes: 11 additions & 14 deletions link/ableton/platforms/Config.hpp
Expand Up @@ -22,15 +22,15 @@
#include <ableton/link/Controller.hpp>
#include <ableton/util/Log.hpp>

#if LINK_PLATFORM_WINDOWS
#if defined(LINK_PLATFORM_WINDOWS)
#include <ableton/platforms/asio/Context.hpp>
#include <ableton/platforms/windows/Clock.hpp>
#include <ableton/platforms/windows/ScanIpIfAddrs.hpp>
#elif LINK_PLATFORM_MACOSX
#elif defined(LINK_PLATFORM_MACOSX)
#include <ableton/platforms/asio/Context.hpp>
#include <ableton/platforms/darwin/Clock.hpp>
#include <ableton/platforms/posix/ScanIpIfAddrs.hpp>
#elif LINK_PLATFORM_LINUX
#elif defined(LINK_PLATFORM_LINUX)
#include <ableton/platforms/asio/Context.hpp>
#include <ableton/platforms/linux/Clock.hpp>
#include <ableton/platforms/posix/ScanIpIfAddrs.hpp>
Expand All @@ -43,28 +43,25 @@ namespace link
namespace platform
{

#if LINK_PLATFORM_WINDOWS
#if defined(LINK_PLATFORM_WINDOWS)
using Clock = platforms::windows::Clock;
using IoContext =
platforms::asio::Context<platforms::windows::ScanIpIfAddrs, util::NullLog>;

#elif LINK_PLATFORM_MACOSX
#elif defined(LINK_PLATFORM_MACOSX)
using Clock = platforms::darwin::Clock;
using IoContext =
platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>;

#elif LINK_PLATFORM_LINUX
#ifdef __ARM_ARCH_7A__
using Clock = platforms::linux::ClockMonotonicRaw;
#else
#elif defined(LINK_PLATFORM_LINUX)
using Clock = platforms::linux::ClockMonotonic;
#endif
using IoContext =
platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>;
#endif

using Controller = Controller<PeerCountCallback, TempoCallback, Clock, IoContext>;
using Controller =
Controller<PeerCountCallback, TempoCallback, StartStopStateCallback, Clock, IoContext>;

} // platform
} // link
} // ableton
} // namespace platform
} // namespace link
} // namespace ableton
105 changes: 0 additions & 105 deletions link/ableton/platforms/asio/AsioService.hpp

This file was deleted.

31 changes: 19 additions & 12 deletions link/ableton/platforms/asio/AsioWrapper.hpp
Expand Up @@ -32,35 +32,41 @@
#pragma push_macro("ASIO_NO_TYPEID")
#define ASIO_NO_TYPEID 1

#if LINK_PLATFORM_WINDOWS
#if defined(LINK_PLATFORM_WINDOWS)
#pragma push_macro("INCL_EXTRA_HTON_FUNCTIONS")
#define INCL_EXTRA_HTON_FUNCTIONS 1
#endif

#if defined(WIN32) || defined(_WIN32)
#if !defined(_WIN32_WINNT)
#define _WIN32_WINNT 0x0501
#endif
#endif

#if defined(__clang__)
#pragma clang diagnostic push
#if __has_warning("-Wcomma")
#pragma clang diagnostic ignored "-Wcomma"
#endif
#if __has_warning("-Wshorten-64-to-32")
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
#endif
#if __has_warning("-Wunused-local-typedef")
#pragma clang diagnostic ignored "-Wunused-local-typedef"
#endif
#endif

#if defined(_MSC_VER)
#pragma warning(push)
// C4191: 'operator/operation': unsafe conversion from 'type of expression' to
// 'type required'
#pragma warning(disable : 4191)
// C4548: expression before comma has no effect; expected expression with side-effect
#pragma warning(disable : 4548)
// C4619: #pragma warning : there is no warning number 'number'
#pragma warning(disable : 4619)
// C4675: 'function' : resolved overload was found by argument-dependent lookup
#pragma warning(disable : 4675)
#define _SCL_SECURE_NO_WARNINGS 1
#pragma warning(push, 0)
#pragma warning(disable : 4242)
#pragma warning(disable : 4702)
#endif

#include <asio.hpp>
#include <asio/system_timer.hpp>

#if LINK_PLATFORM_WINDOWS
#if defined(LINK_PLATFORM_WINDOWS)
#pragma pop_macro("INCL_EXTRA_HTON_FUNCTIONS")
#endif

Expand All @@ -69,6 +75,7 @@

#if defined(_MSC_VER)
#pragma warning(pop)
#undef _SCL_SECURE_NO_WARNINGS
#endif

#if defined(__clang__)
Expand Down
4 changes: 2 additions & 2 deletions link/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp
Expand Up @@ -85,5 +85,5 @@ class LockFreeCallbackDispatcher
std::thread mThread;
};

} // platforms
} // ableton
} // namespace platforms
} // namespace ableton
6 changes: 3 additions & 3 deletions link/ableton/platforms/asio/Util.hpp
Expand Up @@ -38,6 +38,6 @@ AsioAddrType makeAddress(const char* pAddr)
return AsioAddrType{bytes};
}

} // asio
} // platforms
} // ableton
} // namespace asio
} // namespace platforms
} // namespace ableton
8 changes: 0 additions & 8 deletions link/ableton/platforms/linux/Clock.hpp
Expand Up @@ -51,14 +51,6 @@ class Clock
using ClockMonotonic = Clock<CLOCK_MONOTONIC>;
using ClockMonotonicRaw = Clock<CLOCK_MONOTONIC_RAW>;

/* Warning: the realtime clock can be subject to time change.
* One of the hard requirements of Ableton Link is that the clock must be
* monotonic.
* Only use the Realtime Clock if you can't use the monotonic ones, and
* beware that things might go wrong if time jumps.
*/
using ClockRealtime = Clock<CLOCK_REALTIME>;

} // namespace linux
} // namespace platforms
} // namespace ableton
2 changes: 1 addition & 1 deletion link/ableton/platforms/posix/ScanIpIfAddrs.hpp
Expand Up @@ -67,7 +67,7 @@ class GetIfAddrs
struct ifaddrs* interfaces = NULL;
};

} // detail
} // namespace detail


// Posix implementation of ip interface address scanner
Expand Down
2 changes: 1 addition & 1 deletion link/ableton/platforms/windows/ScanIpIfAddrs.hpp
Expand Up @@ -93,7 +93,7 @@ class GetIfAddrs
IP_ADAPTER_ADDRESSES* adapter;
};

} // detail
} // namespace detail

struct ScanIpIfAddrs
{
Expand Down
9 changes: 3 additions & 6 deletions link/ableton/test/CatchWrapper.hpp
Expand Up @@ -26,18 +26,15 @@
* which are specific to that library.
*/

// Visual Studio
#if defined(_MSC_VER)
#pragma warning(push)
// C4388: signed/unsigned mismatch
#pragma warning(disable : 4388)
// C4702: unreachable code
#pragma warning(push, 0)
#pragma warning(disable : 4242)
#pragma warning(disable : 4244)
#pragma warning(disable : 4702)
#endif

#include <catch.hpp>

// Visual Studio
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
83 changes: 0 additions & 83 deletions link/ableton/test/serial_io/Socket.hpp

This file was deleted.

4 changes: 2 additions & 2 deletions link/ableton/util/SampleTiming.hpp
Expand Up @@ -48,5 +48,5 @@ struct SampleTiming
double mSampleRate;
};

} // util
} // ableton
} // namespace util
} // namespace ableton
37 changes: 20 additions & 17 deletions link/asio.hpp
Expand Up @@ -2,7 +2,7 @@
// asio.hpp
// ~~~~~~~~
//
// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com)
// Copyright (c) 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand All @@ -18,13 +18,15 @@
#include "asio/associated_allocator.hpp"
#include "asio/associated_executor.hpp"
#include "asio/async_result.hpp"
#include "asio/awaitable.hpp"
#include "asio/basic_datagram_socket.hpp"
#include "asio/basic_deadline_timer.hpp"
#include "asio/basic_io_object.hpp"
#include "asio/basic_raw_socket.hpp"
#include "asio/basic_seq_packet_socket.hpp"
#include "asio/basic_serial_port.hpp"
#include "asio/basic_signal_set.hpp"
#include "asio/basic_socket.hpp"
#include "asio/basic_socket_acceptor.hpp"
#include "asio/basic_socket_iostream.hpp"
#include "asio/basic_socket_streambuf.hpp"
Expand All @@ -40,13 +42,14 @@
#include "asio/buffered_write_stream_fwd.hpp"
#include "asio/buffered_write_stream.hpp"
#include "asio/buffers_iterator.hpp"
#include "asio/co_spawn.hpp"
#include "asio/completion_condition.hpp"
#include "asio/compose.hpp"
#include "asio/connect.hpp"
#include "asio/coroutine.hpp"
#include "asio/datagram_socket_service.hpp"
#include "asio/deadline_timer_service.hpp"
#include "asio/deadline_timer.hpp"
#include "asio/defer.hpp"
#include "asio/detached.hpp"
#include "asio/dispatch.hpp"
#include "asio/error.hpp"
#include "asio/error_code.hpp"
Expand All @@ -61,7 +64,7 @@
#include "asio/handler_alloc_hook.hpp"
#include "asio/handler_continuation_hook.hpp"
#include "asio/handler_invoke_hook.hpp"
#include "asio/handler_type.hpp"
#include "asio/high_resolution_timer.hpp"
#include "asio/io_context.hpp"
#include "asio/io_context_strand.hpp"
#include "asio/io_service.hpp"
Expand All @@ -73,6 +76,8 @@
#include "asio/ip/address_v6.hpp"
#include "asio/ip/address_v6_iterator.hpp"
#include "asio/ip/address_v6_range.hpp"
#include "asio/ip/network_v4.hpp"
#include "asio/ip/network_v6.hpp"
#include "asio/ip/bad_address_cast.hpp"
#include "asio/ip/basic_endpoint.hpp"
#include "asio/ip/basic_resolver.hpp"
Expand All @@ -84,7 +89,6 @@
#include "asio/ip/multicast.hpp"
#include "asio/ip/resolver_base.hpp"
#include "asio/ip/resolver_query_base.hpp"
#include "asio/ip/resolver_service.hpp"
#include "asio/ip/tcp.hpp"
#include "asio/ip/udp.hpp"
#include "asio/ip/unicast.hpp"
Expand All @@ -96,48 +100,47 @@
#include "asio/local/connect_pair.hpp"
#include "asio/local/datagram_protocol.hpp"
#include "asio/local/stream_protocol.hpp"
#include "asio/packaged_task.hpp"
#include "asio/placeholders.hpp"
#include "asio/posix/basic_descriptor.hpp"
#include "asio/posix/basic_stream_descriptor.hpp"
#include "asio/posix/descriptor.hpp"
#include "asio/posix/descriptor_base.hpp"
#include "asio/posix/stream_descriptor.hpp"
#include "asio/posix/stream_descriptor_service.hpp"
#include "asio/post.hpp"
#include "asio/raw_socket_service.hpp"
#include "asio/read.hpp"
#include "asio/read_at.hpp"
#include "asio/read_until.hpp"
#include "asio/seq_packet_socket_service.hpp"
#include "asio/redirect_error.hpp"
#include "asio/serial_port.hpp"
#include "asio/serial_port_base.hpp"
#include "asio/serial_port_service.hpp"
#include "asio/signal_set.hpp"
#include "asio/signal_set_service.hpp"
#include "asio/socket_acceptor_service.hpp"
#include "asio/socket_base.hpp"
#include "asio/steady_timer.hpp"
#include "asio/strand.hpp"
#include "asio/stream_socket_service.hpp"
#include "asio/streambuf.hpp"
#include "asio/system_context.hpp"
#include "asio/system_error.hpp"
#include "asio/system_executor.hpp"
#include "asio/system_timer.hpp"
#include "asio/this_coro.hpp"
#include "asio/thread.hpp"
#include "asio/thread_pool.hpp"
#include "asio/time_traits.hpp"
#include "asio/use_awaitable.hpp"
#include "asio/use_future.hpp"
#include "asio/uses_executor.hpp"
#include "asio/version.hpp"
#include "asio/wait_traits.hpp"
#include "asio/waitable_timer_service.hpp"
#include "asio/windows/basic_handle.hpp"
#include "asio/windows/basic_object_handle.hpp"
#include "asio/windows/basic_overlapped_handle.hpp"
#include "asio/windows/basic_random_access_handle.hpp"
#include "asio/windows/basic_stream_handle.hpp"
#include "asio/windows/object_handle.hpp"
#include "asio/windows/object_handle_service.hpp"
#include "asio/windows/overlapped_handle.hpp"
#include "asio/windows/overlapped_ptr.hpp"
#include "asio/windows/random_access_handle.hpp"
#include "asio/windows/random_access_handle_service.hpp"
#include "asio/windows/stream_handle.hpp"
#include "asio/windows/stream_handle_service.hpp"
#include "asio/write.hpp"
#include "asio/write_at.hpp"

Expand Down
10 changes: 9 additions & 1 deletion link/asio/associated_allocator.hpp
Expand Up @@ -2,7 +2,7 @@
// associated_allocator.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com)
// Copyright (c) 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand Down Expand Up @@ -116,6 +116,14 @@ get_associated_allocator(const T& t, const Allocator& a) ASIO_NOEXCEPT
return associated_allocator<T, Allocator>::get(t, a);
}

#if defined(ASIO_HAS_ALIAS_TEMPLATES)

template <typename T, typename Allocator = std::allocator<void> >
using associated_allocator_t
= typename associated_allocator<T, Allocator>::type;

#endif // defined(ASIO_HAS_ALIAS_TEMPLATES)

} // namespace asio

#include "asio/detail/pop_options.hpp"
Expand Down
9 changes: 8 additions & 1 deletion link/asio/associated_executor.hpp
Expand Up @@ -2,7 +2,7 @@
// associated_executor.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com)
// Copyright (c) 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand Down Expand Up @@ -135,6 +135,13 @@ get_associated_executor(const T& t, ExecutionContext& ctx,
typename ExecutionContext::executor_type>::get(t, ctx.get_executor());
}

#if defined(ASIO_HAS_ALIAS_TEMPLATES)

template <typename T, typename Executor = system_executor>
using associated_executor_t = typename associated_executor<T, Executor>::type;

#endif // defined(ASIO_HAS_ALIAS_TEMPLATES)

} // namespace asio

#include "asio/detail/pop_options.hpp"
Expand Down