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).
///