From 4bdeb26e743d7b951c38f197c201b901447d48db Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 13 Apr 2023 13:06:39 +0200 Subject: [PATCH 01/28] Add `NetworkReplicator::EnableLog` to optionally enable verbose logging of networking --- Source/Engine/Networking/NetworkReplicator.cpp | 8 +++----- Source/Engine/Networking/NetworkReplicator.h | 7 +++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 7ab1a7c4f..0e989489a 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -29,12 +29,10 @@ #include "Engine/Threading/Threading.h" #include "Engine/Threading/ThreadLocal.h" -// Enables verbose logging for Network Replicator actions (dev-only) -#define NETWORK_REPLICATOR_DEBUG_LOG 0 - -#if NETWORK_REPLICATOR_DEBUG_LOG +#if !BUILD_RELEASE +bool NetworkReplicator::EnableLog = false; #include "Engine/Core/Log.h" -#define NETWORK_REPLICATOR_LOG(messageType, format, ...) LOG(messageType, format, ##__VA_ARGS__) +#define NETWORK_REPLICATOR_LOG(messageType, format, ...) if (NetworkReplicator::EnableLog) { LOG(messageType, format, ##__VA_ARGS__); } #else #define NETWORK_REPLICATOR_LOG(messageType, format, ...) #endif diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index 0957094d0..24cfddd97 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -34,6 +34,13 @@ API_CLASS(static, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API Netw typedef void (*SerializeFunc)(void* instance, NetworkStream* stream, void* tag); public: +#if !BUILD_RELEASE + /// + /// Enables verbose logging of the networking runtime. Can be used to debug problems of missing RPC invoke or object replication issues. + /// + API_FIELD() static bool EnableLog; +#endif + /// /// Adds the network replication serializer for a given type. /// From 62fdfe2519d7e282cdc6745e1071db17f2c64833 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Apr 2023 14:25:18 +0200 Subject: [PATCH 02/28] Add `NetworkStream::SenderId` to detect message sender during object replication or RPC code --- .../Components/NetworkTransform.cpp | 1 - .../Engine/Networking/NetworkReplicator.cpp | 26 ++++++++++++------- Source/Engine/Networking/NetworkStream.h | 5 ++++ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Networking/Components/NetworkTransform.cpp b/Source/Engine/Networking/Components/NetworkTransform.cpp index 58c6481f6..2d2334c46 100644 --- a/Source/Engine/Networking/Components/NetworkTransform.cpp +++ b/Source/Engine/Networking/Components/NetworkTransform.cpp @@ -156,7 +156,6 @@ void NetworkTransform::Serialize(NetworkStream* stream) transform = Transform::Identity; // Encode data - const NetworkObjectRole role = NetworkReplicator::GetObjectRole(this); Data data; data.LocalSpace = LocalSpace; data.HasSequenceIndex = Mode == ReplicationModes::Prediction; diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 0e989489a..36e37b0b0 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -147,6 +147,7 @@ struct ReplicateItem Guid ObjectId; uint16 PartsLeft; uint32 OwnerFrame; + uint32 OwnerClientId; Array Data; }; @@ -488,7 +489,7 @@ void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject* obj) } template -ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& msgData, uint16 partStart, uint16 partSize) +ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& msgData, uint16 partStart, uint16 partSize, uint32 senderClientId) { // Reuse or add part item ReplicateItem* replicateItem = nullptr; @@ -508,6 +509,7 @@ ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& ms replicateItem->ObjectId = msgData.ObjectId; replicateItem->PartsLeft = msgData.PartsCount; replicateItem->OwnerFrame = msgData.OwnerFrame; + replicateItem->OwnerClientId = senderClientId; replicateItem->Data.Resize(msgData.DataSize); } @@ -521,7 +523,7 @@ ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& ms return replicateItem; } -void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, byte* data, uint32 dataSize) +void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, byte* data, uint32 dataSize, uint32 senderClientId) { ScriptingObject* obj = item.Object.Get(); if (!obj) @@ -541,6 +543,7 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b CachedReadStream = New(); NetworkStream* stream = CachedReadStream; stream->Initialize(data, dataSize); + stream->SenderId = senderClientId; // Deserialize object const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, false); @@ -879,6 +882,7 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC() if (CachedWriteStream == nullptr) CachedWriteStream = New(); CachedWriteStream->Initialize(); + CachedWriteStream->SenderId = NetworkManager::LocalClientId; return CachedWriteStream; } @@ -981,12 +985,9 @@ void NetworkInternal::NetworkReplicatorUpdate() ScopeLock lock(ObjectsLock); if (Objects.Count() == 0) return; - if (CachedWriteStream == nullptr) - CachedWriteStream = New(); const bool isClient = NetworkManager::IsClient(); const bool isServer = NetworkManager::IsServer(); const bool isHost = NetworkManager::IsHost(); - NetworkStream* stream = CachedWriteStream; NetworkPeer* peer = NetworkManager::Peer; if (!isClient && NewClients.Count() != 0) @@ -1137,7 +1138,7 @@ void NetworkInternal::NetworkReplicatorUpdate() auto& item = it->Item; // Replicate from all collected parts data - InvokeObjectReplication(item, e.OwnerFrame, e.Data.Get(), e.Data.Count()); + InvokeObjectReplication(item, e.OwnerFrame, e.Data.Get(), e.Data.Count(), e.OwnerClientId); } } @@ -1145,6 +1146,10 @@ void NetworkInternal::NetworkReplicatorUpdate() } // Brute force synchronize all networked objects with clients + if (CachedWriteStream == nullptr) + CachedWriteStream = New(); + NetworkStream* stream = CachedWriteStream; + stream->SenderId = NetworkManager::LocalClientId; // TODO: introduce NetworkReplicationHierarchy to optimize objects replication in large worlds (eg. batched culling networked scene objects that are too far from certain client to be relevant) // TODO: per-object sync interval (in frames) - could be scaled by hierarchy (eg. game could slow down sync rate for objects far from player) for (auto it = Objects.Begin(); it.IsNotEnd(); ++it) @@ -1305,16 +1310,17 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo if (client && item.OwnerClientId != client->ClientId) return; + const uint32 senderClientId = client ? client->ClientId : NetworkManager::LocalClientId; if (msgData.PartsCount == 1) { // Replicate - InvokeObjectReplication(item, msgData.OwnerFrame, event.Message.Buffer + event.Message.Position, msgData.DataSize); + InvokeObjectReplication(item, msgData.OwnerFrame, event.Message.Buffer + event.Message.Position, msgData.DataSize, senderClientId); } else { // Add to replication from multiple parts const uint16 msgMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicate); - ReplicateItem* replicateItem = AddObjectReplicateItem(event, msgData, 0, msgMaxData); + ReplicateItem* replicateItem = AddObjectReplicateItem(event, msgData, 0, msgMaxData, senderClientId); replicateItem->Object = e->Object; } } @@ -1327,7 +1333,8 @@ void NetworkInternal::OnNetworkMessageObjectReplicatePart(NetworkEvent& event, N if (DespawnedObjects.Contains(msgData.ObjectId)) return; // Skip replicating not-existing objects - AddObjectReplicateItem(event, msgData, msgData.PartStart, msgData.PartSize); + const uint32 senderClientId = client ? client->ClientId : NetworkManager::LocalClientId; + AddObjectReplicateItem(event, msgData, msgData.PartStart, msgData.PartSize, senderClientId); } void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) @@ -1620,6 +1627,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie if (CachedReadStream == nullptr) CachedReadStream = New(); NetworkStream* stream = CachedReadStream; + stream->SenderId = client ? client->ClientId : NetworkManager::LocalClientId; stream->Initialize(event.Message.Buffer + event.Message.Position, msgData.ArgsSize); // Execute RPC diff --git a/Source/Engine/Networking/NetworkStream.h b/Source/Engine/Networking/NetworkStream.h index 190bd0dcb..fa91d4912 100644 --- a/Source/Engine/Networking/NetworkStream.h +++ b/Source/Engine/Networking/NetworkStream.h @@ -23,6 +23,11 @@ private: public: ~NetworkStream(); + /// + /// The ClientId of the network client that is a data sender. Can be used to detect who send the incoming RPC or replication data. Set to the current client when writing data. + /// + API_FIELD(ReadOnly) uint32 SenderId = 0; + /// /// Gets the pointer to the native stream memory buffer. /// From 89704bebe9bd78d167ab0656caa06e23ab10a0a0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Apr 2023 15:01:22 +0200 Subject: [PATCH 03/28] Add `Span` support for scripting fields --- Source/Engine/Core/Types/Span.h | 2 +- Source/Engine/Scripting/ManagedCLR/MUtils.h | 4 ++-- .../Bindings/BindingsGenerator.Cpp.cs | 17 ++++++++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Core/Types/Span.h b/Source/Engine/Core/Types/Span.h index d0cbd7993..678550e85 100644 --- a/Source/Engine/Core/Types/Span.h +++ b/Source/Engine/Core/Types/Span.h @@ -8,7 +8,7 @@ /// Universal representation of a contiguous region of arbitrary memory. /// template -class Span +API_CLASS(InBuild) class Span { protected: T* _data; diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index e33e22719..7f7480b0a 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -499,8 +499,8 @@ namespace MUtils /// /// The native array object. /// The output array pointer and size. - template - FORCE_INLINE Span ToSpan(const Array& data) + template + FORCE_INLINE Span ToSpan(const Array& data) { return Span(data.Get(), data.Count()); } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 56419d87c..9acaf1f9c 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -172,6 +172,10 @@ namespace Flax.Build.Bindings CppVariantFromTypes[wrapperName] = typeInfo; return $"VariantFrom{wrapperName}Dictionary({value})"; } + if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null) + { + return "Variant()"; // TODO: Span to Variant converting (use utility method the same way as for arrays) + } var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) @@ -218,6 +222,10 @@ namespace Flax.Build.Bindings CppVariantToTypes.Add(typeInfo); return $"MoveTemp(VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}({value}))"; } + if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null) + { + return $"{typeInfo}()"; // Cannot be implemented since Variant stores array of Variants thus cannot get linear span of data + } var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) @@ -482,13 +490,20 @@ namespace Flax.Build.Bindings return "{0}.GetManagedInstance()"; } - // Array or Span or DataContainer + // Array or DataContainer if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) { type = "MonoArray*"; return "MUtils::ToArray({0}, " + GenerateCppGetNativeClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")"; } + // Span + if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null) + { + type = "MonoArray*"; + return "MUtils::Span({0}, " + GenerateCppGetNativeClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")"; + } + // BytesContainer if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null) { From b4d20e128b11fcb6e2416ac4824286877e327973 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Apr 2023 15:01:40 +0200 Subject: [PATCH 04/28] Add `Span` to C++ debugger natvis file --- Source/flax.natvis | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/flax.natvis b/Source/flax.natvis index 750fd68f5..6942745c2 100644 --- a/Source/flax.natvis +++ b/Source/flax.natvis @@ -221,4 +221,16 @@ Tag={TagsListDebug[Index - 1]} + + + {{ Length={_length} }} + + _length + + _length + _data + + + + From a52b352bd96fdba3eccb9457abd8b86f19d30eca Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Apr 2023 15:03:16 +0200 Subject: [PATCH 05/28] Add `NetworkManager::GetClient` by `uint32 clientId` --- Source/Engine/Networking/NetworkManager.cpp | 10 ++++++++++ Source/Engine/Networking/NetworkManager.h | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index c2b6fd2da..88a104c3e 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -236,6 +236,16 @@ NetworkClient* NetworkManager::GetClient(const NetworkConnection& connection) return nullptr; } +NetworkClient* NetworkManager::GetClient(uint32 clientId) +{ + for (NetworkClient* client : Clients) + { + if (client->ClientId == clientId) + return client; + } + return nullptr; +} + bool NetworkManager::StartServer() { PROFILE_CPU(); diff --git a/Source/Engine/Networking/NetworkManager.h b/Source/Engine/Networking/NetworkManager.h index a634217c1..81d607a65 100644 --- a/Source/Engine/Networking/NetworkManager.h +++ b/Source/Engine/Networking/NetworkManager.h @@ -153,6 +153,13 @@ public: /// Found client or null. API_FUNCTION() static NetworkClient* GetClient(API_PARAM(Ref) const NetworkConnection& connection); + /// + /// Gets the network client with a given identifier. Returns null if failed to find it. + /// + /// Network client identifier (synchronized on all peers). + /// Found client or null. + API_FUNCTION() static NetworkClient* GetClient(uint32 clientId); + public: /// /// Starts the network in server mode. Returns true if failed (eg. invalid config). From c4203182670c58749e6d643b7fa7f9576264c423 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 14 Apr 2023 09:18:06 -0500 Subject: [PATCH 06/28] Fix bug with path name preventing the item to be renamed when duplicated. --- Source/Editor/Windows/ContentWindow.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 654eaa91d..f26e2e097 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -536,7 +536,7 @@ namespace FlaxEditor.Windows destinationName = StringUtils.IncrementNameNumber(item.ShortName, x => !File.Exists(StringUtils.CombinePaths(sourceFolder, x + extension))) + extension; } - return StringUtils.CombinePaths(sourceFolder, destinationName); + return StringUtils.NormalizePath(StringUtils.CombinePaths(sourceFolder, destinationName)); } /// @@ -563,6 +563,7 @@ namespace FlaxEditor.Windows // Start renaming it if (targetItem != null) { + Select(targetItem); Rename(targetItem); } } From d813078e91062f031520ec55d8745a81b4519171 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 14 Apr 2023 12:55:40 -0500 Subject: [PATCH 07/28] Made it so the visuals of dragging an actor will only show between the nodes and on the node. --- Source/Editor/GUI/Tree/Tree.cs | 5 +++++ Source/Editor/GUI/Tree/TreeNode.cs | 7 ++++--- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index ff99f6506..3a9780ed9 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -40,6 +40,11 @@ namespace FlaxEditor.GUI.Tree private Margin _margin; private bool _autoSize = true; + /// + /// The TreeNode that is being dragged over. This could have a value when not dragging. + /// + public TreeNode DraggedOverNode = null; + /// /// Action fired when tree nodes selection gets changed. /// diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index f2c8e2d6a..cd4fc9647 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -659,7 +659,7 @@ namespace FlaxEditor.GUI.Tree Render2D.DrawText(TextFont.GetFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); // Draw drag and drop effect - if (IsDragOver) + if (IsDragOver && _tree.DraggedOverNode == this) { Color dragOverColor = style.BackgroundSelected * 0.6f; Rectangle rect; @@ -669,10 +669,10 @@ namespace FlaxEditor.GUI.Tree rect = textRect; break; case DragItemPositioning.Above: - rect = new Rectangle(textRect.X, textRect.Y - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, textRect.Width, DefaultDragInsertPositionMargin * 2.0f); + rect = new Rectangle(textRect.X, textRect.Top - DefaultDragInsertPositionMargin - DefaultNodeOffsetY - _margin.Top, textRect.Width, DefaultDragInsertPositionMargin * 2.0f); break; case DragItemPositioning.Below: - rect = new Rectangle(textRect.X, textRect.Bottom - DefaultDragInsertPositionMargin, textRect.Width, DefaultDragInsertPositionMargin * 2.0f); + rect = new Rectangle(textRect.X, textRect.Bottom + _margin.Bottom - DefaultDragInsertPositionMargin, textRect.Width, DefaultDragInsertPositionMargin * 2.0f); break; default: rect = Rectangle.Empty; @@ -922,6 +922,7 @@ namespace FlaxEditor.GUI.Tree if (result == DragDropEffect.None) { UpdateDrawPositioning(ref location); + _tree.DraggedOverNode = this; // Check if mouse is over header _isDragOverHeader = TestHeaderHit(ref location); diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 6b6ea2f20..af81cdb98 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -706,6 +706,8 @@ namespace FlaxEditor.SceneGraph.GUI { DragData data; var tree = ParentTree; + if (tree.Selection.Count == 1) + Select(); // Check if this node is selected if (tree.Selection.Contains(this)) From 62a335fab0126aef8906551ee7c2551caacce4dd Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 14 Apr 2023 13:34:43 -0500 Subject: [PATCH 08/28] Fix bug of re-parenting actors if children and parent actors are selected --- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index af81cdb98..db05749b9 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -717,6 +717,11 @@ namespace FlaxEditor.SceneGraph.GUI for (var i = 0; i < tree.Selection.Count; i++) { var e = tree.Selection[i]; + + // Skip if parent is already selected to keep correct parenting + if (tree.Selection.Contains(e.Parent)) + continue; + if (e is ActorTreeNode node && node.ActorNode.CanDrag) actors.Add(node.ActorNode); } From e8aa2f922b41864f45749950ab75a86a5d91dc6e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 14 Apr 2023 15:00:50 -0500 Subject: [PATCH 09/28] Allow word wrapping to wrap on capital letters and underscores. --- Source/Engine/Render2D/Font.cpp | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 0cbc30701..d80f7d19c 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -122,6 +122,12 @@ void Font::ProcessText(const StringView& text, Array& outputLines float lastWhitespaceX = 0; bool lastMoveLine = false; + int32 lastUpperIndex = INVALID_INDEX; + float lastUpperX = 0; + + int32 lastUnderscoreIndex = INVALID_INDEX; + float lastUnderscoreX = 0; + // Process each character to split text into single lines for (int32 currentIndex = 0; currentIndex < textLength;) { @@ -141,6 +147,21 @@ void Font::ProcessText(const StringView& text, Array& outputLines lastWhitespaceX = cursorX; } + // Check if character is an upper case letter + const bool isUpper = StringUtils::IsUpper(currentChar); + if (isUpper && currentIndex != 0) + { + lastUpperIndex = currentIndex; + lastUpperX = cursorX; + } + + const bool isUnderscore = currentChar == '_'; + if (isUnderscore) + { + lastUnderscoreIndex = currentIndex; + lastUnderscoreX = cursorX; + } + // Check if it's a newline character if (currentChar == '\n') { @@ -185,6 +206,20 @@ void Font::ProcessText(const StringView& text, Array& outputLines currentIndex = lastWhitespaceIndex + 1; nextCharIndex = currentIndex; } + else if (lastUpperIndex != INVALID_INDEX) + { + cursorX = lastUpperX; + tmpLine.LastCharIndex = lastUpperIndex - 1; + currentIndex = lastUpperIndex; + nextCharIndex = currentIndex; + } + else if (lastUnderscoreIndex != INVALID_INDEX) + { + cursorX = lastUnderscoreX; + tmpLine.LastCharIndex = lastUnderscoreIndex; + currentIndex = lastUnderscoreIndex + 1; + nextCharIndex = currentIndex; + } else { nextCharIndex = currentIndex; @@ -224,6 +259,12 @@ void Font::ProcessText(const StringView& text, Array& outputLines lastWhitespaceIndex = INVALID_INDEX; lastWhitespaceX = 0; + lastUpperIndex = INVALID_INDEX; + lastUpperX = 0; + + lastUnderscoreIndex = INVALID_INDEX; + lastUnderscoreX = 0; + previous.IsValid = false; } From da857714729d6f2a7606c3fe1f9f28dcf15ebbbb Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 14 Apr 2023 15:07:06 -0500 Subject: [PATCH 10/28] Small fixes --- Source/Engine/Render2D/Font.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index d80f7d19c..5881c2322 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -124,7 +124,7 @@ void Font::ProcessText(const StringView& text, Array& outputLines int32 lastUpperIndex = INVALID_INDEX; float lastUpperX = 0; - + int32 lastUnderscoreIndex = INVALID_INDEX; float lastUnderscoreX = 0; @@ -155,6 +155,7 @@ void Font::ProcessText(const StringView& text, Array& outputLines lastUpperX = cursorX; } + // Check if character is an underscore const bool isUnderscore = currentChar == '_'; if (isUnderscore) { From acecda482d8eb13338036a310e9f12bcb8b20beb Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 14 Apr 2023 15:29:02 -0500 Subject: [PATCH 11/28] Changes the play icon to a stop icon and vice versa when clicked for the profiler. --- Source/Editor/Windows/Profiler/ProfilerWindow.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs index 40bf2a9c2..deffd00bb 100644 --- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs +++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs @@ -91,6 +91,7 @@ namespace FlaxEditor.Windows.Profiler _liveRecordingButton = toolstrip.AddButton(editor.Icons.Play64); _liveRecordingButton.LinkTooltip("Live profiling events recording"); _liveRecordingButton.AutoCheck = true; + _liveRecordingButton.Clicked += () => _liveRecordingButton.Icon = LiveRecording ? editor.Icons.Stop64 : editor.Icons.Play64; _clearButton = toolstrip.AddButton(editor.Icons.Rotate32, Clear); _clearButton.LinkTooltip("Clear data"); toolstrip.AddSeparator(); From 4e5795ab910578b9165f6107e54d31477fb77aac Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 14 Apr 2023 16:21:19 -0500 Subject: [PATCH 12/28] Changed `Cook&Run` to `Cook & Run`. --- Source/Editor/Modules/UIModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 9f8167731..858ef2b1a 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -526,7 +526,7 @@ namespace FlaxEditor.Modules _menuGamePlay = cm.AddButton("Play", inputOptions.Play.ToString(), Editor.Simulation.RequestStartPlay); _menuGamePause = cm.AddButton("Pause", inputOptions.Pause.ToString(), Editor.Simulation.RequestPausePlay); cm.AddSeparator(); - cm.AddButton("Cook&Run", Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after."); + cm.AddButton("Cook & Run", Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after."); cm.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first."); // Tools From e4804db160c2783266a241cd7bf0b8e4e9813e22 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 15 Apr 2023 12:04:40 +0200 Subject: [PATCH 13/28] Add `NetworkRpcParams` for sending RPC to specific set of clients or to read sender id --- .../Engine/Networking/NetworkReplicator.cpp | 68 ++++++++++++++++--- Source/Engine/Networking/NetworkReplicator.cs | 5 +- Source/Engine/Networking/NetworkReplicator.h | 6 +- Source/Engine/Networking/NetworkRpc.h | 19 ++++++ Source/Engine/Networking/Types.h | 1 + .../Build/Plugins/NetworkingPlugin.cs | 63 +++++++++++++++-- 6 files changed, 143 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 36e37b0b0..98c7c2432 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -178,6 +178,7 @@ struct RpcItem NetworkRpcName Name; NetworkRpcInfo Info; BytesContainer ArgsData; + DataContainer Targets; }; namespace @@ -329,6 +330,46 @@ void BuildCachedTargets(const Array& clients, const DataContaine } } +void BuildCachedTargets(const Array& clients, const DataContainer& clientIds1, const Span& clientIds2, const uint32 excludedClientId = NetworkManager::ServerClientId) +{ + CachedTargets.Clear(); + if (clientIds1.IsValid()) + { + if (clientIds2.IsValid()) + { + for (const NetworkClient* client : clients) + { + if (client->State == NetworkConnectionState::Connected && client->ClientId != excludedClientId) + { + for (int32 i = 0; i < clientIds1.Length(); i++) + { + if (clientIds1[i] == client->ClientId) + { + for (int32 j = 0; j < clientIds2.Length(); j++) + { + if (clientIds2[j] == client->ClientId) + { + CachedTargets.Add(client->Connection); + break; + } + } + break; + } + } + } + } + } + else + { + BuildCachedTargets(clients, clientIds1, excludedClientId); + } + } + else + { + BuildCachedTargets(clients, clientIds2, excludedClientId); + } +} + FORCE_INLINE void BuildCachedTargets(const NetworkReplicatedObject& item) { // By default send object to all connected clients excluding the owner but with optional TargetClientIds list @@ -560,6 +601,11 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b DirtyObjectImpl(item, obj); } +NetworkRpcParams::NetworkRpcParams(const NetworkStream* stream) + : SenderId(stream->SenderId) +{ +} + #if !COMPILE_WITHOUT_CSHARP #include "Engine/Scripting/ManagedCLR/MUtils.h" @@ -601,9 +647,9 @@ void NetworkReplicator::AddRPC(const ScriptingTypeHandle& typeHandle, const Stri NetworkRpcInfo::RPCsTable[rpcName] = rpcInfo; } -void NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream) +void NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MonoArray* targetIds) { - EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream); + EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan(targetIds)); } StringAnsiView NetworkReplicator::GetCSharpCachedName(const StringAnsiView& name) @@ -886,7 +932,7 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC() return CachedWriteStream; } -void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream) +void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds) { const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name)); if (!info || !obj || NetworkManager::IsOffline()) @@ -897,8 +943,8 @@ void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa rpc.Name.First = type; rpc.Name.Second = name; rpc.Info = *info; - const Span argsData(argsStream->GetBuffer(), argsStream->GetPosition()); - rpc.ArgsData.Copy(argsData); + rpc.ArgsData.Copy(Span(argsStream->GetBuffer(), argsStream->GetPosition())); + rpc.Targets.Copy(targetIds); #if USE_EDITOR || !BUILD_RELEASE auto it = Objects.Find(obj->GetID()); if (it == Objects.End()) @@ -1279,12 +1325,16 @@ void NetworkInternal::NetworkReplicatorUpdate() if (e.Info.Server && isClient) { // Client -> Server +#if !BUILD_RELEASE + if (e.Targets.Length() != 0) + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Server RPC '{}::{}' called with non-empty list of targets is not supported (only server will receive it)", e.Name.First.ToString(), e.Name.Second.ToString()); +#endif peer->EndSendMessage(channel, msg); } else if (e.Info.Client && (isServer || isHost)) { // Server -> Client(s) - BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, NetworkManager::LocalClientId); + BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, e.Targets, NetworkManager::LocalClientId); peer->EndSendMessage(channel, msg, CachedTargets); } } @@ -1310,7 +1360,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo if (client && item.OwnerClientId != client->ClientId) return; - const uint32 senderClientId = client ? client->ClientId : NetworkManager::LocalClientId; + const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId; if (msgData.PartsCount == 1) { // Replicate @@ -1333,7 +1383,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicatePart(NetworkEvent& event, N if (DespawnedObjects.Contains(msgData.ObjectId)) return; // Skip replicating not-existing objects - const uint32 senderClientId = client ? client->ClientId : NetworkManager::LocalClientId; + const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId; AddObjectReplicateItem(event, msgData, msgData.PartStart, msgData.PartSize, senderClientId); } @@ -1627,7 +1677,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie if (CachedReadStream == nullptr) CachedReadStream = New(); NetworkStream* stream = CachedReadStream; - stream->SenderId = client ? client->ClientId : NetworkManager::LocalClientId; + stream->SenderId = client ? client->ClientId : NetworkManager::ServerClientId; stream->Initialize(event.Message.Buffer + event.Message.Position, msgData.ArgsSize); // Execute RPC diff --git a/Source/Engine/Networking/NetworkReplicator.cs b/Source/Engine/Networking/NetworkReplicator.cs index dc2bc77c8..45fc45ba1 100644 --- a/Source/Engine/Networking/NetworkReplicator.cs +++ b/Source/Engine/Networking/NetworkReplicator.cs @@ -109,10 +109,11 @@ namespace FlaxEngine.Networking /// The RPC type. /// The RPC name. /// The RPC serialized arguments stream returned from BeginInvokeRPC. + /// Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs. [Unmanaged] - public static void EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream) + public static void EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream, uint[] targetIds = null) { - Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream)); + Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream), targetIds); } /// diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index 24cfddd97..e0893cc64 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -3,6 +3,7 @@ #pragma once #include "Types.h" +#include "Engine/Core/Types/Span.h" #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingType.h" @@ -175,13 +176,14 @@ public: /// The RPC type. /// The RPC name. /// The RPC serialized arguments stream returned from BeginInvokeRPC. - static void EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream); + /// Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs. + static void EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds = Span()); private: #if !COMPILE_WITHOUT_CSHARP API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& typeHandle, const Function& serialize, const Function& deserialize); API_FUNCTION(NoProxy) static void AddRPC(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, const Function& execute, bool isServer, bool isClient, NetworkChannelType channel); - API_FUNCTION(NoProxy) static void CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream); + API_FUNCTION(NoProxy) static void CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MonoArray* targetIds); static StringAnsiView GetCSharpCachedName(const StringAnsiView& name); #endif }; diff --git a/Source/Engine/Networking/NetworkRpc.h b/Source/Engine/Networking/NetworkRpc.h index dc485c3bb..c0c8a558e 100644 --- a/Source/Engine/Networking/NetworkRpc.h +++ b/Source/Engine/Networking/NetworkRpc.h @@ -5,6 +5,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/Pair.h" +#include "Engine/Core/Types/Span.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Scripting/ScriptingType.h" @@ -12,6 +13,24 @@ class NetworkStream; +// Additional context parameters for Network RPC execution (eg. to identify who sends the data). +API_STRUCT(NoDefault, Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkRpcParams +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkRpcParams); + NetworkRpcParams() = default; + NetworkRpcParams(const NetworkStream* stream); + + /// + /// The ClientId of the network client that is a data sender. Can be used to detect who send the incoming RPC or replication data. Ignored when sending data. + /// + API_FIELD() uint32 SenderId = 0; + + /// + /// The list of ClientId of the network clients that should receive RPC. Can be used to send RPC to a specific client(s). Ignored when receiving data. + /// + API_FIELD() Span TargetIds; +}; + // Network RPC identifier name (pair of type and function name) typedef Pair NetworkRpcName; diff --git a/Source/Engine/Networking/Types.h b/Source/Engine/Networking/Types.h index 5dd9fc140..11675023a 100644 --- a/Source/Engine/Networking/Types.h +++ b/Source/Engine/Networking/Types.h @@ -17,3 +17,4 @@ struct NetworkConnection; struct NetworkMessage; struct NetworkConfig; struct NetworkDriverStats; +struct NetworkRpcParams; diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 40f86ef66..1266e387d 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -13,7 +13,7 @@ using Mono.Cecil.Cil; namespace Flax.Build.Plugins { /// - /// Flax.Build plugin for Networking extenrions support. Generates required bindings glue code for automatic types replication and RPCs invoking. + /// Flax.Build plugin for Networking extensions support. Generates required bindings glue code for automatic types replication and RPCs invoking. /// /// internal sealed class NetworkingPlugin : Plugin @@ -231,9 +231,16 @@ namespace Flax.Build.Plugins var arg = functionInfo.Parameters[i]; if (i != 0) argNames += ", "; - argNames += arg.Name; - + + // Special handling of Rpc Params + if (!arg.Type.IsPtr && arg.Type.Type == "NetworkRpcParams") + { + argNames += "NetworkRpcParams(stream)"; + continue; + } + // Deserialize arguments + argNames += arg.Name; contents.AppendLine($" {arg.Type.Type} {arg.Name};"); contents.AppendLine($" stream->Read({arg.Name});"); } @@ -250,16 +257,24 @@ namespace Flax.Build.Plugins contents.Append(" static void ").Append(functionInfo.Name).AppendLine("_Invoke(ScriptingObject* obj, void** args)"); contents.AppendLine(" {"); contents.AppendLine(" NetworkStream* stream = NetworkReplicator::BeginInvokeRPC();"); + contents.AppendLine(" Span targetIds;"); for (int i = 0; i < functionInfo.Parameters.Count; i++) { var arg = functionInfo.Parameters[i]; + // Special handling of Rpc Params + if (!arg.Type.IsPtr && arg.Type.Type == "NetworkRpcParams") + { + contents.AppendLine($" targetIds = ((NetworkRpcParams*)args[{i}])->TargetIds;"); + continue; + } + // Serialize arguments contents.AppendLine($" stream->Write(*({arg.Type.Type}*)args[{i}]);"); } // Invoke RPC - contents.AppendLine($" NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream);"); + contents.AppendLine($" NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream, targetIds);"); contents.AppendLine(" }"); } contents.AppendLine(); @@ -1403,6 +1418,22 @@ namespace Flax.Build.Plugins { var parameter = method.Parameters[i]; var parameterType = parameter.ParameterType; + + // Special handling of Rpc Params + if (string.Equals(parameterType.FullName, "FlaxEngine.Networking.NetworkRpcParams", StringComparison.OrdinalIgnoreCase)) + { + // new NetworkRpcParams { SenderId = networkStream.SenderId } + il.Emit(OpCodes.Ldloca_S, (byte)(argsStart + i)); + il.Emit(OpCodes.Initobj, parameterType); + il.Emit(OpCodes.Ldloca_S, (byte)(argsStart + i)); + il.Emit(OpCodes.Ldloc_1); + var getSenderId = networkStreamType.Resolve().GetMethod("get_SenderId"); + il.Emit(OpCodes.Callvirt, module.ImportReference(getSenderId)); + var senderId = parameterType.Resolve().GetField("SenderId"); + il.Emit(OpCodes.Stfld, module.ImportReference(senderId)); + continue; + } + GenerateDotNetRPCSerializerType(ref context, type, false, argsStart + i, parameterType, il, networkStream.Resolve(), 1, null); } @@ -1432,6 +1463,8 @@ namespace Flax.Build.Plugins il.Body.InitLocals = true; var varsStart = il.Body.Variables.Count; + il.InsertBefore(ilStart, il.Create(OpCodes.Nop)); + // Is Server/Is Client boolean constants il.Body.Variables.Add(new VariableDefinition(module.ImportReference(boolType))); // [0] il.Body.Variables.Add(new VariableDefinition(module.ImportReference(boolType))); // [1] @@ -1476,14 +1509,25 @@ namespace Flax.Build.Plugins il.InsertBefore(ilStart, il.Create(OpCodes.Stloc, streamLocalIndex)); // stream loc=3 // Serialize all RPC parameters + var targetIdsArgIndex = -1; + FieldDefinition targetIdsField = null; for (int i = 0; i < method.Parameters.Count; i++) { var parameter = method.Parameters[i]; var parameterType = parameter.ParameterType; + + // Special handling of Rpc Params + if (string.Equals(parameterType.FullName, "FlaxEngine.Networking.NetworkRpcParams", StringComparison.OrdinalIgnoreCase)) + { + targetIdsArgIndex = i + 1; // NetworkRpcParams value argument index (starts at 1, 0 holds this) + targetIdsField = parameterType.Resolve().GetField("TargetIds"); + continue; + } + GenerateDotNetRPCSerializerType(ref context, type, true, i + 1, parameterType, il, networkStream.Resolve(), streamLocalIndex, ilStart); } - // NetworkReplicator.EndInvokeRPC(this, typeof(), "", stream); + // NetworkReplicator.EndInvokeRPC(this, typeof(), "", stream, targetIds); il.InsertBefore(ilStart, il.Create(OpCodes.Nop)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldarg_0)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldtoken, type)); @@ -1492,7 +1536,14 @@ namespace Flax.Build.Plugins il.InsertBefore(ilStart, il.Create(OpCodes.Call, module.ImportReference(getTypeFromHandle))); il.InsertBefore(ilStart, il.Create(OpCodes.Ldstr, method.Name)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, streamLocalIndex)); - var endInvokeRPC = networkReplicatorType.Resolve().GetMethod("EndInvokeRPC", 4); + if (targetIdsArgIndex != -1) + { + il.InsertBefore(ilStart, il.Create(OpCodes.Ldarg, targetIdsArgIndex)); + il.InsertBefore(ilStart, il.Create(OpCodes.Ldfld, module.ImportReference(targetIdsField))); + } + else + il.InsertBefore(ilStart, il.Create(OpCodes.Ldnull)); + var endInvokeRPC = networkReplicatorType.Resolve().GetMethod("EndInvokeRPC", 5); il.InsertBefore(ilStart, il.Create(OpCodes.Call, module.ImportReference(endInvokeRPC))); // if (server && networkMode == NetworkManagerMode.Client) return; From 2d51332bf73d764becddba75aa105974bd2cfc35 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 15 Apr 2023 12:05:33 +0200 Subject: [PATCH 14/28] Rename networking codegen initializer to `NetworkingPlugin` for C# netcode --- Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 1266e387d..758b86e3b 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -573,10 +573,9 @@ namespace Flax.Build.Plugins if (context.AddSerializers.Count != 0 || context.MethodRPCs.Count != 0) { // Create class - var name = "Initializer"; - var idx = 0; - while (module.Types.Any(x => x.Name == name)) - name = "Initializer" + idx++; + var name = "NetworkingPlugin"; + if (module.Types.Any(x => x.Name == name)) + throw new Exception($"Failed to generate network replication for assembly '{Path.GetFileName(assemblyPath)}' that already has net code generated. Rebuild project."); var c = new TypeDefinition("", name, TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit); module.GetType("System.Object", out var objectType); c.BaseType = module.ImportReference(objectType); From 49e05bc206ed9407e33f5fbcb0c0a3b1bca9471e Mon Sep 17 00:00:00 2001 From: PrecisionRender Date: Sat, 15 Apr 2023 12:53:19 -0500 Subject: [PATCH 15/28] Add `ClampLength` functions to C++ Vector3 --- Source/Engine/Core/Math/Vector3.h | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index f2547f47d..76b9aa2f8 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -566,6 +566,58 @@ public: result = Vector3Base(Math::Clamp(v.X, min.X, max.X), Math::Clamp(v.Y, min.Y, max.Y), Math::Clamp(v.Z, min.Z, max.Z)); } + /// + /// Makes sure that Length of the output vector is always below max and above 0. + /// + /// Input Vector. + /// Max Length + static Vector3Base ClampLength(const Vector3Base& v, float max) + { + return ClampLength(v, 0, max); + } + + /// + /// Makes sure that Length of the output vector is always below max and above min. + /// + /// Input Vector. + /// Min Length + /// Max Length + static Vector3Base ClampLength(const Vector3Base& v, float min, float max) + { + Vector3Base result; + ClampLength(v, min, max, result); + return result; + } + + /// + /// Makes sure that Length of the output vector is always below max and above min. + /// + /// Input Vector. + /// Min Length + /// Max Length + /// The result vector. + static void ClampLength(const Vector3Base& v, float min, float max, Vector3Base& result) + { + result.X = v.X; + result.Y = v.Y; + result.Z = v.Z; + auto lenSq = result.LengthSquared(); + if (lenSq > max * max) + { + auto scaleFactor = max / (float)Math::Sqrt(lenSq); + result.X *= scaleFactor; + result.Y *= scaleFactor; + result.Z *= scaleFactor; + } + if (lenSq < min * min) + { + auto scaleFactor = min / (float)Math::Sqrt(lenSq); + result.X *= scaleFactor; + result.Y *= scaleFactor; + result.Z *= scaleFactor; + } + } + // Calculates the distance between two vectors // @param a The first vector // @param b The second vector From dbd5c713445a11f8a377eefffa3467fb451af204 Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Sat, 15 Apr 2023 22:32:52 +0300 Subject: [PATCH 16/28] Fix RMB deletion to take selection into account --- Source/Editor/Windows/ContentWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 654eaa91d..da2c53aa6 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -485,7 +485,7 @@ namespace FlaxEditor.Windows /// The item to delete. public void Delete(ContentItem item) { - Delete(new List(1) { item }); + Delete(Editor.Instance.Windows.ContentWin.View.Selection); } /// From d38badb54be245b3582aa3c6adcad14724b44b93 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Apr 2023 13:00:28 +0200 Subject: [PATCH 17/28] Add array property replication code-gen for C# networking --- .../Build/Plugins/NetworkingPlugin.cs | 61 +++++++++++++++---- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 758b86e3b..f4f69571a 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -1045,18 +1045,27 @@ namespace Flax.Build.Plugins il.Body.Variables.Add(new VariableDefinition(intType)); il.Body.Variables.Add(new VariableDefinition(new PointerType(elementType))); il.Body.Variables.Add(new VariableDefinition(new PinnedType(valueType))); + if (property != null) + il.Body.Variables.Add(new VariableDefinition(valueType)); il.Body.InitLocals = true; if (serialize) { - // [] array2 = Array1; + // [] array = Array; il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ldarg_0); if (field != null) + { il.Emit(OpCodes.Ldfld, field); + } else + { + // [] array = ArrayProperty; il.Emit(propertyGetOpCode, property.GetMethod); + il.Emit(OpCodes.Stloc, varStart + 3); + il.Emit(OpCodes.Ldloc, varStart + 3); + } - // int num2 = ((array2 != null) ? array2.Length : 0); + // int num2 = ((array != null) ? array.Length : 0); il.Emit(OpCodes.Dup); Instruction jmp1 = il.Create(OpCodes.Nop); il.Emit(OpCodes.Brtrue_S, jmp1); @@ -1076,13 +1085,17 @@ namespace Flax.Build.Plugins var m = networkStreamType.GetMethod("WriteInt32"); il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - // fixed (* bytes2 = Array1) + // fixed (* bytes2 = Array) il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldarg_0); if (field != null) + { + il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, field); + } else - il.Emit(propertyGetOpCode, property.GetMethod); + { + il.Emit(OpCodes.Ldloc, varStart + 3); + } il.Emit(OpCodes.Dup); il.Emit(OpCodes.Stloc, varStart + 2); Instruction jmp3 = il.Create(OpCodes.Nop); @@ -1120,9 +1133,6 @@ namespace Flax.Build.Plugins } else { - if (field == null) - throw new NotImplementedException("TODO: add support for array property replication"); - // int num = stream.ReadInt32(); il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ldarg_1); @@ -1130,18 +1140,37 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Callvirt, module.ImportReference(m)); il.Emit(OpCodes.Stloc, varStart + 0); - // System.Array.Resize(ref Array1, num); + // System.Array.Resize(ref ArrayField, num); il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldflda, field); + if (field != null) + { + il.Emit(OpCodes.Ldflda, field); + } + else + { + // [] array = ArrayProperty; + il.Emit(propertyGetOpCode, property.GetMethod); + il.Emit(OpCodes.Stloc, varStart + 3); + il.Emit(OpCodes.Ldloca_S, (byte)(varStart + 3)); + } il.Emit(OpCodes.Ldloc, varStart + 0); module.TryGetTypeReference("System.Array", out var arrayType); + if (arrayType == null) + module.GetType("System.Array", out arrayType); m = arrayType.Resolve().GetMethod("Resize", 2); il.Emit(OpCodes.Call, module.ImportReference(m.InflateGeneric(elementType))); - // fixed (int* buffer = Array1) + // fixed (* buffer = Array) il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); + if (field != null) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, field); + } + else + { + il.Emit(OpCodes.Ldloc, varStart + 3); + } il.Emit(OpCodes.Dup); il.Emit(OpCodes.Stloc, varStart + 2); Instruction jmp1 = il.Create(OpCodes.Nop); @@ -1176,6 +1205,12 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Stloc, varStart + 2); + if (property != null) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldloc, varStart + 3); + il.Emit(propertySetOpCode, property.SetMethod); + } } } else From 2e22abaff95822d97ed59b9026420ecc504ea4c5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Apr 2023 14:56:54 +0200 Subject: [PATCH 18/28] Refactor C# network replication codegen to be more extendable based on value context source --- .../Build/Plugins/NetworkingPlugin.cs | 578 +++++++++++------- 1 file changed, 342 insertions(+), 236 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index f4f69571a..2b5058b0e 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -347,14 +347,14 @@ namespace Flax.Build.Plugins contents.AppendLine(); } - private bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo type) + private static bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo type) { // TODO: what if type fields have custom replication settings (eg. compression)? type.EnsureInited(buildData); return type.IsPod; } - private bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo caller, ApiTypeInfo apiType, TypeInfo type) + private static bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo caller, ApiTypeInfo apiType, TypeInfo type) { if (type.IsPod(buildData, caller)) { @@ -365,6 +365,12 @@ namespace Flax.Build.Plugins return false; } + private static bool IsRawPOD(TypeReference type) + { + // TODO: + return type.IsValueType; + } + private void OnGenerateCppTypeSerializeData(Builder.BuildData buildData, ApiTypeInfo caller, StringBuilder contents, TypeInfo type, string name, bool serialize) { var apiType = BindingsGenerator.FindApiTypeInfo(buildData, type, caller); @@ -789,7 +795,7 @@ namespace Flax.Build.Plugins { if (!f.HasAttribute(NetworkReplicatedAttribute)) continue; - GenerateSerializerType(ref context, type, serialize, f, null, f.FieldType, il); + GenerateSerializerType(ref context, type, serialize, f.FieldType, il, new DotnetValueContext(f)); } // Serialize all type properties marked with NetworkReplicated attribute @@ -797,7 +803,7 @@ namespace Flax.Build.Plugins { if (!p.HasAttribute(NetworkReplicatedAttribute)) continue; - GenerateSerializerType(ref context, type, serialize, null, p, p.PropertyType, il); + GenerateSerializerType(ref context, type, serialize, p.PropertyType, il, new DotnetValueContext(p)); } if (serialize) @@ -877,53 +883,346 @@ namespace Flax.Build.Plugins } } - private static void GenerateSerializerType(ref DotnetContext context, TypeDefinition type, bool serialize, FieldReference field, PropertyDefinition property, TypeReference valueType, ILProcessor il) + private struct DotnetValueContext { - if (field == null && property == null) - throw new ArgumentException(); - TypeDefinition networkStreamType = context.NetworkStreamType.Resolve(); - var propertyGetOpCode = OpCodes.Call; - var propertySetOpCode = OpCodes.Call; - if (property != null) + public FieldReference Field; + public PropertyDefinition Property; + public int LocalVarIndex; + + public OpCode PropertyGetOpCode { - if (property.GetMethod == null) + get { - MonoCecil.CompilationError($"Missing getter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", property); + var propertyGetOpCode = OpCodes.Call; + if (Property != null && Property.GetMethod.IsVirtual) + propertyGetOpCode = OpCodes.Callvirt; + return propertyGetOpCode; + } + } + + public OpCode PropertySetOpCode + { + get + { + var propertyGetOpCode = OpCodes.Call; + if (Property != null && Property.GetMethod.IsVirtual) + propertyGetOpCode = OpCodes.Callvirt; + return propertyGetOpCode; + } + } + + public DotnetValueContext(FieldDefinition field) + { + Field = field; + Property = null; + LocalVarIndex = -1; + } + + public DotnetValueContext(PropertyDefinition property) + { + Field = null; + Property = property; + LocalVarIndex = -1; + } + + public void GetProperty(ILProcessor il, int propertyVar) + { + if (Property != null) + { + // [] array = ArrayProperty; + il.Emit(OpCodes.Ldarg_0); + il.Emit(PropertyGetOpCode, Property.GetMethod); + il.Emit(OpCodes.Stloc, propertyVar); + LocalVarIndex = propertyVar; + } + } + + public void SetProperty(ILProcessor il) + { + if (Property != null) + { + // ArrayProperty = array + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldloc, LocalVarIndex); + il.Emit(PropertySetOpCode, Property.SetMethod); + } + } + + public void Load(ILProcessor il) + { + if (Field != null) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, Field); + } + else if (Property != null && LocalVarIndex == -1) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(PropertyGetOpCode, Property.GetMethod); + } + else + { + il.Emit(OpCodes.Ldloc, LocalVarIndex); + } + } + + public void LoadAddress(ILProcessor il) + { + if (Field != null) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldflda, Field); + } + else if (Property != null && LocalVarIndex == -1) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(PropertyGetOpCode, Property.GetMethod); + } + else + { + il.Emit(OpCodes.Ldloca_S, (byte)LocalVarIndex); + } + } + + public void Store(ILProcessor il) + { + if (Field != null) + { + il.Emit(OpCodes.Stfld, Field); + } + else if (Property != null) + { + il.Emit(PropertySetOpCode, Property.SetMethod); + } + else + { + throw new NotImplementedException("TODO: storing local variable value"); + } + } + } + + private static void GenerateSerializerType(ref DotnetContext context, TypeDefinition type, bool serialize, TypeReference valueType, ILProcessor il, DotnetValueContext valueContext) + { + if (valueContext.Property != null) + { + if (valueContext.Property.GetMethod == null) + { + MonoCecil.CompilationError($"Missing getter method for property '{valueContext.Property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", valueContext.Property); context.Failed = true; return; } - if (property.SetMethod == null) + if (valueContext.Property.SetMethod == null) { - MonoCecil.CompilationError($"Missing setter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", property); + MonoCecil.CompilationError($"Missing setter method for property '{valueContext.Property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", valueContext.Property); context.Failed = true; return; } - - if (property.GetMethod.IsVirtual) - propertyGetOpCode = OpCodes.Callvirt; - if (property.SetMethod.IsVirtual) - propertySetOpCode = OpCodes.Callvirt; } ModuleDefinition module = type.Module; TypeDefinition valueTypeDef = valueType.Resolve(); + TypeDefinition networkStreamType = context.NetworkStreamType.Resolve(); // Ensure to have valid serialization already generated for that value type (eg. when using custom structure field serialization) GenerateTypeSerialization(ref context, valueTypeDef); - if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) + if (valueType.IsArray) + { + var elementType = valueType.GetElementType(); + var isRawPod = IsRawPOD(elementType); // Whether to use raw memory copy (eg. int, enum, Vector2) + var varStart = il.Body.Variables.Count; + module.GetType("System.Int32", out var intType); + il.Body.Variables.Add(new VariableDefinition(intType)); // [0] int length + if (isRawPod) + { + il.Body.Variables.Add(new VariableDefinition(new PointerType(elementType))); // [1] * + il.Body.Variables.Add(new VariableDefinition(new PinnedType(valueType))); // [2] [] pinned + } + else + { + il.Body.Variables.Add(new VariableDefinition(intType)); // [1] int idx + il.Body.Variables.Add(new VariableDefinition(elementType)); // [2] + } + if (valueContext.Property != null) + il.Body.Variables.Add(new VariableDefinition(valueType)); // [3] [] + il.Body.InitLocals = true; + valueContext.GetProperty(il, varStart + 3); + if (serialize) + { + // [] array = Array; + il.Emit(OpCodes.Nop); + valueContext.Load(il); + + // int length = ((array != null) ? array.Length : 0); + il.Emit(OpCodes.Dup); + Instruction jmp1 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Brtrue_S, jmp1); + il.Emit(OpCodes.Pop); + il.Emit(OpCodes.Ldc_I4_0); + Instruction jmp2 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Br_S, jmp2); + il.Append(jmp1); + il.Emit(OpCodes.Ldlen); + il.Emit(OpCodes.Conv_I4); + il.Append(jmp2); + il.Emit(OpCodes.Stloc, varStart + 0); + + // stream.WriteInt32(length); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldloc, varStart + 0); + var m = networkStreamType.GetMethod("WriteInt32"); + il.Emit(OpCodes.Callvirt, module.ImportReference(m)); + + il.Emit(OpCodes.Nop); + if (isRawPod) + { + // fixed (* bytes2 = Array) + valueContext.Load(il); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, varStart + 2); + Instruction jmp3 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Brfalse_S, jmp3); + il.Emit(OpCodes.Ldloc_2); + il.Emit(OpCodes.Ldlen); + il.Emit(OpCodes.Conv_I4); + Instruction jmp4 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Brtrue_S, jmp4); + il.Append(jmp3); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Conv_U); + il.Emit(OpCodes.Stloc, varStart + 1); + Instruction jmp5 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Br_S, jmp5); + + // stream.WriteBytes((byte*)bytes, length * sizeof())); + il.Append(jmp4); + il.Emit(OpCodes.Ldloc, varStart + 2); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ldelema, elementType); + il.Emit(OpCodes.Conv_U); + il.Emit(OpCodes.Stloc, varStart + 1); + il.Append(jmp5); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldloc, varStart + 1); + il.Emit(OpCodes.Ldloc, varStart + 0); + il.Emit(OpCodes.Sizeof, elementType); + il.Emit(OpCodes.Mul); + m = networkStreamType.GetMethod("WriteBytes", 2); + il.Emit(OpCodes.Callvirt, module.ImportReference(m)); + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldnull); + il.Emit(OpCodes.Stloc, varStart + 2); + } + else + { + // int idx = 0 + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Stloc, varStart + 1); + Instruction jmp3 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Br_S, jmp3); + + // element = array[idx]; + Instruction jmp4 = il.Create(OpCodes.Nop); + il.Append(jmp4); + valueContext.Load(il); + il.Emit(OpCodes.Ldloc, varStart + 1); // idx + il.Emit(OpCodes.Ldelem_Ref); + il.Emit(OpCodes.Stloc, varStart + 2); // + + // TODO: serialize element type from [varStart + 2] + + il.Emit(OpCodes.Nop); + + // idx++ + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldloc, varStart + 1); // idx + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, varStart + 1); // idx + + // idx < num + il.Append(jmp3); + il.Emit(OpCodes.Ldloc, varStart + 1); // idx + il.Emit(OpCodes.Ldloc, varStart + 0); // length + il.Emit(OpCodes.Clt); + il.Emit(OpCodes.Brtrue_S, jmp4); + } + } + else + { + // int length = stream.ReadInt32(); + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldarg_1); + var m = networkStreamType.GetMethod("ReadInt32"); + il.Emit(OpCodes.Callvirt, module.ImportReference(m)); + il.Emit(OpCodes.Stloc, varStart + 0); + + // System.Array.Resize(ref Array, length); + valueContext.LoadAddress(il); + il.Emit(OpCodes.Ldloc, varStart + 0); + module.TryGetTypeReference("System.Array", out var arrayType); + if (arrayType == null) + module.GetType("System.Array", out arrayType); + m = arrayType.Resolve().GetMethod("Resize", 2); + il.Emit(OpCodes.Call, module.ImportReference(m.InflateGeneric(elementType))); + + il.Emit(OpCodes.Nop); + if (isRawPod) + { + // fixed (* buffer = Array) + valueContext.Load(il); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, varStart + 2); + Instruction jmp1 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Brfalse_S, jmp1); + il.Emit(OpCodes.Ldloc, varStart + 2); + il.Emit(OpCodes.Ldlen); + il.Emit(OpCodes.Conv_I4); + Instruction jmp2 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Brtrue_S, jmp2); + il.Append(jmp1); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Conv_U); + il.Emit(OpCodes.Stloc, varStart + 1); + Instruction jmp3 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Br_S, jmp3); + + // stream.ReadBytes((byte*)buffer, num * sizeof()); + il.Append(jmp2); + il.Emit(OpCodes.Ldloc, varStart + 2); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ldelema, elementType); + il.Emit(OpCodes.Conv_U); + il.Emit(OpCodes.Stloc, varStart + 1); + il.Append(jmp3); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldloc, varStart + 1); + il.Emit(OpCodes.Ldloc, varStart + 0); + il.Emit(OpCodes.Sizeof, elementType); + il.Emit(OpCodes.Mul); + m = networkStreamType.GetMethod("ReadBytes", 2); + il.Emit(OpCodes.Callvirt, module.ImportReference(m)); + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldnull); + il.Emit(OpCodes.Stloc, varStart + 2); + } + else + { + // TODO: deserialize item-by-item + } + + valueContext.SetProperty(il); + } + } + else if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) { // Call NetworkStream method to write/read data MethodDefinition m; if (serialize) { il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_0); - if (field != null) - il.Emit(OpCodes.Ldfld, field); - else - il.Emit(propertyGetOpCode, property.GetMethod); + valueContext.Load(il); m = networkStreamType.GetMethod(serializer.WriteMethod); } else @@ -936,10 +1235,7 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Callvirt, module.ImportReference(m)); if (!serialize) { - if (field != null) - il.Emit(OpCodes.Stfld, field); - else - il.Emit(propertySetOpCode, property.SetMethod); + valueContext.Store(il); } } else if (valueType.IsScriptingObject()) @@ -950,11 +1246,7 @@ namespace Flax.Build.Plugins if (serialize) { il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_0); - if (field != null) - il.Emit(OpCodes.Ldfld, field); - else - il.Emit(propertyGetOpCode, property.GetMethod); + valueContext.Load(il); il.Emit(OpCodes.Dup); Instruction jmp1 = il.Create(OpCodes.Nop); il.Emit(OpCodes.Brtrue_S, jmp1); @@ -990,10 +1282,7 @@ namespace Flax.Build.Plugins var tryFind = scriptingObjectType.Resolve().GetMethod("TryFind", 2); il.Emit(OpCodes.Call, module.ImportReference(tryFind)); il.Emit(OpCodes.Castclass, valueType); - if (field != null) - il.Emit(OpCodes.Stfld, field); - else - il.Emit(propertySetOpCode, property.SetMethod); + valueContext.Store(il); } } else if (valueTypeDef.IsEnum) @@ -1003,11 +1292,7 @@ namespace Flax.Build.Plugins if (serialize) { il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_0); - if (field != null) - il.Emit(OpCodes.Ldfld, field); - else - il.Emit(propertyGetOpCode, property.GetMethod); + valueContext.Load(il); var m = networkStreamType.GetMethod("WriteUInt32"); il.Emit(OpCodes.Callvirt, module.ImportReference(m)); } @@ -1017,209 +1302,24 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Ldarg_1); var m = networkStreamType.GetMethod("ReadUInt32"); il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - if (field != null) - il.Emit(OpCodes.Stfld, field); - else - il.Emit(propertySetOpCode, property.SetMethod); + valueContext.Store(il); } } else if (valueType.IsValueType) { // Invoke structure generated serializer - // TODO: check if this type has generated serialization code - il.Emit(OpCodes.Ldarg_0); - if (field != null) - il.Emit(OpCodes.Ldflda, field); - else - il.Emit(propertyGetOpCode, property.GetMethod); + valueContext.LoadAddress(il); il.Emit(OpCodes.Ldarg_1); var m = valueTypeDef.GetMethod(serialize ? Thunk1 : Thunk2); il.Emit(OpCodes.Call, module.ImportReference(m)); } - else if (valueType.IsArray && valueType.GetElementType().IsValueType) - { - // TODO: support any array type by iterating over elements (separate serialize for each one) - var elementType = valueType.GetElementType(); - var varStart = il.Body.Variables.Count; - module.GetType("System.Int32", out var intType); - il.Body.Variables.Add(new VariableDefinition(intType)); - il.Body.Variables.Add(new VariableDefinition(new PointerType(elementType))); - il.Body.Variables.Add(new VariableDefinition(new PinnedType(valueType))); - if (property != null) - il.Body.Variables.Add(new VariableDefinition(valueType)); - il.Body.InitLocals = true; - if (serialize) - { - // [] array = Array; - il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldarg_0); - if (field != null) - { - il.Emit(OpCodes.Ldfld, field); - } - else - { - // [] array = ArrayProperty; - il.Emit(propertyGetOpCode, property.GetMethod); - il.Emit(OpCodes.Stloc, varStart + 3); - il.Emit(OpCodes.Ldloc, varStart + 3); - } - - // int num2 = ((array != null) ? array.Length : 0); - il.Emit(OpCodes.Dup); - Instruction jmp1 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Brtrue_S, jmp1); - il.Emit(OpCodes.Pop); - il.Emit(OpCodes.Ldc_I4_0); - Instruction jmp2 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Br_S, jmp2); - il.Append(jmp1); - il.Emit(OpCodes.Ldlen); - il.Emit(OpCodes.Conv_I4); - il.Append(jmp2); - il.Emit(OpCodes.Stloc, varStart + 0); - - // stream.WriteInt32(num2); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldloc, varStart + 0); - var m = networkStreamType.GetMethod("WriteInt32"); - il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - - // fixed (* bytes2 = Array) - il.Emit(OpCodes.Nop); - if (field != null) - { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); - } - else - { - il.Emit(OpCodes.Ldloc, varStart + 3); - } - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, varStart + 2); - Instruction jmp3 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Brfalse_S, jmp3); - il.Emit(OpCodes.Ldloc_2); - il.Emit(OpCodes.Ldlen); - il.Emit(OpCodes.Conv_I4); - Instruction jmp4 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Brtrue_S, jmp4); - il.Append(jmp3); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); - Instruction jmp5 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Br_S, jmp5); - - // stream.WriteBytes((byte*)bytes, num * sizeof())); - il.Append(jmp4); - il.Emit(OpCodes.Ldloc, varStart + 2); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ldelema, elementType); - il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); - il.Append(jmp5); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldloc, varStart + 1); - il.Emit(OpCodes.Ldloc, varStart + 0); - il.Emit(OpCodes.Sizeof, elementType); - il.Emit(OpCodes.Mul); - m = networkStreamType.GetMethod("WriteBytes", 2); - il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldnull); - il.Emit(OpCodes.Stloc, varStart + 2); - } - else - { - // int num = stream.ReadInt32(); - il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldarg_1); - var m = networkStreamType.GetMethod("ReadInt32"); - il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - il.Emit(OpCodes.Stloc, varStart + 0); - - // System.Array.Resize(ref ArrayField, num); - il.Emit(OpCodes.Ldarg_0); - if (field != null) - { - il.Emit(OpCodes.Ldflda, field); - } - else - { - // [] array = ArrayProperty; - il.Emit(propertyGetOpCode, property.GetMethod); - il.Emit(OpCodes.Stloc, varStart + 3); - il.Emit(OpCodes.Ldloca_S, (byte)(varStart + 3)); - } - il.Emit(OpCodes.Ldloc, varStart + 0); - module.TryGetTypeReference("System.Array", out var arrayType); - if (arrayType == null) - module.GetType("System.Array", out arrayType); - m = arrayType.Resolve().GetMethod("Resize", 2); - il.Emit(OpCodes.Call, module.ImportReference(m.InflateGeneric(elementType))); - - // fixed (* buffer = Array) - il.Emit(OpCodes.Nop); - if (field != null) - { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); - } - else - { - il.Emit(OpCodes.Ldloc, varStart + 3); - } - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, varStart + 2); - Instruction jmp1 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Brfalse_S, jmp1); - il.Emit(OpCodes.Ldloc, varStart + 2); - il.Emit(OpCodes.Ldlen); - il.Emit(OpCodes.Conv_I4); - Instruction jmp2 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Brtrue_S, jmp2); - il.Append(jmp1); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); - Instruction jmp3 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Br_S, jmp3); - - // stream.ReadBytes((byte*)buffer, num * sizeof()); - il.Append(jmp2); - il.Emit(OpCodes.Ldloc, varStart + 2); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ldelema, elementType); - il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); - il.Append(jmp3); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldloc, varStart + 1); - il.Emit(OpCodes.Ldloc, varStart + 0); - il.Emit(OpCodes.Sizeof, elementType); - il.Emit(OpCodes.Mul); - m = networkStreamType.GetMethod("ReadBytes", 2); - il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldnull); - il.Emit(OpCodes.Stloc, varStart + 2); - if (property != null) - { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldloc, varStart + 3); - il.Emit(propertySetOpCode, property.SetMethod); - } - } - } else { // Unknown type - if (property != null) - MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {property.Name} in {type.FullName} for automatic replication.", property); - else if (field != null) - MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {field.Name} in {type.FullName} for automatic replication.", field.Resolve()); + if (valueContext.Property != null) + MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {valueContext.Property.Name} in {type.FullName} for automatic replication.", valueContext.Property); + else if (valueContext.Field != null) + MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {valueContext.Field.Name} in {type.FullName} for automatic replication.", valueContext.Field.Resolve()); else MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' for automatic replication."); context.Failed = true; @@ -1234,7 +1334,13 @@ namespace Flax.Build.Plugins // Ensure to have valid serialization already generated for that value type GenerateTypeSerialization(ref context, valueTypeDef); - if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) + if (type.IsArray) + { + // TODO: refactor network stream read/write to share code between replication and rpcs + Log.Error($"Not supported type '{valueType.FullName}' for RPC parameter in {type.FullName}."); + context.Failed = true; + } + else if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) { // Call NetworkStream method to write/read data if (serialize) From 8a80a265234babc93cf22b7fd8b7415144c0467a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Apr 2023 15:30:51 +0200 Subject: [PATCH 19/28] Add networking replication codegen for C# array properties with object references or custom structures --- .../Build/Plugins/NetworkingPlugin.cs | 99 +++++++++++++------ 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 2b5058b0e..b4e52108d 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -231,14 +231,14 @@ namespace Flax.Build.Plugins var arg = functionInfo.Parameters[i]; if (i != 0) argNames += ", "; - + // Special handling of Rpc Params if (!arg.Type.IsPtr && arg.Type.Type == "NetworkRpcParams") { argNames += "NetworkRpcParams(stream)"; continue; } - + // Deserialize arguments argNames += arg.Name; contents.AppendLine($" {arg.Type.Type} {arg.Name};"); @@ -268,7 +268,7 @@ namespace Flax.Build.Plugins contents.AppendLine($" targetIds = ((NetworkRpcParams*)args[{i}])->TargetIds;"); continue; } - + // Serialize arguments contents.AppendLine($" stream->Write(*({arg.Type.Type}*)args[{i}]);"); } @@ -925,6 +925,13 @@ namespace Flax.Build.Plugins LocalVarIndex = -1; } + public DotnetValueContext(int localVarIndex) + { + Field = null; + Property = null; + LocalVarIndex = localVarIndex; + } + public void GetProperty(ILProcessor il, int propertyVar) { if (Property != null) @@ -996,7 +1003,7 @@ namespace Flax.Build.Plugins } else { - throw new NotImplementedException("TODO: storing local variable value"); + il.Emit(OpCodes.Stloc, LocalVarIndex); } } } @@ -1044,6 +1051,7 @@ namespace Flax.Build.Plugins il.Body.Variables.Add(new VariableDefinition(intType)); // [1] int idx il.Body.Variables.Add(new VariableDefinition(elementType)); // [2] } + if (valueContext.Property != null) il.Body.Variables.Add(new VariableDefinition(valueType)); // [3] [] il.Body.InitLocals = true; @@ -1073,7 +1081,7 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Ldloc, varStart + 0); var m = networkStreamType.GetMethod("WriteInt32"); il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - + il.Emit(OpCodes.Nop); if (isRawPod) { @@ -1091,7 +1099,7 @@ namespace Flax.Build.Plugins il.Append(jmp3); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); + il.Emit(OpCodes.Stloc, varStart + 1); // * Instruction jmp5 = il.Create(OpCodes.Nop); il.Emit(OpCodes.Br_S, jmp5); @@ -1101,10 +1109,10 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ldelema, elementType); il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); + il.Emit(OpCodes.Stloc, varStart + 1); // * il.Append(jmp5); il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldloc, varStart + 1); + il.Emit(OpCodes.Ldloc, varStart + 1); // * il.Emit(OpCodes.Ldloc, varStart + 0); il.Emit(OpCodes.Sizeof, elementType); il.Emit(OpCodes.Mul); @@ -1118,11 +1126,11 @@ namespace Flax.Build.Plugins { // int idx = 0 il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Stloc, varStart + 1); + il.Emit(OpCodes.Stloc, varStart + 1); // idx Instruction jmp3 = il.Create(OpCodes.Nop); il.Emit(OpCodes.Br_S, jmp3); - - // element = array[idx]; + + // element = array[idx] Instruction jmp4 = il.Create(OpCodes.Nop); il.Append(jmp4); valueContext.Load(il); @@ -1130,18 +1138,18 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Ldelem_Ref); il.Emit(OpCodes.Stloc, varStart + 2); // - // TODO: serialize element type from [varStart + 2] - + // Serialize item value il.Emit(OpCodes.Nop); - - // idx++ + GenerateSerializerType(ref context, type, serialize, elementType, il, new DotnetValueContext(varStart + 2)); + + // idx++ il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ldloc, varStart + 1); // idx il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Add); il.Emit(OpCodes.Stloc, varStart + 1); // idx - // idx < num + // idx < length il.Append(jmp3); il.Emit(OpCodes.Ldloc, varStart + 1); // idx il.Emit(OpCodes.Ldloc, varStart + 0); // length @@ -1156,17 +1164,17 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Ldarg_1); var m = networkStreamType.GetMethod("ReadInt32"); il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - il.Emit(OpCodes.Stloc, varStart + 0); + il.Emit(OpCodes.Stloc, varStart + 0); // length // System.Array.Resize(ref Array, length); valueContext.LoadAddress(il); - il.Emit(OpCodes.Ldloc, varStart + 0); + il.Emit(OpCodes.Ldloc, varStart + 0); // length module.TryGetTypeReference("System.Array", out var arrayType); if (arrayType == null) module.GetType("System.Array", out arrayType); m = arrayType.Resolve().GetMethod("Resize", 2); il.Emit(OpCodes.Call, module.ImportReference(m.InflateGeneric(elementType))); - + il.Emit(OpCodes.Nop); if (isRawPod) { @@ -1184,21 +1192,21 @@ namespace Flax.Build.Plugins il.Append(jmp1); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); + il.Emit(OpCodes.Stloc, varStart + 1); // * buffer Instruction jmp3 = il.Create(OpCodes.Nop); il.Emit(OpCodes.Br_S, jmp3); - // stream.ReadBytes((byte*)buffer, num * sizeof()); + // stream.ReadBytes((byte*)buffer, length * sizeof()); il.Append(jmp2); il.Emit(OpCodes.Ldloc, varStart + 2); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ldelema, elementType); il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); + il.Emit(OpCodes.Stloc, varStart + 1); // * buffer il.Append(jmp3); il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldloc, varStart + 1); - il.Emit(OpCodes.Ldloc, varStart + 0); + il.Emit(OpCodes.Ldloc, varStart + 1); // * buffer + il.Emit(OpCodes.Ldloc, varStart + 0); // length il.Emit(OpCodes.Sizeof, elementType); il.Emit(OpCodes.Mul); m = networkStreamType.GetMethod("ReadBytes", 2); @@ -1209,7 +1217,37 @@ namespace Flax.Build.Plugins } else { - // TODO: deserialize item-by-item + // int idx = 0 + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Stloc, varStart + 1); // idx + Instruction jmp3 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Br_S, jmp3); + + // Deserialize item value + Instruction jmp4 = il.Create(OpCodes.Nop); + il.Append(jmp4); + GenerateSerializerType(ref context, type, serialize, elementType, il, new DotnetValueContext(varStart + 2)); + + // array[idx] = element + il.Emit(OpCodes.Nop); + valueContext.Load(il); + il.Emit(OpCodes.Ldloc, varStart + 1); // idx + il.Emit(OpCodes.Ldloc, varStart + 2); // + il.Emit(OpCodes.Stelem_Ref); + + // idx++ + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldloc, varStart + 1); // idx + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, varStart + 1); // idx + + // idx < length + il.Append(jmp3); + il.Emit(OpCodes.Ldloc, varStart + 1); // idx + il.Emit(OpCodes.Ldloc, varStart + 0); // length + il.Emit(OpCodes.Clt); + il.Emit(OpCodes.Brtrue_S, jmp4); } valueContext.SetProperty(il); @@ -1264,17 +1302,16 @@ namespace Flax.Build.Plugins } else { - var m = networkStreamType.GetMethod("ReadGuid"); - module.GetType("System.Type", out var typeType); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - il.Emit(OpCodes.Stloc_0); - il.Emit(OpCodes.Ldarg_0); var varStart = il.Body.Variables.Count; var reference = module.ImportReference(guidType); reference.IsValueType = true; // Fix locals init to have valuetype for Guid instead of class il.Body.Variables.Add(new VariableDefinition(reference)); il.Body.InitLocals = true; + var m = networkStreamType.GetMethod("ReadGuid"); + module.GetType("System.Type", out var typeType); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Callvirt, module.ImportReference(m)); + il.Emit(OpCodes.Stloc_S, (byte)varStart); il.Emit(OpCodes.Ldloca_S, (byte)varStart); il.Emit(OpCodes.Ldtoken, valueType); var getTypeFromHandle = typeType.Resolve().GetMethod("GetTypeFromHandle"); From f5c9dce34a459e8616549bbcf96487c8177c76c2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Apr 2023 18:55:31 +0200 Subject: [PATCH 20/28] Fix network RPC object id mapping back to server id when sent from client --- .../Engine/Networking/NetworkReplicator.cpp | 69 +++++++++++++++++-- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 98c7c2432..d20f31b0b 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -32,7 +32,9 @@ #if !BUILD_RELEASE bool NetworkReplicator::EnableLog = false; #include "Engine/Core/Log.h" +#include "Engine/Content/Content.h" #define NETWORK_REPLICATOR_LOG(messageType, format, ...) if (NetworkReplicator::EnableLog) { LOG(messageType, format, ##__VA_ARGS__); } +#define USE_NETWORK_REPLICATOR_LOG 1 #else #define NETWORK_REPLICATOR_LOG(messageType, format, ...) #endif @@ -105,10 +107,15 @@ struct NetworkReplicatedObject uint32 OwnerClientId; uint32 LastOwnerFrame = 0; NetworkObjectRole Role; - uint8 Spawned = false; + uint8 Spawned : 1; DataContainer TargetClientIds; INetworkObject* AsNetworkObject; + NetworkReplicatedObject() + { + Spawned = 0; + } + bool operator==(const NetworkReplicatedObject& other) const { return Object == other.Object; @@ -524,6 +531,36 @@ void SetupObjectSpawnGroupItem(ScriptingObject* obj, ArrayItems.Add(&spawnItem); } +void FindObjectsForSpawn(SpawnGroup& group, ChunkedArray& spawnItems, ScriptingObject* obj) +{ + // Add any registered network objects + auto it = Objects.Find(obj->GetID()); + if (it != Objects.End()) + { + auto& item = it->Item; + if (!item.Spawned) + { + // One of the parents of this object is being spawned so spawn it too + item.Spawned = true; + auto& spawnItem = spawnItems.AddOne(); + spawnItem.Object = obj; + spawnItem.Targets.Link(item.TargetClientIds); + spawnItem.OwnerClientId = item.OwnerClientId; + spawnItem.Role = item.Role; + group.Items.Add(&spawnItem); + } + } + + // Iterate over children + if (auto* actor = ScriptingObject::Cast(obj)) + { + for (auto* script : actor->Scripts) + FindObjectsForSpawn(group, spawnItems, script); + for (auto* child : actor->Children) + FindObjectsForSpawn(group, spawnItems, child); + } +} + void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject* obj) { // TODO: implement objects state replication frequency and dirtying @@ -1159,9 +1196,16 @@ void NetworkInternal::NetworkReplicatorUpdate() } // Spawn groups of objects + ChunkedArray spawnItems; for (SpawnGroup& g : spawnGroups) { + // Include any added objects within spawn group that were not spawned manually (eg. AddObject for script/actor attached to spawned actor) + ScriptingObject* groupRoot = g.Items[0]->Object.Get(); + FindObjectsForSpawn(g, spawnItems, groupRoot); + SendObjectSpawnMessage(g, NetworkManager::Clients); + + spawnItems.Clear(); } SpawnQueue.Clear(); } @@ -1325,7 +1369,7 @@ void NetworkInternal::NetworkReplicatorUpdate() if (e.Info.Server && isClient) { // Client -> Server -#if !BUILD_RELEASE +#if USE_NETWORK_REPLICATOR_LOG if (e.Targets.Length() != 0) NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Server RPC '{}::{}' called with non-empty list of targets is not supported (only server will receive it)", e.Name.First.ToString(), e.Name.Second.ToString()); #endif @@ -1520,10 +1564,6 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl if (!obj->IsRegistered()) obj->RegisterObject(); const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId); - if (!parent && msgDataItem.ParentId.IsValid()) - { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString()); - } // Add object to the list NetworkReplicatedObject item; @@ -1554,6 +1594,21 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl sceneObject->SetParent(parent->Object.As()); else if (auto* parentActor = Scripting::TryFindObject(msgDataItem.ParentId)) sceneObject->SetParent(parentActor); + else if (msgDataItem.ParentId.IsValid()) + { +#if USE_NETWORK_REPLICATOR_LOG + // Ignore case when parent object in a message was a scene (eg. that is already unloaded on a client) + AssetInfo assetInfo; + if (!Content::GetAssetInfo(msgDataItem.ParentId, assetInfo) || assetInfo.TypeName == TEXT("FlaxEngine.SceneAsset")) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString()); + } +#endif + } + } + else if (!parent && msgDataItem.ParentId.IsValid()) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString()); } if (item.AsNetworkObject) @@ -1657,7 +1712,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name); if (!info) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgData.ObjectId, String(msgData.RpcTypeName), String(msgData.RpcName)); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(msgData.RpcTypeName), String(msgData.RpcName), msgData.ObjectId); return; } From c3cc78b7c22fc676214f10ec1a919279cecff638 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Apr 2023 19:46:36 +0200 Subject: [PATCH 21/28] Cleanup code after #1001 --- Source/Engine/Core/Math/Vector3.h | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index 76b9aa2f8..590c3adb0 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -569,7 +569,7 @@ public: /// /// Makes sure that Length of the output vector is always below max and above 0. /// - /// Input Vector. + /// Input Vector. /// Max Length static Vector3Base ClampLength(const Vector3Base& v, float max) { @@ -579,7 +579,7 @@ public: /// /// Makes sure that Length of the output vector is always below max and above min. /// - /// Input Vector. + /// Input Vector. /// Min Length /// Max Length static Vector3Base ClampLength(const Vector3Base& v, float min, float max) @@ -592,26 +592,24 @@ public: /// /// Makes sure that Length of the output vector is always below max and above min. /// - /// Input Vector. + /// Input Vector. /// Min Length /// Max Length /// The result vector. static void ClampLength(const Vector3Base& v, float min, float max, Vector3Base& result) { - result.X = v.X; - result.Y = v.Y; - result.Z = v.Z; - auto lenSq = result.LengthSquared(); + result = v; + T lenSq = result.LengthSquared(); if (lenSq > max * max) { - auto scaleFactor = max / (float)Math::Sqrt(lenSq); + T scaleFactor = max / (T)Math::Sqrt(lenSq); result.X *= scaleFactor; result.Y *= scaleFactor; result.Z *= scaleFactor; } if (lenSq < min * min) { - auto scaleFactor = min / (float)Math::Sqrt(lenSq); + T scaleFactor = min / (T)Math::Sqrt(lenSq); result.X *= scaleFactor; result.Y *= scaleFactor; result.Z *= scaleFactor; From b85184eee094e54b1358bbd11a16534a5bc7333c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Apr 2023 11:10:14 +0200 Subject: [PATCH 22/28] Optimize vectors normalization --- Source/Engine/Core/Math/Double2.cs | 2 +- Source/Engine/Core/Math/Double3.cs | 2 +- Source/Engine/Core/Math/Double4.cs | 2 +- Source/Engine/Core/Math/Float2.cs | 2 +- Source/Engine/Core/Math/Float3.cs | 2 +- Source/Engine/Core/Math/Float4.cs | 2 +- Source/Engine/Core/Math/Plane.cs | 4 ++- Source/Engine/Core/Math/Quaternion.cs | 2 +- Source/Engine/Core/Math/Vector2.cs | 38 ++++++++++++----------- Source/Engine/Core/Math/Vector2.h | 8 ++--- Source/Engine/Core/Math/Vector3.cs | 44 ++++++++++++++------------- Source/Engine/Core/Math/Vector3.h | 8 ++--- Source/Engine/Core/Math/Vector4.cs | 36 +++++++++++----------- 13 files changed, 80 insertions(+), 72 deletions(-) diff --git a/Source/Engine/Core/Math/Double2.cs b/Source/Engine/Core/Math/Double2.cs index 825582ceb..20a8094f6 100644 --- a/Source/Engine/Core/Math/Double2.cs +++ b/Source/Engine/Core/Math/Double2.cs @@ -268,7 +268,7 @@ namespace FlaxEngine public void Normalize() { double length = Length; - if (!Mathd.IsZero(length)) + if (length >= Mathd.Epsilon) { double inv = 1.0 / length; X *= inv; diff --git a/Source/Engine/Core/Math/Double3.cs b/Source/Engine/Core/Math/Double3.cs index d38320254..85360f0ff 100644 --- a/Source/Engine/Core/Math/Double3.cs +++ b/Source/Engine/Core/Math/Double3.cs @@ -345,7 +345,7 @@ namespace FlaxEngine public void Normalize() { double length = Length; - if (!Mathd.IsZero(length)) + if (length >= Mathd.Epsilon) { double inv = 1.0 / length; X *= inv; diff --git a/Source/Engine/Core/Math/Double4.cs b/Source/Engine/Core/Math/Double4.cs index 774b3b5ed..bf69f2c72 100644 --- a/Source/Engine/Core/Math/Double4.cs +++ b/Source/Engine/Core/Math/Double4.cs @@ -322,7 +322,7 @@ namespace FlaxEngine public void Normalize() { double length = Length; - if (!Mathd.IsZero(length)) + if (length >= Mathd.Epsilon) { double inverse = 1.0 / length; X *= inverse; diff --git a/Source/Engine/Core/Math/Float2.cs b/Source/Engine/Core/Math/Float2.cs index 15f412d6e..a53fddd34 100644 --- a/Source/Engine/Core/Math/Float2.cs +++ b/Source/Engine/Core/Math/Float2.cs @@ -286,7 +286,7 @@ namespace FlaxEngine public void Normalize() { float length = Length; - if (!Mathf.IsZero(length)) + if (length >= Mathf.Epsilon) { float inv = 1.0f / length; X *= inv; diff --git a/Source/Engine/Core/Math/Float3.cs b/Source/Engine/Core/Math/Float3.cs index e0ef9b1da..9ea922c2b 100644 --- a/Source/Engine/Core/Math/Float3.cs +++ b/Source/Engine/Core/Math/Float3.cs @@ -339,7 +339,7 @@ namespace FlaxEngine public void Normalize() { float length = Length; - if (!Mathf.IsZero(length)) + if (length >= Mathf.Epsilon) { float inv = 1.0f / length; X *= inv; diff --git a/Source/Engine/Core/Math/Float4.cs b/Source/Engine/Core/Math/Float4.cs index 1504a9e03..eb10e5db4 100644 --- a/Source/Engine/Core/Math/Float4.cs +++ b/Source/Engine/Core/Math/Float4.cs @@ -304,7 +304,7 @@ namespace FlaxEngine public void Normalize() { float length = Length; - if (!Mathf.IsZero(length)) + if (length >= Mathf.Epsilon) { float inverse = 1.0f / length; X *= inverse; diff --git a/Source/Engine/Core/Math/Plane.cs b/Source/Engine/Core/Math/Plane.cs index 66d6a7376..1da997cab 100644 --- a/Source/Engine/Core/Math/Plane.cs +++ b/Source/Engine/Core/Math/Plane.cs @@ -2,8 +2,10 @@ #if USE_LARGE_WORLDS using Real = System.Double; +using Mathr = FlaxEngine.Mathd; #else using Real = System.Single; +using Mathr = FlaxEngine.Mathf; #endif // ----------------------------------------------------------------------------- @@ -175,7 +177,7 @@ namespace FlaxEngine public void Normalize() { Real length = Normal.Length; - if (!Mathf.IsZero(length)) + if (length >= Mathr.Epsilon) { Real rcp = 1.0f / length; Normal.X *= rcp; diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs index 30cc39208..5fccd5941 100644 --- a/Source/Engine/Core/Math/Quaternion.cs +++ b/Source/Engine/Core/Math/Quaternion.cs @@ -336,7 +336,7 @@ namespace FlaxEngine public void Normalize() { float length = Length; - if (!Mathf.IsZero(length)) + if (length >= Mathf.Epsilon) { float inverse = 1.0f / length; X *= inverse; diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index cd1849383..ea84628b2 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -2,8 +2,10 @@ #if USE_LARGE_WORLDS using Real = System.Double; +using Mathr = FlaxEngine.Mathd; #else using Real = System.Single; +using Mathr = FlaxEngine.Mathf; #endif // ----------------------------------------------------------------------------- @@ -203,22 +205,22 @@ namespace FlaxEngine /// /// Gets a value indicting whether this instance is normalized. /// - public bool IsNormalized => Mathf.IsOne(X * X + Y * Y); + public bool IsNormalized => Mathr.IsOne(X * X + Y * Y); /// /// Gets a value indicting whether this vector is zero /// - public bool IsZero => Mathf.IsZero(X) && Mathf.IsZero(Y); + public bool IsZero => Mathr.IsZero(X) && Mathr.IsZero(Y); /// /// Gets a minimum component value /// - public Real MinValue => Mathf.Min(X, Y); + public Real MinValue => Mathr.Min(X, Y); /// /// Gets a maximum component value /// - public Real MaxValue => Mathf.Max(X, Y); + public Real MaxValue => Mathr.Max(X, Y); /// /// Gets an arithmetic average value of all vector components. @@ -233,7 +235,7 @@ namespace FlaxEngine /// /// Gets a vector with values being absolute values of that vector. /// - public Vector2 Absolute => new Vector2(Mathf.Abs(X), Mathf.Abs(Y)); + public Vector2 Absolute => new Vector2(Mathr.Abs(X), Mathr.Abs(Y)); /// /// Gets a vector with values being opposite to values of that vector. @@ -292,8 +294,8 @@ namespace FlaxEngine /// public void Normalize() { - Real length = Length; - if (!Mathf.IsZero(length)) + Real length = (Real)Math.Sqrt(X * X + Y * Y); + if (length >= Mathr.Epsilon) { Real inv = 1.0f / length; X *= inv; @@ -904,8 +906,8 @@ namespace FlaxEngine /// Passing a value of 0 will cause to be returned; a value of 1 will cause to be returned. public static void Lerp(ref Vector2 start, ref Vector2 end, float amount, out Vector2 result) { - result.X = Mathf.Lerp(start.X, end.X, amount); - result.Y = Mathf.Lerp(start.Y, end.Y, amount); + result.X = Mathr.Lerp(start.X, end.X, amount); + result.Y = Mathr.Lerp(start.Y, end.Y, amount); } /// @@ -932,8 +934,8 @@ namespace FlaxEngine /// Passing a value of 0 will cause to be returned; a value of 1 will cause to be returned. public static void Lerp(ref Vector2 start, ref Vector2 end, ref Vector2 amount, out Vector2 result) { - result.X = Mathf.Lerp(start.X, end.X, amount.X); - result.Y = Mathf.Lerp(start.Y, end.Y, amount.Y); + result.X = Mathr.Lerp(start.X, end.X, amount.X); + result.Y = Mathr.Lerp(start.Y, end.Y, amount.Y); } /// @@ -959,7 +961,7 @@ namespace FlaxEngine /// When the method completes, contains the cubic interpolation of the two vectors. public static void SmoothStep(ref Vector2 start, ref Vector2 end, float amount, out Vector2 result) { - amount = Mathf.SmoothStep(amount); + amount = Mathr.SmoothStep(amount); Lerp(ref start, ref end, amount, out result); } @@ -1552,7 +1554,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Vector2 left, Vector2 right) { - return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y); + return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y); } /// @@ -1564,7 +1566,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Vector2 left, Vector2 right) { - return !Mathf.NearEqual(left.X, right.X) || !Mathf.NearEqual(left.Y, right.Y); + return !Mathr.NearEqual(left.X, right.X) || !Mathr.NearEqual(left.Y, right.Y); } /// @@ -1670,7 +1672,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Vector2 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y); + return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y); } /// @@ -1678,7 +1680,7 @@ namespace FlaxEngine /// public static bool Equals(ref Vector2 a, ref Vector2 b) { - return Mathf.NearEqual(a.X, b.X) && Mathf.NearEqual(a.Y, b.Y); + return Mathr.NearEqual(a.X, b.X) && Mathr.NearEqual(a.Y, b.Y); } /// @@ -1689,7 +1691,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Vector2 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y); + return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y); } /// @@ -1699,7 +1701,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Vector2 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y); + return value is Vector2 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y); } } } diff --git a/Source/Engine/Core/Math/Vector2.h b/Source/Engine/Core/Math/Vector2.h index cff32539d..87ced1de6 100644 --- a/Source/Engine/Core/Math/Vector2.h +++ b/Source/Engine/Core/Math/Vector2.h @@ -240,9 +240,9 @@ public: void Normalize() { const T length = Math::Sqrt(X * X + Y * Y); - if (!Math::IsZero(length)) + if (length >= ZeroTolerance) { - const T invLength = 1.0f / length; + const T invLength = (T)1.0f / length; X *= invLength; Y *= invLength; } @@ -547,9 +547,9 @@ public: { Vector2Base r = v; const T length = Math::Sqrt(r.X * r.X + r.Y * r.Y); - if (Math::Abs(length) >= ZeroTolerance) + if (length >= ZeroTolerance) { - const T inv = 1.0f / length; + const T inv = (T)1.0f / length; r.X *= inv; r.Y *= inv; } diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index 3c318117f..07798e9a6 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -2,8 +2,10 @@ #if USE_LARGE_WORLDS using Real = System.Double; +using Mathr = FlaxEngine.Mathd; #else using Real = System.Single; +using Mathr = FlaxEngine.Mathf; #endif // ----------------------------------------------------------------------------- @@ -252,7 +254,7 @@ namespace FlaxEngine /// /// Gets a value indicting whether this instance is normalized. /// - public bool IsNormalized => Mathf.IsOne(X * X + Y * Y + Z * Z); + public bool IsNormalized => Mathr.IsOne(X * X + Y * Y + Z * Z); /// /// Gets the normalized vector. Returned vector has length equal 1. @@ -270,22 +272,22 @@ namespace FlaxEngine /// /// Gets a value indicting whether this vector is zero /// - public bool IsZero => Mathf.IsZero(X) && Mathf.IsZero(Y) && Mathf.IsZero(Z); + public bool IsZero => Mathr.IsZero(X) && Mathr.IsZero(Y) && Mathr.IsZero(Z); /// /// Gets a value indicting whether this vector is one /// - public bool IsOne => Mathf.IsOne(X) && Mathf.IsOne(Y) && Mathf.IsOne(Z); + public bool IsOne => Mathr.IsOne(X) && Mathr.IsOne(Y) && Mathr.IsOne(Z); /// /// Gets a minimum component value /// - public Real MinValue => Mathf.Min(X, Mathf.Min(Y, Z)); + public Real MinValue => Mathr.Min(X, Mathr.Min(Y, Z)); /// /// Gets a maximum component value /// - public Real MaxValue => Mathf.Max(X, Mathf.Max(Y, Z)); + public Real MaxValue => Mathr.Max(X, Mathr.Max(Y, Z)); /// /// Gets an arithmetic average value of all vector components. @@ -300,7 +302,7 @@ namespace FlaxEngine /// /// Gets a vector with values being absolute values of that vector. /// - public Vector3 Absolute => new Vector3(Mathf.Abs(X), Mathf.Abs(Y), Mathf.Abs(Z)); + public Vector3 Absolute => new Vector3(Mathr.Abs(X), Mathr.Abs(Y), Mathr.Abs(Z)); /// /// Gets a vector with values being opposite to values of that vector. @@ -363,8 +365,8 @@ namespace FlaxEngine /// public void Normalize() { - Real length = Length; - if (!Mathf.IsZero(length)) + Real length = (Real)Math.Sqrt(X * X + Y * Y + Z * Z); + if (length >= Mathr.Epsilon) { Real inv = 1.0f / length; X *= inv; @@ -1020,9 +1022,9 @@ namespace FlaxEngine /// Passing a value of 0 will cause to be returned; a value of 1 will cause to be returned. public static void Lerp(ref Vector3 start, ref Vector3 end, float amount, out Vector3 result) { - result.X = Mathf.Lerp(start.X, end.X, amount); - result.Y = Mathf.Lerp(start.Y, end.Y, amount); - result.Z = Mathf.Lerp(start.Z, end.Z, amount); + result.X = Mathr.Lerp(start.X, end.X, amount); + result.Y = Mathr.Lerp(start.Y, end.Y, amount); + result.Z = Mathr.Lerp(start.Z, end.Z, amount); } /// @@ -1048,7 +1050,7 @@ namespace FlaxEngine /// When the method completes, contains the cubic interpolation of the two vectors. public static void SmoothStep(ref Vector3 start, ref Vector3 end, float amount, out Vector3 result) { - amount = Mathf.SmoothStep(amount); + amount = Mathr.SmoothStep(amount); Lerp(ref start, ref end, amount, out result); } @@ -1210,7 +1212,7 @@ namespace FlaxEngine public static Vector3 Project(Vector3 vector, Vector3 onNormal) { Real sqrMag = Dot(onNormal, onNormal); - if (sqrMag < Mathf.Epsilon) + if (sqrMag < Mathr.Epsilon) return Zero; return onNormal * Dot(vector, onNormal) / sqrMag; } @@ -1234,10 +1236,10 @@ namespace FlaxEngine /// The angle (in degrees). public static Real Angle(Vector3 from, Vector3 to) { - Real dot = Mathf.Clamp(Dot(from.Normalized, to.Normalized), -1.0f, 1.0f); - if (Mathf.Abs(dot) > (1.0f - Mathf.Epsilon)) + Real dot = Mathr.Clamp(Dot(from.Normalized, to.Normalized), -1.0f, 1.0f); + if (Mathr.Abs(dot) > (1.0f - Mathr.Epsilon)) return dot > 0.0f ? 0.0f : 180.0f; - return (Real)Math.Acos(dot) * Mathf.RadiansToDegrees; + return (Real)Math.Acos(dot) * Mathr.RadiansToDegrees; } /// @@ -1825,7 +1827,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Vector3 left, Vector3 right) { - return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y) && Mathf.NearEqual(left.Z, right.Z); + return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y) && Mathr.NearEqual(left.Z, right.Z); } /// @@ -1837,7 +1839,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Vector3 left, Vector3 right) { - return !Mathf.NearEqual(left.X, right.X) || !Mathf.NearEqual(left.Y, right.Y) || !Mathf.NearEqual(left.Z, right.Z); + return !Mathr.NearEqual(left.X, right.X) || !Mathr.NearEqual(left.Y, right.Y) || !Mathr.NearEqual(left.Z, right.Z); } /// @@ -1946,7 +1948,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Vector3 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z); + return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z); } /// @@ -1957,7 +1959,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Vector3 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z); + return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z); } /// @@ -1967,7 +1969,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Vector3 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z); + return value is Vector3 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z); } } } diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index 590c3adb0..33be7f2d6 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -266,9 +266,9 @@ public: void Normalize() { const T length = Math::Sqrt(X * X + Y * Y + Z * Z); - if (Math::Abs(length) >= ZeroTolerance) + if (length >= ZeroTolerance) { - const T inv = 1.0f / length; + const T inv = (T)1.0f / length; X *= inv; Y *= inv; Z *= inv; @@ -645,9 +645,9 @@ public: { Vector3Base r = v; const T length = Math::Sqrt(r.X * r.X + r.Y * r.Y + r.Z * r.Z); - if (Math::Abs(length) >= ZeroTolerance) + if (length >= ZeroTolerance) { - const T inv = 1.0f / length; + const T inv = (T)1.0f / length; r.X *= inv; r.Y *= inv; r.Z *= inv; diff --git a/Source/Engine/Core/Math/Vector4.cs b/Source/Engine/Core/Math/Vector4.cs index 738fcfffb..8c83e5d3e 100644 --- a/Source/Engine/Core/Math/Vector4.cs +++ b/Source/Engine/Core/Math/Vector4.cs @@ -2,8 +2,10 @@ #if USE_LARGE_WORLDS using Real = System.Double; +using Mathr = FlaxEngine.Mathd; #else using Real = System.Single; +using Mathr = FlaxEngine.Mathf; #endif // ----------------------------------------------------------------------------- @@ -255,27 +257,27 @@ namespace FlaxEngine /// /// Gets a value indicting whether this instance is normalized. /// - public bool IsNormalized => Mathf.IsOne(X * X + Y * Y + Z * Z + W * W); + public bool IsNormalized => Mathr.IsOne(X * X + Y * Y + Z * Z + W * W); /// /// Gets a value indicting whether this vector is zero /// - public bool IsZero => Mathf.IsZero(X) && Mathf.IsZero(Y) && Mathf.IsZero(Z) && Mathf.IsZero(W); + public bool IsZero => Mathr.IsZero(X) && Mathr.IsZero(Y) && Mathr.IsZero(Z) && Mathr.IsZero(W); /// /// Gets a value indicting whether this vector is one /// - public bool IsOne => Mathf.IsOne(X) && Mathf.IsOne(Y) && Mathf.IsOne(Z) && Mathf.IsOne(W); + public bool IsOne => Mathr.IsOne(X) && Mathr.IsOne(Y) && Mathr.IsOne(Z) && Mathr.IsOne(W); /// /// Gets a minimum component value /// - public Real MinValue => Mathf.Min(X, Mathf.Min(Y, Mathf.Min(Z, W))); + public Real MinValue => Mathr.Min(X, Mathr.Min(Y, Mathr.Min(Z, W))); /// /// Gets a maximum component value /// - public Real MaxValue => Mathf.Max(X, Mathf.Max(Y, Mathf.Max(Z, W))); + public Real MaxValue => Mathr.Max(X, Mathr.Max(Y, Mathr.Max(Z, W))); /// /// Gets an arithmetic average value of all vector components. @@ -290,7 +292,7 @@ namespace FlaxEngine /// /// Gets a vector with values being absolute values of that vector. /// - public Vector4 Absolute => new Vector4(Mathf.Abs(X), Mathf.Abs(Y), Mathf.Abs(Z), Mathf.Abs(W)); + public Vector4 Absolute => new Vector4(Mathr.Abs(X), Mathr.Abs(Y), Mathr.Abs(Z), Mathr.Abs(W)); /// /// Gets a vector with values being opposite to values of that vector. @@ -357,8 +359,8 @@ namespace FlaxEngine /// public void Normalize() { - Real length = Length; - if (!Mathf.IsZero(length)) + Real length = (Real)Math.Sqrt(X * X + Y * Y + Z * Z + W * W); + if (length >= Mathr.Epsilon) { Real inverse = 1.0f / length; X *= inverse; @@ -855,10 +857,10 @@ namespace FlaxEngine /// Passing a value of 0 will cause to be returned; a value of 1 will cause to be returned. public static void Lerp(ref Vector4 start, ref Vector4 end, Real amount, out Vector4 result) { - result.X = Mathf.Lerp(start.X, end.X, amount); - result.Y = Mathf.Lerp(start.Y, end.Y, amount); - result.Z = Mathf.Lerp(start.Z, end.Z, amount); - result.W = Mathf.Lerp(start.W, end.W, amount); + result.X = Mathr.Lerp(start.X, end.X, amount); + result.Y = Mathr.Lerp(start.Y, end.Y, amount); + result.Z = Mathr.Lerp(start.Z, end.Z, amount); + result.W = Mathr.Lerp(start.W, end.W, amount); } /// @@ -884,7 +886,7 @@ namespace FlaxEngine /// When the method completes, contains the cubic interpolation of the two vectors. public static void SmoothStep(ref Vector4 start, ref Vector4 end, Real amount, out Vector4 result) { - amount = Mathf.SmoothStep(amount); + amount = Mathr.SmoothStep(amount); Lerp(ref start, ref end, amount, out result); } @@ -1359,7 +1361,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Vector4 left, Vector4 right) { - return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y) && Mathf.NearEqual(left.Z, right.Z) && Mathf.NearEqual(left.W, right.W); + return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y) && Mathr.NearEqual(left.Z, right.Z) && Mathr.NearEqual(left.W, right.W); } /// @@ -1480,7 +1482,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public bool Equals(ref Vector4 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); + return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W); } /// @@ -1491,7 +1493,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Vector4 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); + return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W); } /// @@ -1501,7 +1503,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Vector4 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); + return value is Vector4 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W); } } } From d5b8e44572a8298f4965dd2a1cbe86bd8fe45a9c Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Tue, 14 Mar 2023 19:47:59 +0200 Subject: [PATCH 23/28] Cache networking code generation task results --- Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index b4e52108d..9404b6617 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -529,7 +529,6 @@ namespace Flax.Build.Plugins task.CommandPath = null; task.InfoMessage = $"Generating networking code for {Path.GetFileName(assemblyPath)}..."; task.Cost = 50; - task.DisableCache = true; task.DependentTasks = new HashSet(); task.DependentTasks.Add(buildTask); } From 71b5b528fdc30b5f97cbda86efd477b540d627be Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Apr 2023 11:52:19 +0200 Subject: [PATCH 24/28] Remove shadows casting and sdf data from editor camera model --- Content/Editor/Camera/O_Camera.flax | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content/Editor/Camera/O_Camera.flax b/Content/Editor/Camera/O_Camera.flax index 5e0940624..47d37472d 100644 --- a/Content/Editor/Camera/O_Camera.flax +++ b/Content/Editor/Camera/O_Camera.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2ec3410338bc342f7de1c4af6ae0f6310c739140e83de45632f3a3bc7c47f12 -size 88720 +oid sha256:0f9bbd661420e4f930a995acc46c9530fcacb8836db4d0bbfa5df184e4d1a5cc +size 88495 From 2eea7abc2771c9cea67f3f3aa0c54f1337e63317 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Apr 2023 12:20:02 +0200 Subject: [PATCH 25/28] Add `Create collision data` action to be performed for each model selected in the Content Window --- .../Content/Proxy/CollisionDataProxy.cs | 5 +- Source/Editor/Content/Proxy/ModelProxy.cs | 18 +++- Source/Editor/Windows/ContentWindow.cs | 94 ++++++++++++++----- 3 files changed, 87 insertions(+), 30 deletions(-) diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs index e865834fd..55e8c6327 100644 --- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs +++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs @@ -80,7 +80,8 @@ namespace FlaxEditor.Content /// /// The associated model. /// The action to call once the collision data gets created (or reused from existing). - public void CreateCollisionDataFromModel(Model model, Action created = null) + /// True if start initial item renaming by user, or tru to skip it. + public void CreateCollisionDataFromModel(Model model, Action created = null, bool withRenaming = true) { // Check if there already is collision data for that model to reuse var modelItem = (AssetItem)Editor.Instance.ContentDatabase.Find(model.ID); @@ -140,7 +141,7 @@ namespace FlaxEditor.Content }); }; var initialName = (modelItem?.ShortName ?? Path.GetFileNameWithoutExtension(model.Path)) + " Collision"; - Editor.Instance.Windows.ContentWin.NewItem(this, null, create, initialName); + Editor.Instance.Windows.ContentWin.NewItem(this, null, create, initialName, withRenaming); } } } diff --git a/Source/Editor/Content/Proxy/ModelProxy.cs b/Source/Editor/Content/Proxy/ModelProxy.cs index ec6b3cd1b..845cbc80b 100644 --- a/Source/Editor/Content/Proxy/ModelProxy.cs +++ b/Source/Editor/Content/Proxy/ModelProxy.cs @@ -47,9 +47,23 @@ namespace FlaxEditor.Content menu.AddButton("Create collision data", () => { - var model = FlaxEngine.Content.LoadAsync(((ModelItem)item).ID); var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy(); - collisionDataProxy.CreateCollisionDataFromModel(model); + var selection = Editor.Instance.Windows.ContentWin.View.Selection; + if (selection.Count > 1) + { + // Batch action + var items = selection.ToArray(); // Clone to prevent issue when iterating over and content window changes the selection + foreach (var contentItem in items) + { + if (contentItem is ModelItem modelItem) + collisionDataProxy.CreateCollisionDataFromModel(FlaxEngine.Content.LoadAsync(modelItem.ID), null, false); + } + } + else + { + var model = FlaxEngine.Content.LoadAsync(((ModelItem)item).ID); + collisionDataProxy.CreateCollisionDataFromModel(model); + } }); } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 654eaa91d..82f700274 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -319,6 +319,7 @@ namespace FlaxEditor.Windows /// Shows popup dialog with UI to rename content item. /// /// The item to rename. + /// The created renaming popup. public void Rename(ContentItem item) { // Show element in the view @@ -336,24 +337,7 @@ namespace FlaxEditor.Windows popup.Tag = item; popup.Validate += OnRenameValidate; popup.Renamed += renamePopup => Rename((ContentItem)renamePopup.Tag, renamePopup.Text); - popup.Closed += renamePopup => - { - // Restore scrolling in content view - if (_contentViewPanel.VScrollBar != null) - _contentViewPanel.VScrollBar.ThumbEnabled = true; - if (_contentViewPanel.HScrollBar != null) - _contentViewPanel.HScrollBar.ThumbEnabled = true; - ScrollingOnContentView(true); - - // Check if was creating new element - if (_newElement != null) - { - // Destroy mock control - _newElement.ParentFolder = null; - _newElement.Dispose(); - _newElement = null; - } - }; + popup.Closed += OnRenameClosed; // For new asset we want to mock the initial value so user can press just Enter to use default name if (_newElement != null) @@ -367,6 +351,25 @@ namespace FlaxEditor.Windows return Editor.ContentEditing.IsValidAssetName((ContentItem)popup.Tag, value, out _); } + private void OnRenameClosed(RenamePopup popup) + { + // Restore scrolling in content view + if (_contentViewPanel.VScrollBar != null) + _contentViewPanel.VScrollBar.ThumbEnabled = true; + if (_contentViewPanel.HScrollBar != null) + _contentViewPanel.HScrollBar.ThumbEnabled = true; + ScrollingOnContentView(true); + + // Check if was creating new element + if (_newElement != null) + { + // Destroy mock control + _newElement.ParentFolder = null; + _newElement.Dispose(); + _newElement = null; + } + } + /// /// Renames the specified item. /// @@ -644,7 +647,8 @@ namespace FlaxEditor.Windows /// The argument passed to the proxy for the item creation. In most cases it is null. /// The event called when the item is crated by the user. The argument is the new item. /// The initial item name. - public void NewItem(ContentProxy proxy, object argument = null, Action created = null, string initialName = null) + /// True if start initial item renaming by user, or tru to skip it. + public void NewItem(ContentProxy proxy, object argument = null, Action created = null, string initialName = null, bool withRenaming = true) { Assert.IsNull(_newElement); if (proxy == null) @@ -666,14 +670,52 @@ namespace FlaxEditor.Windows } while (parentFolder.FindChild(path) != null); } - // Create new asset proxy, add to view and rename it - _newElement = new NewItem(path, proxy, argument) + if (withRenaming) { - ParentFolder = parentFolder, - Tag = created, - }; - RefreshView(); - Rename(_newElement); + // Create new asset proxy, add to view and rename it + _newElement = new NewItem(path, proxy, argument) + { + ParentFolder = parentFolder, + Tag = created, + }; + RefreshView(); + Rename(_newElement); + } + else + { + // Create new asset + try + { + Editor.Log(string.Format("Creating asset {0} in {1}", proxy.Name, path)); + proxy.Create(path, argument); + } + catch (Exception ex) + { + Editor.LogWarning(ex); + Editor.LogError("Failed to create asset."); + return; + } + + // Focus content window + Focus(); + RootWindow?.Focus(); + + // Refresh database and view now + Editor.ContentDatabase.RefreshFolder(parentFolder, false); + RefreshView(); + var newItem = parentFolder.FindChild(path); + if (newItem == null) + { + Editor.LogWarning("Failed to find the created new item."); + return; + } + + // Auto-select item + Select(newItem, true); + + // Custom post-action + created?.Invoke(newItem); + } } private void ContentDatabaseOnItemRemoved(ContentItem contentItem) From e9bdc8b319cfe1113bff2b45c6dbe6f6ec8ee937 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Apr 2023 12:36:59 +0200 Subject: [PATCH 26/28] Fix crash when starting drag&drop with invalid control state (detached from window) --- Source/Engine/UI/GUI/Control.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index 8b2401428..427c1aa8f 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -987,7 +987,7 @@ namespace FlaxEngine.GUI { // Hide tooltip Tooltip?.Hide(); - Root.DoDragDrop(data); + Root?.DoDragDrop(data); } #endregion From 414bce38bba27021dd8a7b3e0e42c8b4e3de4914 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Apr 2023 12:54:01 +0200 Subject: [PATCH 27/28] DOn't even start drag and drop if content item is detached from UI --- Source/Editor/Content/Items/ContentItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs index fb930d192..284f3f1b3 100644 --- a/Source/Editor/Content/Items/ContentItem.cs +++ b/Source/Editor/Content/Items/ContentItem.cs @@ -222,7 +222,7 @@ namespace FlaxEditor.Content /// /// Gets a value indicating whether this item can be dragged and dropped. /// - public virtual bool CanDrag => true; + public virtual bool CanDrag => Root != null; /// /// Gets a value indicating whether this exists on drive. From 378e48e7aaaa92e3dd264fe3251b322b69021613 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Apr 2023 13:06:56 +0200 Subject: [PATCH 28/28] Fix cached `DraggedOverNode` to be properly reset after drag and #996 --- Source/Editor/GUI/Tree/TreeNode.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index cd4fc9647..8691cd14d 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -922,7 +922,8 @@ namespace FlaxEditor.GUI.Tree if (result == DragDropEffect.None) { UpdateDrawPositioning(ref location); - _tree.DraggedOverNode = this; + if (ParentTree != null) + ParentTree.DraggedOverNode = this; // Check if mouse is over header _isDragOverHeader = TestHeaderHit(ref location); @@ -1000,6 +1001,8 @@ namespace FlaxEditor.GUI.Tree // Clear cache _isDragOverHeader = false; _dragOverMode = DragItemPositioning.None; + if (ParentTree != null) + ParentTree.DraggedOverNode = null; return result; }