#ifndef STAR_NET_STATES_HPP #define STAR_NET_STATES_HPP #include "StarAlgorithm.hpp" #include "StarAny.hpp" #include "StarDynamicArray.hpp" #include "StarDataStream.hpp" #include "StarStepInterpolation.hpp" namespace Star { STAR_EXCEPTION(NetStatesException, StarException); STAR_CLASS(NetStepStates); // NetStates values that can be interpolated can be interpolated using the following methods: // // - "exact" interpolation, meaning that the interpolator simply does not look // into the future at all and does no interpolation, which is the default. // This is the same method that is used for values that cannot be // interpolated at all. // // - "lerp" interpolation, or simple linear interpolation. // // - A custom interpolation function, for values that where it may not make // sense to use linear interpolation, such as circular values. typedef function NetStatesInterpolator; NetStatesInterpolator const NetStatesExactInterpolator = NetStatesInterpolator(); NetStatesInterpolator const NetStatesLerpInterpolator = lerp; NetStatesInterpolator const NetStatesAngleInterpolator = angleLerp; namespace NetStatesDetail { typedef Any> Value; STAR_STRUCT(TypeInfo); STAR_CLASS(Field); struct GenericSerializer { virtual ~GenericSerializer() = default; virtual shared_ptr read(DataStream& ds) const = 0; virtual void write(DataStream& ds, shared_ptr const& data) const = 0; }; } // A handle to a single field in a NetStates object. Fields are designed to be // very fast to set and get, so they can generally be used in place of a // regular variable without much cost. class NetStatesField { public: NetStatesField(NetStatesField const&) = delete; NetStatesField(NetStatesField&&) = default; NetStatesField& operator=(NetStatesField const&) = delete; NetStatesField& operator=(NetStatesField&&) = default; // Pull whether or not the field has been updated since the last call to // pullUpdated. bool pullUpdated() const; // Is this field connected to a NetStepStates object? bool connected() const; protected: friend NetStepStates; NetStatesField(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value); NetStatesDetail::FieldPtr m_field; }; // NetStates integers are clamped BOTH when setting and when receiving delta // updates to the available bit width, 8, 16, or 64 bit. class NetStatesInt : public NetStatesField { public: static NetStatesInt makeInt8(int64_t initialValue = 0); static NetStatesInt makeInt16(int64_t initialValue = 0); static NetStatesInt makeVarInt(int64_t initialValue = 0); NetStatesInt(NetStatesInt&&) = default; NetStatesInt& operator=(NetStatesInt&&) = default; int64_t get() const; void set(int64_t value); private: NetStatesInt(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value); }; class NetStatesUInt : public NetStatesField { public: static NetStatesUInt makeUInt8(uint64_t initialValue = 0); static NetStatesUInt makeUInt16(uint64_t initialValue = 0); static NetStatesUInt makeVarUInt(uint64_t initialValue = 0); NetStatesUInt(NetStatesUInt&&) = default; NetStatesUInt& operator=(NetStatesUInt&&) = default; uint64_t get() const; void set(uint64_t value); private: NetStatesUInt(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value); }; // Properly encodes NPos no matter the platform width of size_t NetStates // size_t values are NOT clamped when setting. class NetStatesSize : public NetStatesField { public: NetStatesSize(size_t initialValue = 0); NetStatesSize(NetStatesSize&&) = default; NetStatesSize& operator=(NetStatesSize&&) = default; size_t get() const; void set(size_t value); }; // NetStates floats are clamped to their maximum and minimum legal values when // calling set if the type is one of the normalized float types NFloat8 or // NFloat16, or one of the fixed point types Fixed8 or Fixed16 (but not // VarFixed) class NetStatesFloat : public NetStatesField { public: static NetStatesFloat makeFloat(double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator); static NetStatesFloat makeDouble(double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator); // Float only from 0.0 to 1.0 static NetStatesFloat makeNormalizedFloat8(double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator); static NetStatesFloat makeNormalizedFloat16(double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator); // Float represented as a rational number of a fixed base static NetStatesFloat makeFixedPoint8(double base, double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator); static NetStatesFloat makeFixedPoint16(double base, double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator); static NetStatesFloat makeFixedPoint(double base, double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator); NetStatesFloat(NetStatesFloat&&) = default; NetStatesFloat& operator=(NetStatesFloat&&) = default; double get() const; void set(double value); void setInterpolator(NetStatesInterpolator interpolator); private: NetStatesFloat(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value); }; class NetStatesBool : public NetStatesField { public: NetStatesBool(bool initialValue = false); NetStatesBool(NetStatesBool&&) = default; NetStatesBool& operator=(NetStatesBool&&) = default; bool get() const; void set(bool value); }; // Wraps a uint64_t to give a simple event stream. Every trigger is an // increment to a held uint64_t value, and slaves can see how many triggers // have occurred since the last check. class NetStatesEvent : public NetStatesField { public: NetStatesEvent(); NetStatesEvent(NetStatesEvent&&) = default; NetStatesEvent& operator=(NetStatesEvent&&) = default; void trigger(); // Returns the number of times this event has been triggered since the last // pullOccurrences call. uint64_t pullOccurrences(); // Pulls whether this event occurred at all, ignoring the number bool pullOccurred(); // Ignore all the existing ocurrences void ignoreOccurrences(); private: using NetStatesField::pullUpdated; }; // Holds an arbitrary serializable value template class NetStatesData : public NetStatesField { public: NetStatesData(T initialValue = T()); NetStatesData(function reader, function writer, T initialValue = T()); NetStatesData(NetStatesData&&) = default; NetStatesData& operator=(NetStatesData&&) = default; T const& get() const; // Updates the value if the value is different than the existing value, // requires T have operator== void set(T const& value); // Always updates the value and marks it as updated. void push(T value); // Update the value in place. The mutator will be called as bool // mutator(T&), return true to signal that the value was updated. template void update(Mutator&& mutator); private: struct SerializerImpl : NetStatesDetail::GenericSerializer { SerializerImpl(function reader, function writer); function reader; function writer; shared_ptr read(DataStream& ds) const override; void write(DataStream& ds, shared_ptr const& data) const override; }; }; // Convenience around NetStatesInt to hold an enum value (avoids casting) template class NetStatesEnum : public NetStatesField { public: NetStatesEnum(T initialValue = T()); T get() const; void set(T value); }; // Convenience for very common NetStatesData types typedef NetStatesData NetStatesString; typedef NetStatesData NetStatesBytes; // Send and receives state using delta coding based on a always increasing step // value. The step is never sent over the wire, rather it is used as a way to // track versions of states. class NetStepStates { public: NetStepStates(); template NetStepStates(Args const&... fields); NetStepStates(NetStepStates const& states) = delete; NetStepStates& operator=(NetStepStates const& states) = delete; // Add the given unconnected field and connect it to this NetStepStates // instance. Individual fields in a NetStates object are identified based on // the order in which they are added. Both sides of a NetStates object must // add the same number of fields and of the same type, and in the same order, // for communication to work. void addField(NetStatesField const& nsfield); template void addFields(Args const&... fields); // Removes all fields and resets back to the initial state. void clearFields(); // Produces a digest of all the field types in this NetStates object uint32_t fieldDigest() const; // When sending, it is important *when* precisely updateStep is called in // order not to lose data, the order *must* be: // - updateStep() // - *do things that change data* // - *write deltas* // - repeat. // No data should be set in between writing a delta and incrementing step, or // data will be lost to slaves. (Assuming no delta overlapping is done to // prevent it). // // An exception will be thrown if newStep ever decreases in a call to // updateStep, reset must be called instead. // // Returns true in the event that updating the step changed the value of any // fields. This will only be true when interpolation is enabled. bool updateStep(uint64_t newStep); uint64_t currentStep() const; // Start the NetStepStates object back from the initial step (will all the // existing fields). All current field values are kept unchanged and // interpolation data is cleared. void resetStep(); // Enables interpolation mode. If interpolation mode is enabled, then all // incoming states are marked with a floating target step value, and states // are delayed until the interpolation step reaches the given step value for // that state. This also enables optional interpolation smoothing on // floating point values that looks ahead into the future to even out // changes, and can also optionally extrapolate into the future if no data is // available. // // While interpolating, calling a set will discard any held interpolation // data even into the future. void enableInterpolation(uint64_t extrapolationSteps = 0); void disableInterpolation(); bool interpolationEnabled() const; // Returns true if there is changed data since this timestep, and writeDelta // called with this step would write data. bool hasDelta(uint64_t fromStep) const; // Write all the state changes that have happened since (and including) // fromStep. The normal way to use this would be each call to writeDelta // should be passed the step at the time of the *last* call to writeDelta, + // 1. writeDelta with fromStep = 0 writes all states, and is a larger // version of writeFull(). If hasDelta(fromStep) returns false, this will // still write some data as an end marker, it is best to call hasDelta first // to see if any delta is needed. void writeDelta(DataStream& ds, uint64_t fromStep) const; // predictedDeltaStep is only necessary if interpolation is enabled, // otherwise the delta is assumed to apply immediately. Will return true // when reading this delta has changed the current state of any fields. bool readDelta(DataStream& ds, double predictedDeltaStep = 0); // Writes / reads all values regardless of changed state. Usually a slight // network improvement over writeDelta(0), as it does not need to prefix // every field with an index. Additionall, first writes the 4 byte field // digest to the full state, so readFull also acts as a type consistency // check. void writeFull(DataStream& ds) const; void readFull(DataStream& ds, double predictedDeltaStep = 0); // Write and read deltas directly to a byte array. Uses a slightly different // binary format that relies on the size of the array packet, so cannot be // mixed with normal writes and reads. ByteArray writeDeltaPacket(uint64_t fromStep) const; bool readDeltaPacket(ByteArray packet, double predictedDeltaStep = 0); ByteArray writeFullPacket() const; void readFullPacket(ByteArray packet, double predictedDeltaStep = 0); // Equivalent to reading a blank delta state, simply lets receiver know that // no delta is needed for up to this timestep, so no extrapolation is // required. Not required if interpolation is not enabled or is enabled with // zero extrapolation steps. void interpolationHeartbeat(double predictedDeltaStep = 0); private: DynamicArray m_fields; uint64_t m_currentStep; bool m_interpolationEnabled; uint64_t m_extrapolationSteps; }; // NetStepStates with no concept of a step, only sends values that have been // changed between writes. Only works for writing to a single receiver. class NetSyncStates : private NetStepStates { public: NetSyncStates(); template NetSyncStates(Args const&... fields); bool hasDelta() const; void writeDelta(DataStream& ds); bool readDelta(DataStream& ds); ByteArray writeDeltaPacket(); bool readDeltaPacket(ByteArray packet); void reset(); using NetStepStates::addField; using NetStepStates::clearFields; using NetStepStates::fieldDigest; }; namespace NetStatesDetail { // Values as they are sent across the wire, converted to their compressed or // truncated transmission format. typedef Any> TransmissionValue; enum class TransmissionType : uint8_t { Int8, UInt8, Int16, UInt16, VarInt, VarUInt, Size, Float, Double, NFloat8, NFloat16, Fixed8, Fixed16, VarFixed, Bool, Event, Generic }; struct TypeInfo { TransmissionType type; // If this is a fixed point float, this is its fixed point base Maybe fixedPointBase; // If the type is Data, this holds the reader / writer for it. shared_ptr genericSerializer; // If this is any sort of floating type, then if value interpolation is // enabled this will hold the interpolator NetStatesInterpolator interpolator; Value valueFromTransmission(TransmissionValue const& transmission) const; TransmissionValue transmissionFromValue(Value const& value) const; }; class Field { public: Field(TypeInfo typeInfo, Value value); TypeInfo const& typeInfo() const; int64_t getInt() const; void setInt(int64_t value); uint64_t getUInt() const; void setUInt(uint64_t value); size_t getSize() const; void setSize(size_t value); double getFloat() const; void setFloat(double value); void setFloatInterpolator(NetStatesInterpolator interpolator); bool getBool() const; void setBool(bool value); void triggerEvent(); uint64_t pullOccurrences(); void ignoreOccurrences(); template T const& getGeneric() const; template void setGeneric(T const& data); template void pushGeneric(T data); template void updateGeneric(Mutator&& mutator); bool pullUpdated(); // Returns true if field value may have changed bool updateStep(uint64_t step); void resetStep(); bool hasDelta(uint64_t fromStep) const; bool readField(DataStream& ds, double interpolationStep); void writeField(DataStream& ds) const; void enableInterpolation(uint64_t extrapolationSteps); void disableInterpolation(); void interpolationHeartbeat(double predictedDeltaStep); private: bool updateValueFromTransmission(); // Marks the value as updated by setting the unpulledUpdate flag and the // transmissionDirty flag void markValueUpdated(); // To support fast setting of values, transmission values are updated // lazily. Once the transmissionDirty flag is set, call // freshenTransmission() to (IF the transmission value has changed) update // the latest transmission and latest transmission step from the current // value. void freshenTransmission(); TypeInfo m_typeInfo; uint64_t m_currentStep; Value m_value; bool m_unpulledUpdate; uint64_t m_pulledOccurrences; bool m_transmissionDirty; TransmissionValue m_latestTransmission; uint64_t m_latestTransmissionStep; Maybe> m_interpolatedTransmissions; }; } template NetStatesData::NetStatesData(T initialValue) : NetStatesData( [](DataStream& ds, T& t) { ds >> t; }, [](DataStream& ds, T const& t) { ds << t; }, move(initialValue)) {} template NetStatesData::NetStatesData(function reader, function writer, T initialValue) : NetStatesField(NetStatesDetail::TypeInfo{ NetStatesDetail::TransmissionType::Generic, {}, make_shared(move(reader), move(writer)), {} }, shared_ptr(make_shared(move(initialValue)))) {} template T const& NetStatesData::get() const { return m_field->template getGeneric(); } template void NetStatesData::set(T const& value) { m_field->template setGeneric(value); } template void NetStatesData::push(T value) { m_field->template pushGeneric(move(value)); } template template void NetStatesData::update(Mutator&& mutator) { m_field->template updateGeneric(forward(mutator)); } template NetStatesData::SerializerImpl::SerializerImpl(function reader, function writer) : reader(move(reader)), writer(move(writer)) {} template shared_ptr NetStatesData::SerializerImpl::read(DataStream& ds) const { auto data = make_shared(); reader(ds, *data); return data; } template void NetStatesData::SerializerImpl::write(DataStream& ds, shared_ptr const& data) const { writer(ds, *(T*)data.get()); } template NetStatesEnum::NetStatesEnum(T initialValue) : NetStatesField( NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::VarInt, {}, {}, {}}, (int64_t)initialValue) {} template T NetStatesEnum::get() const { return (T)m_field->getInt(); } template void NetStatesEnum::set(T value) { return m_field->setInt((int64_t)value); } template NetStepStates::NetStepStates(Args const&... fields) : NetStepStates() { addFields(fields...); } template void NetStepStates::addFields(Args const&... fields) { callFunctionVariadic([this](NetStatesField const& field) { addField(field); }, fields...); } template NetSyncStates::NetSyncStates(Args const&... fields) : NetSyncStates() { addFields(fields...); } template T const& NetStatesDetail::Field::getGeneric() const { starAssert(m_value.get>().get() != nullptr); return *(T const*)m_value.get>().get(); } template void NetStatesDetail::Field::setGeneric(T const& data) { auto& p = m_value.get>(); if (!(*(T*)p.get() == data)) { *(T*)p.get() = data; markValueUpdated(); } } template void NetStatesDetail::Field::pushGeneric(T data) { if (m_value) { auto& p = m_value.get>(); *(T*)p.get() = move(data); } else { m_value = shared_ptr(make_shared(move(data))); } markValueUpdated(); } template void NetStatesDetail::Field::updateGeneric(Mutator&& mutator) { auto const& p = m_value.get>(); if (mutator(*(T*)p.get())) markValueUpdated(); } } #endif