osb/source/core/StarNetElementFloatFields.hpp
2023-06-20 14:33:09 +10:00

247 lines
7.2 KiB
C++

#ifndef STAR_NET_ELEMENT_FLOAT_FIELDS_HPP
#define STAR_NET_ELEMENT_FLOAT_FIELDS_HPP
#include <type_traits>
#include "StarNetElement.hpp"
#include "StarInterpolation.hpp"
namespace Star {
STAR_EXCEPTION(StepStreamException, StarException);
template <typename T>
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<T> 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<T(T, T, T)> 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<T> m_fixedPointBase;
NetElementVersion const* m_netVersion = nullptr;
uint64_t m_latestUpdateVersion = 0;
T m_value = T();
function<T(T, T, T)> m_interpolator;
float m_extrapolation = 0.0f;
Maybe<Deque<pair<float, T>>> m_interpolationDataPoints;
};
typedef NetElementFloating<float> NetElementFloat;
typedef NetElementFloating<double> NetElementDouble;
template <typename T>
T NetElementFloating<T>::get() const {
return m_value;
}
template <typename T>
void NetElementFloating<T>::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 <typename T>
void NetElementFloating<T>::setFixedPointBase(Maybe<T> fixedPointBase) {
m_fixedPointBase = fixedPointBase;
}
template <typename T>
void NetElementFloating<T>::setInterpolator(function<T(T, T, T)> interpolator) {
m_interpolator = move(interpolator);
}
template <typename T>
void NetElementFloating<T>::initNetVersion(NetElementVersion const* version) {
m_netVersion = version;
m_latestUpdateVersion = 0;
}
template <typename T>
void NetElementFloating<T>::enableNetInterpolation(float extrapolationHint) {
m_extrapolation = extrapolationHint;
if (!m_interpolationDataPoints) {
m_interpolationDataPoints.emplace();
m_interpolationDataPoints->append({0.0f, m_value});
}
}
template <typename T>
void NetElementFloating<T>::disableNetInterpolation() {
if (m_interpolationDataPoints) {
m_value = m_interpolationDataPoints->last().second;
m_interpolationDataPoints.reset();
}
}
template <typename T>
void NetElementFloating<T>::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 <typename T>
void NetElementFloating<T>::netStore(DataStream& ds) const {
if (m_interpolationDataPoints)
writeValue(ds, m_interpolationDataPoints->last().second);
else
writeValue(ds, m_value);
}
template <typename T>
void NetElementFloating<T>::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 <typename T>
bool NetElementFloating<T>::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 <typename T>
void NetElementFloating<T>::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 <typename T>
void NetElementFloating<T>::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 <typename T>
void NetElementFloating<T>::writeValue(DataStream& ds, T t) const {
if (m_fixedPointBase)
ds.writeVlqI(round(t / *m_fixedPointBase));
else
ds.write(t);
}
template <typename T>
T NetElementFloating<T>::readValue(DataStream& ds) const {
T t;
if (m_fixedPointBase)
t = ds.readVlqI() * *m_fixedPointBase;
else
ds.read(t);
return t;
}
template <typename T>
T NetElementFloating<T>::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<float>(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;
}
}
}
#endif