Add ReplicationModes to NetworkTransform with interpolation and prediction modes
This commit is contained in:
@@ -1,26 +1,62 @@
|
|||||||
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
// Interpolation and prediction logic based on https://www.gabrielgambetta.com/client-server-game-architecture.html
|
||||||
|
|
||||||
#include "NetworkTransform.h"
|
#include "NetworkTransform.h"
|
||||||
#include "Engine/Core/Math/Transform.h"
|
#include "Engine/Core/Math/Transform.h"
|
||||||
|
#include "Engine/Engine/Time.h"
|
||||||
#include "Engine/Level/Actor.h"
|
#include "Engine/Level/Actor.h"
|
||||||
|
#include "Engine/Networking/NetworkManager.h"
|
||||||
|
#include "Engine/Networking/NetworkPeer.h"
|
||||||
#include "Engine/Networking/NetworkReplicator.h"
|
#include "Engine/Networking/NetworkReplicator.h"
|
||||||
#include "Engine/Networking/NetworkStream.h"
|
#include "Engine/Networking/NetworkStream.h"
|
||||||
|
#include "Engine/Networking/NetworkStats.h"
|
||||||
|
#include "Engine/Networking/INetworkDriver.h"
|
||||||
|
#include "Engine/Networking/NetworkRpc.h"
|
||||||
|
|
||||||
PACK_STRUCT(struct Data
|
PACK_STRUCT(struct Data
|
||||||
{
|
{
|
||||||
uint8 LocalSpace : 1;
|
uint8 LocalSpace : 1;
|
||||||
NetworkTransform::SyncModes SyncMode : 9;
|
uint8 HasSequenceIndex : 1;
|
||||||
|
NetworkTransform::ReplicationComponents Components : 9;
|
||||||
});
|
});
|
||||||
|
|
||||||
static_assert((int32)NetworkTransform::SyncModes::All + 1 == 512, "Invalid SyncModes bit count for Data.");
|
static_assert((int32)NetworkTransform::ReplicationComponents::All + 1 == 512, "Invalid ReplicationComponents bit count for Data.");
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Percentage of local error that is acceptable (eg. 4 frames error)
|
||||||
|
constexpr float Precision = 4.0f;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
FORCE_INLINE bool IsWithinPrecision(const Vector3Base<T>& currentDelta, const Vector3Base<T>& targetDelta)
|
||||||
|
{
|
||||||
|
const T targetDeltaMax = targetDelta.GetAbsolute().MaxValue();
|
||||||
|
return targetDeltaMax > (T)ZeroTolerance && currentDelta.GetAbsolute().MaxValue() < targetDeltaMax * (T)Precision;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NetworkTransform::NetworkTransform(const SpawnParams& params)
|
NetworkTransform::NetworkTransform(const SpawnParams& params)
|
||||||
: Script(params)
|
: Script(params)
|
||||||
{
|
{
|
||||||
|
// TODO: don't tick when using Default mode or with OwnedAuthoritative role to optimize cpu perf OR introduce TaskGraphSystem to batch NetworkTransform updates over Job System
|
||||||
|
_tickUpdate = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkTransform::SetSequenceIndex(uint16 value)
|
||||||
|
{
|
||||||
|
NETWORK_RPC_IMPL(NetworkTransform, SetSequenceIndex, value);
|
||||||
|
_currentSequenceIndex = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkTransform::OnEnable()
|
void NetworkTransform::OnEnable()
|
||||||
{
|
{
|
||||||
|
// Initialize state
|
||||||
|
_bufferHasDeltas = false;
|
||||||
|
_currentSequenceIndex = 0.0f;
|
||||||
|
_lastFrameTransform = GetActor() ? GetActor()->GetTransform() : Transform::Identity;
|
||||||
|
_buffer.Clear();
|
||||||
|
|
||||||
// Register for replication
|
// Register for replication
|
||||||
NetworkReplicator::AddObject(this);
|
NetworkReplicator::AddObject(this);
|
||||||
}
|
}
|
||||||
@@ -29,6 +65,81 @@ void NetworkTransform::OnDisable()
|
|||||||
{
|
{
|
||||||
// Unregister from replication
|
// Unregister from replication
|
||||||
NetworkReplicator::RemoveObject(this);
|
NetworkReplicator::RemoveObject(this);
|
||||||
|
|
||||||
|
_buffer.Resize(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkTransform::OnUpdate()
|
||||||
|
{
|
||||||
|
// TODO: cache role in Deserialize to improve cpu perf
|
||||||
|
const NetworkObjectRole role = NetworkReplicator::GetObjectRole(this);
|
||||||
|
if (role == NetworkObjectRole::OwnedAuthoritative)
|
||||||
|
return; // Ignore itself
|
||||||
|
if (Mode == ReplicationModes::Default)
|
||||||
|
{
|
||||||
|
// Transform replicated in Deserialize
|
||||||
|
}
|
||||||
|
else if (role == NetworkObjectRole::ReplicatedSimulated && Mode == ReplicationModes::Prediction)
|
||||||
|
{
|
||||||
|
// Compute delta of the actor transformation simulated locally
|
||||||
|
const Transform thisFrameTransform = GetActor() ? GetActor()->GetTransform() : Transform::Identity;
|
||||||
|
Transform delta = thisFrameTransform - _lastFrameTransform;
|
||||||
|
|
||||||
|
if (!delta.IsIdentity())
|
||||||
|
{
|
||||||
|
// Move to the next input sequence number
|
||||||
|
_currentSequenceIndex++;
|
||||||
|
|
||||||
|
// Add delta to buffer to re-apply after receiving authoritative transform value
|
||||||
|
if (!_bufferHasDeltas)
|
||||||
|
{
|
||||||
|
_buffer.Clear();
|
||||||
|
_bufferHasDeltas = true;
|
||||||
|
}
|
||||||
|
delta.Orientation = thisFrameTransform.Orientation; // Store absolute orientation value to prevent jittering when blending rotation deltas
|
||||||
|
_buffer.Add({ 0.0f, _currentSequenceIndex, delta, });
|
||||||
|
|
||||||
|
// Inform server about sequence number change (add offset to lead before server data)
|
||||||
|
SetSequenceIndex(_currentSequenceIndex - 1);
|
||||||
|
}
|
||||||
|
_lastFrameTransform = thisFrameTransform;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float lag = 0.0f;
|
||||||
|
// TODO: use lag from last used NetworkStream context
|
||||||
|
if (NetworkManager::Peer && NetworkManager::Peer->NetworkDriver)
|
||||||
|
{
|
||||||
|
// Use lag from the RTT between server and the client
|
||||||
|
const auto stats = NetworkManager::Peer->NetworkDriver->GetStats();
|
||||||
|
lag = stats.RTT / 2000.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default lag is based on the network manager update rate
|
||||||
|
const float fps = NetworkManager::NetworkFPS;
|
||||||
|
lag = 1.0f / fps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the two authoritative positions surrounding the rendering timestamp
|
||||||
|
const float now = Time::Update.UnscaledTime.GetTotalSeconds();
|
||||||
|
const float gameTime = now - lag;
|
||||||
|
|
||||||
|
// Drop older positions
|
||||||
|
while (_buffer.Count() >= 2 && _buffer[1].Timestamp <= gameTime)
|
||||||
|
_buffer.RemoveAtKeepOrder(0);
|
||||||
|
|
||||||
|
// Interpolate between the two surrounding authoritative positions
|
||||||
|
if (_buffer.Count() >= 2 && _buffer[0].Timestamp <= gameTime && gameTime <= _buffer[1].Timestamp)
|
||||||
|
{
|
||||||
|
const auto& b0 = _buffer[0];
|
||||||
|
const auto& b1 = _buffer[1];
|
||||||
|
Transform transform;
|
||||||
|
const float alpha = (gameTime - b0.Timestamp) / (b1.Timestamp - b0.Timestamp);
|
||||||
|
Transform::Lerp(b0.Value, b1.Value, alpha, transform);
|
||||||
|
Set(transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkTransform::Serialize(NetworkStream* stream)
|
void NetworkTransform::Serialize(NetworkStream* stream)
|
||||||
@@ -41,58 +152,62 @@ void NetworkTransform::Serialize(NetworkStream* stream)
|
|||||||
transform = Transform::Identity;
|
transform = Transform::Identity;
|
||||||
|
|
||||||
// Encode data
|
// Encode data
|
||||||
|
const NetworkObjectRole role = NetworkReplicator::GetObjectRole(this);
|
||||||
Data data;
|
Data data;
|
||||||
data.LocalSpace = LocalSpace;
|
data.LocalSpace = LocalSpace;
|
||||||
data.SyncMode = SyncMode;
|
data.HasSequenceIndex = Mode == ReplicationModes::Prediction;
|
||||||
|
data.Components = Components;
|
||||||
stream->Write(data);
|
stream->Write(data);
|
||||||
if ((data.SyncMode & SyncModes::All) == (int)SyncModes::All)
|
if ((data.Components & ReplicationComponents::All) == (int)ReplicationComponents::All)
|
||||||
{
|
{
|
||||||
stream->Write(transform);
|
stream->Write(transform);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if ((data.SyncMode & SyncModes::Position) == (int)SyncModes::Position)
|
if ((data.Components & ReplicationComponents::Position) == (int)ReplicationComponents::Position)
|
||||||
{
|
{
|
||||||
stream->Write(transform.Translation);
|
stream->Write(transform.Translation);
|
||||||
}
|
}
|
||||||
else if (data.SyncMode & SyncModes::Position)
|
else if (data.Components & ReplicationComponents::Position)
|
||||||
{
|
{
|
||||||
if (data.SyncMode & SyncModes::PositionX)
|
if (data.Components & ReplicationComponents::PositionX)
|
||||||
stream->Write(transform.Translation.X);
|
stream->Write(transform.Translation.X);
|
||||||
if (data.SyncMode & SyncModes::PositionY)
|
if (data.Components & ReplicationComponents::PositionY)
|
||||||
stream->Write(transform.Translation.X);
|
stream->Write(transform.Translation.X);
|
||||||
if (data.SyncMode & SyncModes::PositionZ)
|
if (data.Components & ReplicationComponents::PositionZ)
|
||||||
stream->Write(transform.Translation.X);
|
stream->Write(transform.Translation.X);
|
||||||
}
|
}
|
||||||
if ((data.SyncMode & SyncModes::Scale) == (int)SyncModes::Scale)
|
if ((data.Components & ReplicationComponents::Scale) == (int)ReplicationComponents::Scale)
|
||||||
{
|
{
|
||||||
stream->Write(transform.Scale);
|
stream->Write(transform.Scale);
|
||||||
}
|
}
|
||||||
else if (data.SyncMode & SyncModes::Scale)
|
else if (data.Components & ReplicationComponents::Scale)
|
||||||
{
|
{
|
||||||
if (data.SyncMode & SyncModes::ScaleX)
|
if (data.Components & ReplicationComponents::ScaleX)
|
||||||
stream->Write(transform.Scale.X);
|
stream->Write(transform.Scale.X);
|
||||||
if (data.SyncMode & SyncModes::ScaleY)
|
if (data.Components & ReplicationComponents::ScaleY)
|
||||||
stream->Write(transform.Scale.X);
|
stream->Write(transform.Scale.X);
|
||||||
if (data.SyncMode & SyncModes::ScaleZ)
|
if (data.Components & ReplicationComponents::ScaleZ)
|
||||||
stream->Write(transform.Scale.X);
|
stream->Write(transform.Scale.X);
|
||||||
}
|
}
|
||||||
if ((data.SyncMode & SyncModes::Rotation) == (int)SyncModes::Rotation)
|
if ((data.Components & ReplicationComponents::Rotation) == (int)ReplicationComponents::Rotation)
|
||||||
{
|
{
|
||||||
const Float3 rotation = transform.Orientation.GetEuler();
|
const Float3 rotation = transform.Orientation.GetEuler();
|
||||||
stream->Write(rotation);
|
stream->Write(rotation);
|
||||||
}
|
}
|
||||||
else if (data.SyncMode & SyncModes::Rotation)
|
else if (data.Components & ReplicationComponents::Rotation)
|
||||||
{
|
{
|
||||||
const Float3 rotation = transform.Orientation.GetEuler();
|
const Float3 rotation = transform.Orientation.GetEuler();
|
||||||
if (data.SyncMode & SyncModes::RotationX)
|
if (data.Components & ReplicationComponents::RotationX)
|
||||||
stream->Write(rotation.X);
|
stream->Write(rotation.X);
|
||||||
if (data.SyncMode & SyncModes::RotationY)
|
if (data.Components & ReplicationComponents::RotationY)
|
||||||
stream->Write(rotation.Y);
|
stream->Write(rotation.Y);
|
||||||
if (data.SyncMode & SyncModes::RotationZ)
|
if (data.Components & ReplicationComponents::RotationZ)
|
||||||
stream->Write(rotation.Z);
|
stream->Write(rotation.Z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (data.HasSequenceIndex)
|
||||||
|
stream->Write(_currentSequenceIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkTransform::Deserialize(NetworkStream* stream)
|
void NetworkTransform::Deserialize(NetworkStream* stream)
|
||||||
@@ -103,65 +218,133 @@ void NetworkTransform::Deserialize(NetworkStream* stream)
|
|||||||
transform = LocalSpace ? parent->GetLocalTransform() : parent->GetTransform();
|
transform = LocalSpace ? parent->GetLocalTransform() : parent->GetTransform();
|
||||||
else
|
else
|
||||||
transform = Transform::Identity;
|
transform = Transform::Identity;
|
||||||
|
Transform transformLocal = transform;
|
||||||
|
|
||||||
// Decode data
|
// Decode data
|
||||||
Data data;
|
Data data;
|
||||||
stream->Read(data);
|
stream->Read(data);
|
||||||
if ((data.SyncMode & SyncModes::All) == (int)SyncModes::All)
|
if ((data.Components & ReplicationComponents::All) == (int)ReplicationComponents::All)
|
||||||
{
|
{
|
||||||
stream->Read(transform);
|
stream->Read(transform);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if ((data.SyncMode & SyncModes::Position) == (int)SyncModes::Position)
|
if ((data.Components & ReplicationComponents::Position) == (int)ReplicationComponents::Position)
|
||||||
{
|
{
|
||||||
stream->Read(transform.Translation);
|
stream->Read(transform.Translation);
|
||||||
}
|
}
|
||||||
else if (data.SyncMode & SyncModes::Position)
|
else if (data.Components & ReplicationComponents::Position)
|
||||||
{
|
{
|
||||||
if (data.SyncMode & SyncModes::PositionX)
|
if (data.Components & ReplicationComponents::PositionX)
|
||||||
stream->Read(transform.Translation.X);
|
stream->Read(transform.Translation.X);
|
||||||
if (data.SyncMode & SyncModes::PositionY)
|
if (data.Components & ReplicationComponents::PositionY)
|
||||||
stream->Read(transform.Translation.X);
|
stream->Read(transform.Translation.X);
|
||||||
if (data.SyncMode & SyncModes::PositionZ)
|
if (data.Components & ReplicationComponents::PositionZ)
|
||||||
stream->Read(transform.Translation.X);
|
stream->Read(transform.Translation.X);
|
||||||
}
|
}
|
||||||
if ((data.SyncMode & SyncModes::Scale) == (int)SyncModes::Scale)
|
if ((data.Components & ReplicationComponents::Scale) == (int)ReplicationComponents::Scale)
|
||||||
{
|
{
|
||||||
stream->Read(transform.Scale);
|
stream->Read(transform.Scale);
|
||||||
}
|
}
|
||||||
else if (data.SyncMode & SyncModes::Scale)
|
else if (data.Components & ReplicationComponents::Scale)
|
||||||
{
|
{
|
||||||
if (data.SyncMode & SyncModes::ScaleX)
|
if (data.Components & ReplicationComponents::ScaleX)
|
||||||
stream->Read(transform.Scale.X);
|
stream->Read(transform.Scale.X);
|
||||||
if (data.SyncMode & SyncModes::ScaleY)
|
if (data.Components & ReplicationComponents::ScaleY)
|
||||||
stream->Read(transform.Scale.X);
|
stream->Read(transform.Scale.X);
|
||||||
if (data.SyncMode & SyncModes::ScaleZ)
|
if (data.Components & ReplicationComponents::ScaleZ)
|
||||||
stream->Read(transform.Scale.X);
|
stream->Read(transform.Scale.X);
|
||||||
}
|
}
|
||||||
if ((data.SyncMode & SyncModes::Rotation) == (int)SyncModes::Rotation)
|
if ((data.Components & ReplicationComponents::Rotation) == (int)ReplicationComponents::Rotation)
|
||||||
{
|
{
|
||||||
Float3 rotation;
|
Float3 rotation;
|
||||||
stream->Read(rotation);
|
stream->Read(rotation);
|
||||||
transform.Orientation = Quaternion::Euler(rotation);
|
transform.Orientation = Quaternion::Euler(rotation);
|
||||||
}
|
}
|
||||||
else if (data.SyncMode & SyncModes::Rotation)
|
else if (data.Components & ReplicationComponents::Rotation)
|
||||||
{
|
{
|
||||||
Float3 rotation = transform.Orientation.GetEuler();
|
Float3 rotation = transform.Orientation.GetEuler();
|
||||||
if (data.SyncMode & SyncModes::RotationX)
|
if (data.Components & ReplicationComponents::RotationX)
|
||||||
stream->Read(rotation.X);
|
stream->Read(rotation.X);
|
||||||
if (data.SyncMode & SyncModes::RotationY)
|
if (data.Components & ReplicationComponents::RotationY)
|
||||||
stream->Read(rotation.Y);
|
stream->Read(rotation.Y);
|
||||||
if (data.SyncMode & SyncModes::RotationZ)
|
if (data.Components & ReplicationComponents::RotationZ)
|
||||||
stream->Read(rotation.Z);
|
stream->Read(rotation.Z);
|
||||||
transform.Orientation = Quaternion::Euler(rotation);
|
transform.Orientation = Quaternion::Euler(rotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
uint16 sequenceIndex = 0;
|
||||||
|
if (data.HasSequenceIndex)
|
||||||
|
stream->Read(sequenceIndex);
|
||||||
|
if (data.LocalSpace != LocalSpace)
|
||||||
|
return; // TODO: convert transform space if server-client have different values set
|
||||||
|
|
||||||
// Set transform
|
const NetworkObjectRole role = NetworkReplicator::GetObjectRole(this);
|
||||||
|
if (role == NetworkObjectRole::OwnedAuthoritative)
|
||||||
|
return; // Ignore itself
|
||||||
|
if (Mode == ReplicationModes::Default)
|
||||||
|
{
|
||||||
|
// Immediate set
|
||||||
|
Set(transform);
|
||||||
|
}
|
||||||
|
else if (role == NetworkObjectRole::ReplicatedSimulated && Mode == ReplicationModes::Prediction)
|
||||||
|
{
|
||||||
|
const Transform transformAuthoritative = transform;
|
||||||
|
const Transform transformDeltaBefore = transformAuthoritative - transformLocal;
|
||||||
|
|
||||||
|
// Remove any transform deltas from local simulation that happened before the incoming authoritative transform data
|
||||||
|
if (!_bufferHasDeltas)
|
||||||
|
{
|
||||||
|
_buffer.Clear();
|
||||||
|
_bufferHasDeltas = true;
|
||||||
|
}
|
||||||
|
// TODO: items are added in order to do batch removal
|
||||||
|
for (int32 i = 0; i < _buffer.Count() && _buffer[i].SequenceIndex < sequenceIndex; i++)
|
||||||
|
{
|
||||||
|
_buffer.RemoveAtKeepOrder(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use received authoritative actor transformation but re-apply all deltas not yet processed by the server due to lag (reconciliation)
|
||||||
|
for (auto& e : _buffer)
|
||||||
|
{
|
||||||
|
transform.Translation = transform.Translation + e.Value.Translation;
|
||||||
|
transform.Scale = transform.Scale * e.Value.Scale;
|
||||||
|
}
|
||||||
|
// TODO: use euler angles or similar to cache/reapply rotation deltas (Quaternion jitters)
|
||||||
|
transform.Orientation = transformLocal.Orientation;
|
||||||
|
|
||||||
|
// If local simulation is very close to the authoritative server value then ignore slight error (based relative delta threshold)
|
||||||
|
const Transform transformDeltaAfter = transformAuthoritative - transformLocal;
|
||||||
|
const Transform transformDeltaDelta = transformDeltaAfter - transformDeltaBefore;
|
||||||
|
if (IsWithinPrecision(transformDeltaDelta.Translation, transformDeltaAfter.Translation) &&
|
||||||
|
IsWithinPrecision(transformDeltaDelta.Scale, transformDeltaAfter.Scale)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set to the incoming value with applied local deltas
|
||||||
|
Set(transform);
|
||||||
|
_lastFrameTransform = transform;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Add to the interpolation buffer
|
||||||
|
const float now = Time::Update.UnscaledTime.GetTotalSeconds();
|
||||||
|
_buffer.Add({ now, 0, transform });
|
||||||
|
if (_bufferHasDeltas)
|
||||||
|
{
|
||||||
|
_buffer.Clear();
|
||||||
|
_bufferHasDeltas = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkTransform::Set(const Transform& transform)
|
||||||
|
{
|
||||||
if (auto* parent = GetParent())
|
if (auto* parent = GetParent())
|
||||||
{
|
{
|
||||||
if (data.LocalSpace)
|
if (LocalSpace)
|
||||||
parent->SetLocalTransform(transform);
|
parent->SetLocalTransform(transform);
|
||||||
else
|
else
|
||||||
parent->SetTransform(transform);
|
parent->SetTransform(transform);
|
||||||
|
|||||||
@@ -3,20 +3,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Engine/Scripting/Script.h"
|
#include "Engine/Scripting/Script.h"
|
||||||
|
#include "Engine/Core/Math/Transform.h"
|
||||||
#include "Engine/Networking/INetworkSerializable.h"
|
#include "Engine/Networking/INetworkSerializable.h"
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Actor script component that synchronizes the Transform over the network.
|
/// Actor script component that synchronizes the Transform over the network.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>Interpolation and prediction logic based on https://www.gabrielgambetta.com/client-server-game-architecture.html.</remarks>
|
||||||
API_CLASS(Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkTransform : public Script, public INetworkSerializable
|
API_CLASS(Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkTransform : public Script, public INetworkSerializable
|
||||||
{
|
{
|
||||||
API_AUTO_SERIALIZATION();
|
API_AUTO_SERIALIZATION();
|
||||||
DECLARE_SCRIPTING_TYPE(NetworkTransform);
|
DECLARE_SCRIPTING_TYPE(NetworkTransform);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Actor transform synchronization modes (flags).
|
/// Actor transform replication components (flags).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_ENUM(Attributes="Flags") enum class SyncModes
|
API_ENUM(Attributes="Flags") enum class ReplicationComponents
|
||||||
{
|
{
|
||||||
// No sync.
|
// No sync.
|
||||||
None = 0,
|
None = 0,
|
||||||
@@ -52,27 +54,66 @@ API_CLASS(Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkTransfo
|
|||||||
All = Position | Scale | Rotation,
|
All = Position | Scale | Rotation,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actor transform replication modes.
|
||||||
|
/// </summary>
|
||||||
|
API_ENUM() enum class ReplicationModes
|
||||||
|
{
|
||||||
|
// The transform replicated from the owner (raw replication data messages that might result in sudden object jumps when moving).
|
||||||
|
Default,
|
||||||
|
// The transform replicated from the owner with local interpolation between received data to provide smoother movement.
|
||||||
|
Interpolation,
|
||||||
|
// The transform replicated from the owner but with local prediction (eg. player character that has local simulation but is validated against authoritative server).
|
||||||
|
Prediction,
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct BufferedItem
|
||||||
|
{
|
||||||
|
float Timestamp;
|
||||||
|
uint16 SequenceIndex;
|
||||||
|
Transform Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool _bufferHasDeltas;
|
||||||
|
uint16 _currentSequenceIndex = 0;
|
||||||
|
Transform _lastFrameTransform;
|
||||||
|
Array<BufferedItem> _buffer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If checked, actor transform will be synchronized in local space of the parent actor (otherwise in world space).
|
/// If checked, actor transform will be synchronized in local space of the parent actor (otherwise in world space).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD(Attributes="EditorOrder(10)")
|
API_FIELD(Attributes="EditorOrder(10)")
|
||||||
bool LocalSpace = false;
|
bool LocalSpace = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Actor transform synchronization mode (flags).
|
/// Actor transform replication components (flags).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD(Attributes="EditorOrder(20)")
|
API_FIELD(Attributes="EditorOrder(20)")
|
||||||
SyncModes SyncMode = SyncModes::All;
|
ReplicationComponents Components = ReplicationComponents::All;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actor transform replication mode.
|
||||||
|
/// </summary>
|
||||||
|
API_FIELD(Attributes="EditorOrder(30)")
|
||||||
|
ReplicationModes Mode = ReplicationModes::Default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
API_FUNCTION(Hidden, NetworkRpc=Server) void SetSequenceIndex(uint16 value);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// [Script]
|
// [Script]
|
||||||
void OnEnable() override;
|
void OnEnable() override;
|
||||||
void OnDisable() override;
|
void OnDisable() override;
|
||||||
|
void OnUpdate() override;
|
||||||
|
|
||||||
// [INetworkSerializable]
|
// [INetworkSerializable]
|
||||||
void Serialize(NetworkStream* stream) override;
|
void Serialize(NetworkStream* stream) override;
|
||||||
void Deserialize(NetworkStream* stream) override;
|
void Deserialize(NetworkStream* stream) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Set(const Transform& transform);
|
||||||
};
|
};
|
||||||
|
|
||||||
DECLARE_ENUM_OPERATORS(NetworkTransform::SyncModes);
|
DECLARE_ENUM_OPERATORS(NetworkTransform::ReplicationComponents);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Engine/Core/Log.h"
|
||||||
#include "Engine/Core/Types/StringView.h"
|
#include "Engine/Core/Types/StringView.h"
|
||||||
#include "Engine/Core/Types/Pair.h"
|
#include "Engine/Core/Types/Pair.h"
|
||||||
#include "Engine/Core/Collections/Array.h"
|
#include "Engine/Core/Collections/Array.h"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public class Networking : EngineModule
|
|||||||
{
|
{
|
||||||
base.Setup(options);
|
base.Setup(options);
|
||||||
|
|
||||||
|
Tags["Network"] = string.Empty;
|
||||||
options.PublicDefinitions.Add("COMPILE_WITH_NETWORKING");
|
options.PublicDefinitions.Add("COMPILE_WITH_NETWORKING");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user