diff --git a/Source/Engine/Core/Collections/ChunkedArray.h b/Source/Engine/Core/Collections/ChunkedArray.h index 89c2bfe64..2069ac463 100644 --- a/Source/Engine/Core/Collections/ChunkedArray.h +++ b/Source/Engine/Core/Collections/ChunkedArray.h @@ -334,7 +334,7 @@ public: /// Removes the element at specified iterator position. /// /// The element iterator to remove. - void Remove(Iterator& i) + void Remove(const Iterator& i) { if (IsEmpty()) return; diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index feda96bd7..7211d0ecd 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -665,7 +665,7 @@ public: /// /// The element iterator to remove. /// True if cannot remove item from the collection because cannot find it, otherwise false. - bool Remove(Iterator& i) + bool Remove(const Iterator& i) { ASSERT(&i._collection == this); if (i) diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index 304d07926..a2cb21be7 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -496,7 +496,7 @@ public: /// /// The element iterator to remove. /// True if cannot remove item from the collection because cannot find it, otherwise false. - bool Remove(Iterator& i) + bool Remove(const Iterator& i) { ASSERT(&i._collection == this); if (i) diff --git a/Source/Engine/Networking/Components/NetworkTransform.cpp b/Source/Engine/Networking/Components/NetworkTransform.cpp new file mode 100644 index 000000000..df7550d2e --- /dev/null +++ b/Source/Engine/Networking/Components/NetworkTransform.cpp @@ -0,0 +1,169 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "NetworkTransform.h" +#include "Engine/Core/Math/Transform.h" +#include "Engine/Level/Actor.h" +#include "Engine/Networking/NetworkReplicator.h" +#include "Engine/Networking/NetworkStream.h" + +PACK_STRUCT(struct Data + { + uint8 LocalSpace : 1; + NetworkTransform::SyncModes SyncMode : 9; + }); + +static_assert((int32)NetworkTransform::SyncModes::All + 1 == 512, "Invalid SyncModes bit count for Data."); + +NetworkTransform::NetworkTransform(const SpawnParams& params) + : Script(params) +{ +} + +void NetworkTransform::OnEnable() +{ + // Register for replication + NetworkReplicator::AddObject(this); +} + +void NetworkTransform::OnDisable() +{ + // Unregister from replication + NetworkReplicator::RemoveObject(this); +} + +void NetworkTransform::Serialize(NetworkStream* stream) +{ + // Get transform + Transform transform; + if (const auto* parent = GetParent()) + transform = LocalSpace ? parent->GetLocalTransform() : parent->GetTransform(); + else + transform = Transform::Identity; + + // Encode data + Data data; + data.LocalSpace = LocalSpace; + data.SyncMode = SyncMode; + stream->Write(data); + if ((data.SyncMode & SyncModes::All) == (int)SyncModes::All) + { + stream->Write(transform); + } + else + { + if ((data.SyncMode & SyncModes::Position) == (int)SyncModes::Position) + { + stream->Write(transform.Translation); + } + else if (data.SyncMode & SyncModes::Position) + { + if (data.SyncMode & SyncModes::PositionX) + stream->Write(transform.Translation.X); + if (data.SyncMode & SyncModes::PositionY) + stream->Write(transform.Translation.X); + if (data.SyncMode & SyncModes::PositionZ) + stream->Write(transform.Translation.X); + } + if ((data.SyncMode & SyncModes::Scale) == (int)SyncModes::Scale) + { + stream->Write(transform.Scale); + } + else if (data.SyncMode & SyncModes::Scale) + { + if (data.SyncMode & SyncModes::ScaleX) + stream->Write(transform.Scale.X); + if (data.SyncMode & SyncModes::ScaleY) + stream->Write(transform.Scale.X); + if (data.SyncMode & SyncModes::ScaleZ) + stream->Write(transform.Scale.X); + } + if ((data.SyncMode & SyncModes::Rotation) == (int)SyncModes::Rotation) + { + const Float3 rotation = transform.Orientation.GetEuler(); + stream->Write(rotation); + } + else if (data.SyncMode & SyncModes::Rotation) + { + const Float3 rotation = transform.Orientation.GetEuler(); + if (data.SyncMode & SyncModes::RotationX) + stream->Write(rotation.X); + if (data.SyncMode & SyncModes::RotationY) + stream->Write(rotation.Y); + if (data.SyncMode & SyncModes::RotationZ) + stream->Write(rotation.Z); + } + } +} + +void NetworkTransform::Deserialize(NetworkStream* stream) +{ + // Get transform + Transform transform; + if (const auto* parent = GetParent()) + transform = LocalSpace ? parent->GetLocalTransform() : parent->GetTransform(); + else + transform = Transform::Identity; + + // Decode data + Data data; + stream->Read(data); + if ((data.SyncMode & SyncModes::All) == (int)SyncModes::All) + { + stream->Read(transform); + } + else + { + if ((data.SyncMode & SyncModes::Position) == (int)SyncModes::Position) + { + stream->Read(transform.Translation); + } + else if (data.SyncMode & SyncModes::Position) + { + if (data.SyncMode & SyncModes::PositionX) + stream->Read(transform.Translation.X); + if (data.SyncMode & SyncModes::PositionY) + stream->Read(transform.Translation.X); + if (data.SyncMode & SyncModes::PositionZ) + stream->Read(transform.Translation.X); + } + if ((data.SyncMode & SyncModes::Scale) == (int)SyncModes::Scale) + { + stream->Read(transform.Scale); + } + else if (data.SyncMode & SyncModes::Scale) + { + if (data.SyncMode & SyncModes::ScaleX) + stream->Read(transform.Scale.X); + if (data.SyncMode & SyncModes::ScaleY) + stream->Read(transform.Scale.X); + if (data.SyncMode & SyncModes::ScaleZ) + stream->Read(transform.Scale.X); + } + if ((data.SyncMode & SyncModes::Rotation) == (int)SyncModes::Rotation) + { + Float3 rotation; + stream->Read(rotation); + transform.Orientation = Quaternion::Euler(rotation); + } + else if (data.SyncMode & SyncModes::Rotation) + { + Float3 rotation = transform.Orientation.GetEuler(); + if (data.SyncMode & SyncModes::RotationX) + stream->Read(rotation.X); + if (data.SyncMode & SyncModes::RotationY) + stream->Read(rotation.Y); + if (data.SyncMode & SyncModes::RotationZ) + stream->Read(rotation.Z); + transform.Orientation = Quaternion::Euler(rotation); + } + } + + // Set transform + if (auto* parent = GetParent()) + { + if (data.LocalSpace) + parent->SetLocalTransform(transform); + else + parent->SetTransform(transform); + } +} diff --git a/Source/Engine/Networking/Components/NetworkTransform.h b/Source/Engine/Networking/Components/NetworkTransform.h new file mode 100644 index 000000000..375fc63b1 --- /dev/null +++ b/Source/Engine/Networking/Components/NetworkTransform.h @@ -0,0 +1,78 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/Script.h" +#include "Engine/Networking/INetworkSerializable.h" + +/// +/// Actor script component that synchronizes the Transform over the network. +/// +API_CLASS(Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkTransform : public Script, public INetworkSerializable +{ + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE(NetworkTransform); + + /// + /// Actor transform synchronization modes (flags). + /// + API_ENUM(Attributes="Flags") enum class SyncModes + { + // No sync. + None = 0, + + // Position X component. + PositionX = 1 << 0, + // Position Y component. + PositionY = 1 << 1, + // Position Z component. + PositionZ = 1 << 2, + // Position XYZ components (full). + Position = PositionX | PositionY | PositionZ, + + // Scale X component. + ScaleX = 1 << 3, + // Scale Y component. + ScaleY = 1 << 4, + // Scale Z component. + ScaleZ = 1 << 5, + // Scale XYZ components (full). + Scale = ScaleX | ScaleY | ScaleZ, + + // Position X component. + RotationX = 1 << 6, + // Position Y component. + RotationY = 1 << 7, + // Position Z component. + RotationZ = 1 << 8, + // Rotation XYZ components (full). + Rotation = RotationX | RotationY | RotationZ, + + // All components fully synchronized. + All = Position | Scale | Rotation, + }; + +public: + /// + /// If checked, actor transform will be synchronized in local space of the parent actor (otherwise in world space). + /// + API_FIELD(Attributes="EditorOrder(10)") + bool LocalSpace = false; + + /// + /// Actor transform synchronization mode (flags). + /// + API_FIELD(Attributes="EditorOrder(20)") + SyncModes SyncMode = SyncModes::All; + +public: + // [Script] + void OnEnable() override; + void OnDisable() override; + + // [INetworkSerializable] + void Serialize(NetworkStream* stream) override; + void Deserialize(NetworkStream* stream) override; +}; + +DECLARE_ENUM_OPERATORS(NetworkTransform::SyncModes); diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 055075201..af8a5de17 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -367,6 +367,20 @@ void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* parent) Objects.Add(MoveTemp(item)); } +void NetworkReplicator::RemoveObject(ScriptingObject* obj) +{ + if (!obj || NetworkManager::State == NetworkConnectionState::Offline) + return; + ScopeLock lock(ObjectsLock); + const auto it = Objects.Find(obj->GetID()); + if (it != Objects.End()) + return; + + // Remove object from the list + NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remove object {}, owned by {}", obj->GetID().ToString(), it->Item.ParentId.ToString()); + Objects.Remove(it); +} + void NetworkReplicator::SpawnObject(ScriptingObject* obj) { if (!obj || NetworkManager::State == NetworkConnectionState::Offline) @@ -507,10 +521,9 @@ void NetworkInternal::NetworkReplicatorClear() { // Cleanup any spawned objects DeleteNetworkObject(obj); + Objects.Remove(it); } } - Objects.Clear(); - Objects.SetCapacity(0); SpawnQueue.Clear(); DespawnQueue.Clear(); IdsRemappingTable.Clear(); diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index 4ec23805e..b2c272198 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -59,6 +59,13 @@ public: /// The parent of the object (eg. player that spawned it). API_FUNCTION() static void AddObject(ScriptingObject* obj, ScriptingObject* parent = nullptr); + /// + /// Removes the object from the network replication system. + /// + /// Does nothing if network is offline. + /// The object to don't replicate. + API_FUNCTION() static void RemoveObject(ScriptingObject* obj); + /// /// Spawns the object to the other clients. Can be spawned by the owner who locally created it (eg. from prefab). ///