1 /* Copyright 2016, Ableton AG, Berlin. All rights reserved. 2 * 3 * This program is free software: you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation, either version 2 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 * 16 * If you would like to incorporate Link into a proprietary software application, 17 * please contact <link-devs@ableton.com>. 18 */ 19 20 #pragma once 21 22 #include <ableton/link/GhostXForm.hpp> 23 #include <ableton/link/Kalman.hpp> 24 #include <ableton/link/LinearRegression.hpp> 25 #include <ableton/link/Measurement.hpp> 26 #include <ableton/link/PeerState.hpp> 27 #include <ableton/link/PingResponder.hpp> 28 #include <ableton/link/SessionId.hpp> 29 #include <ableton/link/v1/Messages.hpp> 30 #include <map> 31 #include <memory> 32 33 namespace ableton 34 { 35 namespace link 36 { 37 38 template <typename Clock, typename IoContext> 39 class MeasurementService 40 { 41 public: 42 using IoType = util::Injected<IoContext>; 43 using Point = std::pair<double, double>; 44 using MeasurementInstance = Measurement<Clock, IoContext>; 45 MeasurementService(asio::ip::address_v4 address,SessionId sessionId,GhostXForm ghostXForm,Clock clock,IoType io)46 MeasurementService(asio::ip::address_v4 address, 47 SessionId sessionId, 48 GhostXForm ghostXForm, 49 Clock clock, 50 IoType io) 51 : mClock(std::move(clock)) 52 , mIo(std::move(io)) 53 , mPingResponder(std::move(address), 54 std::move(sessionId), 55 std::move(ghostXForm), 56 mClock, 57 util::injectRef(*mIo)) 58 { 59 } 60 61 MeasurementService(const MeasurementService&) = delete; 62 MeasurementService(MeasurementService&&) = delete; 63 ~MeasurementService()64 ~MeasurementService() 65 { 66 // Clear the measurement map in the IoContext so that whatever 67 // cleanup code executes in response to the destruction of the 68 // measurement objects still have access to the IoContext. 69 mIo->async([this] { mMeasurementMap.clear(); }); 70 } 71 updateNodeState(const SessionId & sessionId,const GhostXForm & xform)72 void updateNodeState(const SessionId& sessionId, const GhostXForm& xform) 73 { 74 mPingResponder.updateNodeState(sessionId, xform); 75 } 76 endpoint() const77 asio::ip::udp::endpoint endpoint() const 78 { 79 return mPingResponder.endpoint(); 80 } 81 82 // Measure the peer and invoke the handler with a GhostXForm 83 template <typename Handler> measurePeer(const PeerState & state,const Handler handler)84 void measurePeer(const PeerState& state, const Handler handler) 85 { 86 using namespace std; 87 88 mIo->async([this, state, handler] { 89 const auto nodeId = state.nodeState.nodeId; 90 auto addr = mPingResponder.endpoint().address().to_v4(); 91 auto callback = CompletionCallback<Handler>{*this, nodeId, handler}; 92 93 try 94 { 95 96 mMeasurementMap[nodeId] = 97 std::unique_ptr<MeasurementInstance>(new MeasurementInstance{ 98 state, move(callback), move(addr), mClock, mIo->clone()}); 99 } 100 catch (const runtime_error& err) 101 { 102 info(mIo->log()) << "gateway@" + addr.to_string() 103 << " Failed to measure. Reason: " << err.what(); 104 handler(GhostXForm{}); 105 } 106 }); 107 } 108 filter(std::vector<Point>::const_iterator begin,std::vector<Point>::const_iterator end)109 static GhostXForm filter( 110 std::vector<Point>::const_iterator begin, std::vector<Point>::const_iterator end) 111 { 112 using namespace std; 113 using std::chrono::microseconds; 114 115 Kalman<5> kalman; 116 for (auto it = begin; it != end; ++it) 117 { 118 kalman.iterate(it->second - it->first); 119 } 120 121 return GhostXForm{1, microseconds(llround(kalman.getValue()))}; 122 } 123 124 private: 125 template <typename Handler> 126 struct CompletionCallback 127 { operator ()ableton::link::MeasurementService::CompletionCallback128 void operator()(const std::vector<Point> data) 129 { 130 using namespace std; 131 using std::chrono::microseconds; 132 133 // Post this to the measurement service's IoContext so that we 134 // don't delete the measurement object in its stack. Capture all 135 // needed data separately from this, since this object may be 136 // gone by the time the block gets executed. 137 auto nodeId = mNodeId; 138 auto handler = mHandler; 139 auto& measurementMap = mMeasurementService.mMeasurementMap; 140 mMeasurementService.mIo->async([nodeId, handler, &measurementMap, data] { 141 const auto it = measurementMap.find(nodeId); 142 if (it != measurementMap.end()) 143 { 144 if (data.empty()) 145 { 146 handler(GhostXForm{}); 147 } 148 else 149 { 150 handler(MeasurementService::filter(begin(data), end(data))); 151 } 152 measurementMap.erase(it); 153 } 154 }); 155 } 156 157 MeasurementService& mMeasurementService; 158 NodeId mNodeId; 159 Handler mHandler; 160 }; 161 162 // Make sure the measurement map outlives the IoContext so that the rest of 163 // the members are guaranteed to be valid when any final handlers 164 // are begin run. 165 using MeasurementMap = std::map<NodeId, std::unique_ptr<MeasurementInstance>>; 166 MeasurementMap mMeasurementMap; 167 Clock mClock; 168 IoType mIo; 169 PingResponder<Clock, IoContext> mPingResponder; 170 }; 171 172 } // namespace link 173 } // namespace ableton 174