#pragma once #include #include "StarNetElement.hpp" #include "StarInterpolation.hpp" namespace Star { STAR_EXCEPTION(StepStreamException, StarException); template class NetElementFloating : public NetElement { public: T get() const; void set(T value); // If a fixed point base is given, then instead of transmitting the value as // a float, it is transmitted as a VLQ of the value divided by the fixed // point base. Any NetElementFloating that is transmitted to must also have // the same fixed point base set. void setFixedPointBase(Maybe fixedPointBase = {}); // If interpolation is enabled on the NetStepStates parent, and an // interpolator is set, then on steps in between data points this will be // used to interpolate this value. It is not necessary that senders and // receivers both have matching interpolation functions, or any interpolation // functions at all. void setInterpolator(function interpolator); void initNetVersion(NetElementVersion const* version = nullptr) override; // Values are never interpolated, but they will be delayed for the given // interpolationTime. void enableNetInterpolation(float extrapolationHint = 0.0f) override; void disableNetInterpolation() override; void tickNetInterpolation(float dt) override; void netStore(DataStream& ds) const override; void netLoad(DataStream& ds) override; bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override; void blankNetDelta(float interpolationTime = 0.0f) override; private: void writeValue(DataStream& ds, T t) const; T readValue(DataStream& ds) const; T interpolate() const; Maybe m_fixedPointBase; NetElementVersion const* m_netVersion = nullptr; uint64_t m_latestUpdateVersion = 0; T m_value = T(); function m_interpolator; float m_extrapolation = 0.0f; Maybe>> m_interpolationDataPoints; }; typedef NetElementFloating NetElementFloat; typedef NetElementFloating NetElementDouble; template T NetElementFloating::get() const { return m_value; } template void NetElementFloating::set(T value) { if (m_value != value) { // Only mark the step as updated here if it actually would change the // transmitted value. if (!m_fixedPointBase || round(m_value / *m_fixedPointBase) != round(value / *m_fixedPointBase)) m_latestUpdateVersion = m_netVersion ? m_netVersion->current() : 0; m_value = value; if (m_interpolationDataPoints) { m_interpolationDataPoints->clear(); m_interpolationDataPoints->append({0.0f, m_value}); } } } template void NetElementFloating::setFixedPointBase(Maybe fixedPointBase) { m_fixedPointBase = fixedPointBase; } template void NetElementFloating::setInterpolator(function interpolator) { m_interpolator = std::move(interpolator); } template void NetElementFloating::initNetVersion(NetElementVersion const* version) { m_netVersion = version; m_latestUpdateVersion = 0; } template void NetElementFloating::enableNetInterpolation(float extrapolationHint) { m_extrapolation = extrapolationHint; if (!m_interpolationDataPoints) { m_interpolationDataPoints.emplace(); m_interpolationDataPoints->append({0.0f, m_value}); } } template void NetElementFloating::disableNetInterpolation() { if (m_interpolationDataPoints) { m_value = m_interpolationDataPoints->last().second; m_interpolationDataPoints.reset(); } } template void NetElementFloating::tickNetInterpolation(float dt) { if (m_interpolationDataPoints) { for (auto& p : *m_interpolationDataPoints) p.first -= dt; while (m_interpolationDataPoints->size() > 2 && (*m_interpolationDataPoints)[1].first <= 0.0f) m_interpolationDataPoints->removeFirst(); m_value = interpolate(); } } template void NetElementFloating::netStore(DataStream& ds) const { if (m_interpolationDataPoints) writeValue(ds, m_interpolationDataPoints->last().second); else writeValue(ds, m_value); } template void NetElementFloating::netLoad(DataStream& ds) { m_value = readValue(ds); m_latestUpdateVersion = m_netVersion ? m_netVersion->current() : 0; if (m_interpolationDataPoints) { m_interpolationDataPoints->clear(); m_interpolationDataPoints->append({0.0f, m_value}); } } template bool NetElementFloating::writeNetDelta(DataStream& ds, uint64_t fromVersion) const { if (m_latestUpdateVersion < fromVersion) return false; if (m_interpolationDataPoints) writeValue(ds, m_interpolationDataPoints->last().second); else writeValue(ds, m_value); return true; } template void NetElementFloating::readNetDelta(DataStream& ds, float interpolationTime) { T t = readValue(ds); m_latestUpdateVersion = m_netVersion ? m_netVersion->current() : 0; if (m_interpolationDataPoints) { if (interpolationTime < m_interpolationDataPoints->last().first) m_interpolationDataPoints->clear(); m_interpolationDataPoints->append({interpolationTime, t}); m_value = interpolate(); } else { m_value = t; } } template void NetElementFloating::blankNetDelta(float interpolationTime) { if (m_interpolationDataPoints) { auto lastPoint = m_interpolationDataPoints->last(); float lastTime = lastPoint.first; lastPoint.first = interpolationTime; if (interpolationTime < lastTime) *m_interpolationDataPoints = {lastPoint}; else m_interpolationDataPoints->append(lastPoint); m_value = interpolate(); } } template void NetElementFloating::writeValue(DataStream& ds, T t) const { if (m_fixedPointBase) ds.writeVlqI(round(t / *m_fixedPointBase)); else ds.write(t); } template T NetElementFloating::readValue(DataStream& ds) const { T t; if (m_fixedPointBase) t = ds.readVlqI() * *m_fixedPointBase; else ds.read(t); return t; } template T NetElementFloating::interpolate() const { auto& dataPoints = *m_interpolationDataPoints; float ipos = inverseLinearInterpolateUpper(dataPoints.begin(), dataPoints.end(), 0.0f, [](float lhs, auto const& rhs) { return lhs < rhs.first; }, [](auto const& dataPoint) { return dataPoint.first; }); auto bound = getBound2(ipos, dataPoints.size(), BoundMode::Extrapolate); if (m_interpolator) { auto const& minPoint = dataPoints[bound.i0]; auto const& maxPoint = dataPoints[bound.i1]; // If step separation is less than 1.0, don't normalize extrapolation to // the very small step difference, because this can result in large jumps // during jitter. float stepDist = max(maxPoint.first - minPoint.first, 1.0f); float offset = clamp(bound.offset, 0.0f, 1.0f + m_extrapolation / stepDist); return m_interpolator(offset, minPoint.second, maxPoint.second); } else { if (bound.offset < 1.0f) return dataPoints[bound.i0].second; else return dataPoints[bound.i1].second; } } }