diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs
index 070ad1fb3..7f653376b 100644
--- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs
+++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
+using FlaxEditor.CustomEditors.Dedicated;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.Scripting;
using FlaxEngine;
@@ -107,7 +108,7 @@ namespace FlaxEditor.CustomEditors
// Select default editor (based on type)
if (targetType.IsEnum)
return new EnumEditor();
- if (targetType.IsGenericType)
+ if (targetType.IsGenericType)
{
if (targetTypeType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
return new DictionaryEditor();
@@ -118,6 +119,8 @@ namespace FlaxEditor.CustomEditors
if (customEditorType != null)
return (CustomEditor)Activator.CreateInstance(customEditorType);
}
+ if (typeof(FlaxEngine.Object).IsAssignableFrom(targetTypeType))
+ return new ScriptingObjectEditor();
// The most generic editor
return new GenericEditor();
diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
index 75e3fff23..a8773a8d7 100644
--- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
@@ -20,7 +20,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
///
///
[CustomEditor(typeof(Actor)), DefaultEditor]
- public class ActorEditor : GenericEditor
+ public class ActorEditor : ScriptingObjectEditor
{
private Guid _linkedPrefabId;
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptingObjectEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptingObjectEditor.cs
new file mode 100644
index 000000000..477deb708
--- /dev/null
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptingObjectEditor.cs
@@ -0,0 +1,29 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using FlaxEditor.CustomEditors.Editors;
+using FlaxEngine.Networking;
+
+namespace FlaxEditor.CustomEditors.Dedicated
+{
+ ///
+ /// Custom editor for .
+ ///
+ public class ScriptingObjectEditor : GenericEditor
+ {
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ // Network objects debugging
+ var obj = Values[0] as FlaxEngine.Object;
+ if (Editor.IsPlayMode && NetworkManager.IsConnected && NetworkReplicator.HasObject(obj))
+ {
+ var group = layout.Group("Network");
+ group.Panel.Open();
+ group.Label("Role", Utilities.Utils.GetPropertyNameUI(NetworkReplicator.GetObjectRole(obj).ToString()));
+ group.Label("Owner Client Id", NetworkReplicator.GetObjectOwnerClientId(obj).ToString());
+ }
+
+ base.Initialize(layout);
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index 73a43f1bf..613f5d3b4 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.Linq;
using FlaxEditor.Actions;
using FlaxEditor.Content;
using FlaxEditor.GUI;
diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
index 2612440a4..076b94b0a 100644
--- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
@@ -78,7 +78,7 @@ namespace FlaxEditor.CustomEditors.Editors
///
public class ScaleEditor : Float3Editor
{
- private Image _linkImage;
+ private Button _linkButton;
///
public override void Initialize(LayoutElementsContainer layout)
@@ -87,19 +87,20 @@ namespace FlaxEditor.CustomEditors.Editors
LinkValues = Editor.Instance.Windows.PropertiesWin.ScaleLinked;
- _linkImage = new Image
+ // Add button with the link icon
+ _linkButton = new Button
{
+ BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Link32),
Parent = LinkedLabel,
Width = 18,
Height = 18,
- Brush = LinkValues ? new SpriteBrush(Editor.Instance.Icons.Link32) : new SpriteBrush(),
AnchorPreset = AnchorPresets.TopLeft,
- TooltipText = "Scale values are linked together.",
};
+ _linkButton.Clicked += ToggleLink;
+ SetLinkStyle();
var x = LinkedLabel.Text.Value.Length * 7 + 5;
- _linkImage.LocalX += x;
- _linkImage.LocalY += 1;
-
+ _linkButton.LocalX += x;
+ _linkButton.LocalY += 1;
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
{
menu.AddSeparator();
@@ -127,7 +128,16 @@ namespace FlaxEditor.CustomEditors.Editors
{
LinkValues = !LinkValues;
Editor.Instance.Windows.PropertiesWin.ScaleLinked = LinkValues;
- _linkImage.Brush = LinkValues ? new SpriteBrush(Editor.Instance.Icons.Link32) : new SpriteBrush();
+ SetLinkStyle();
+ }
+
+ private void SetLinkStyle()
+ {
+ var style = FlaxEngine.GUI.Style.Current;
+ var backgroundColor = LinkValues ? style.Foreground : style.ForegroundDisabled;
+ _linkButton.SetColors(backgroundColor);
+ _linkButton.BorderColor = _linkButton.BorderColorSelected = _linkButton.BorderColorHighlighted = Color.Transparent;
+ _linkButton.TooltipText = LinkValues ? "Unlinks scale components from uniform scaling" : "Links scale components for uniform scaling";
}
}
}
diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs
index 544253fa5..f2f3b4aff 100644
--- a/Source/Editor/Windows/OutputLogWindow.cs
+++ b/Source/Editor/Windows/OutputLogWindow.cs
@@ -541,7 +541,7 @@ namespace FlaxEditor.Windows
{
ref var line = ref lines[j];
textBlock.Range.StartIndex = startIndex + line.FirstCharIndex;
- textBlock.Range.EndIndex = startIndex + line.LastCharIndex;
+ textBlock.Range.EndIndex = startIndex + line.LastCharIndex + 1;
textBlock.Bounds = new Rectangle(new Float2(0.0f, prevBlockBottom), line.Size);
if (textBlock.Range.Length > 0)
@@ -550,7 +550,7 @@ namespace FlaxEditor.Windows
var regexStart = line.FirstCharIndex;
if (j == 0)
regexStart += prefixLength;
- var regexLength = line.LastCharIndex - regexStart;
+ var regexLength = line.LastCharIndex + 1 - regexStart;
if (regexLength > 0)
{
var match = _compileRegex.Match(entryText, regexStart, regexLength);
diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp
index 88a104c3e..88c59ad9a 100644
--- a/Source/Engine/Networking/NetworkManager.cpp
+++ b/Source/Engine/Networking/NetworkManager.cpp
@@ -317,11 +317,12 @@ bool NetworkManager::StartHost()
LocalClient = New(LocalClientId, NetworkConnection{ 0 });
// Auto-connect host
+ LocalClient->State = NetworkConnectionState::Connecting;
+ State = NetworkConnectionState::Connected;
+ StateChanged();
LocalClient->State = NetworkConnectionState::Connected;
ClientConnected(LocalClient);
- State = NetworkConnectionState::Connected;
- StateChanged();
return false;
}
diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp
index e150b1ea0..be7571b80 100644
--- a/Source/Engine/Networking/NetworkReplicator.cpp
+++ b/Source/Engine/Networking/NetworkReplicator.cpp
@@ -745,7 +745,7 @@ bool NetworkReplicator::InvokeSerializer(const ScriptingTypeHandle& typeHandle,
return false;
}
-void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* parent)
+void NetworkReplicator::AddObject(ScriptingObject* obj, const ScriptingObject* parent)
{
if (!obj || NetworkManager::IsOffline())
return;
@@ -774,6 +774,19 @@ void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* parent)
item.OwnerClientId = NetworkManager::ServerClientId; // Server owns objects by default
item.Role = NetworkManager::IsClient() ? NetworkObjectRole::Replicated : NetworkObjectRole::OwnedAuthoritative;
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Add new object {}:{}, parent {}:{}", item.ToString(), obj->GetType().ToString(), item.ParentId.ToString(), parent ? parent->GetType().ToString() : String::Empty);
+ for (const SpawnItem& spawnItem : SpawnQueue)
+ {
+ if (spawnItem.HasOwnership && spawnItem.HierarchicalOwnership)
+ {
+ if (IsParentOf(obj, spawnItem.Object))
+ {
+ // Inherit ownership
+ item.Role = spawnItem.Role;
+ item.OwnerClientId = spawnItem.OwnerClientId;
+ break;
+ }
+ }
+ }
Objects.Add(MoveTemp(item));
}
@@ -861,10 +874,27 @@ void NetworkReplicator::DespawnObject(ScriptingObject* obj)
DeleteNetworkObject(obj);
}
+bool NetworkReplicator::HasObject(const ScriptingObject* obj)
+{
+ if (obj)
+ {
+ ScopeLock lock(ObjectsLock);
+ const auto it = Objects.Find(obj->GetID());
+ if (it != Objects.End())
+ return true;
+ for (const SpawnItem& item : SpawnQueue)
+ {
+ if (item.Object == obj)
+ return true;
+ }
+ }
+ return false;
+}
+
uint32 NetworkReplicator::GetObjectOwnerClientId(const ScriptingObject* obj)
{
uint32 id = NetworkManager::ServerClientId;
- if (obj)
+ if (obj && NetworkManager::IsConnected())
{
ScopeLock lock(ObjectsLock);
const auto it = Objects.Find(obj->GetID());
@@ -878,9 +908,16 @@ uint32 NetworkReplicator::GetObjectOwnerClientId(const ScriptingObject* obj)
{
if (item.HasOwnership)
id = item.OwnerClientId;
+#if USE_NETWORK_REPLICATOR_LOG
+ return id;
+#else
break;
+#endif
}
}
+#if USE_NETWORK_REPLICATOR_LOG
+ NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to get ownership of unregistered network object {} ({})", obj->GetID(), obj->GetType().ToString());
+#endif
}
}
return id;
@@ -889,7 +926,7 @@ uint32 NetworkReplicator::GetObjectOwnerClientId(const ScriptingObject* obj)
NetworkObjectRole NetworkReplicator::GetObjectRole(const ScriptingObject* obj)
{
NetworkObjectRole role = NetworkObjectRole::None;
- if (obj)
+ if (obj && NetworkManager::IsConnected())
{
ScopeLock lock(ObjectsLock);
const auto it = Objects.Find(obj->GetID());
@@ -903,9 +940,16 @@ NetworkObjectRole NetworkReplicator::GetObjectRole(const ScriptingObject* obj)
{
if (item.HasOwnership)
role = item.Role;
+#if USE_NETWORK_REPLICATOR_LOG
+ return role;
+#else
break;
+#endif
}
}
+#if USE_NETWORK_REPLICATOR_LOG
+ NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to get ownership of unregistered network object {} ({})", obj->GetID(), obj->GetType().ToString());
+#endif
}
}
return role;
@@ -913,10 +957,11 @@ NetworkObjectRole NetworkReplicator::GetObjectRole(const ScriptingObject* obj)
void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerClientId, NetworkObjectRole localRole, bool hierarchical)
{
- if (!obj)
+ if (!obj || NetworkManager::IsOffline())
return;
+ const Guid objectId = obj->GetID();
ScopeLock lock(ObjectsLock);
- const auto it = Objects.Find(obj->GetID());
+ const auto it = Objects.Find(objectId);
if (it == Objects.End())
{
// Special case if we're just spawning this object
@@ -944,31 +989,33 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli
break;
}
}
- return;
- }
- auto& item = it->Item;
- if (item.Object != obj)
- return;
-
- // Check if this client is object owner
- if (item.OwnerClientId == NetworkManager::LocalClientId)
- {
- // Check if object owner will change
- if (item.OwnerClientId != ownerClientId)
- {
- // Change role locally
- CHECK(localRole != NetworkObjectRole::OwnedAuthoritative);
- item.OwnerClientId = ownerClientId;
- item.LastOwnerFrame = 1;
- item.Role = localRole;
- SendObjectRoleMessage(item);
- }
}
else
{
- // Allow to change local role of the object (except ownership)
- CHECK(localRole != NetworkObjectRole::OwnedAuthoritative);
- item.Role = localRole;
+ auto& item = it->Item;
+ if (item.Object != obj)
+ return;
+
+ // Check if this client is object owner
+ if (item.OwnerClientId == NetworkManager::LocalClientId)
+ {
+ // Check if object owner will change
+ if (item.OwnerClientId != ownerClientId)
+ {
+ // Change role locally
+ CHECK(localRole != NetworkObjectRole::OwnedAuthoritative);
+ item.OwnerClientId = ownerClientId;
+ item.LastOwnerFrame = 1;
+ item.Role = localRole;
+ SendObjectRoleMessage(item);
+ }
+ }
+ else
+ {
+ // Allow to change local role of the object (except ownership)
+ CHECK(localRole != NetworkObjectRole::OwnedAuthoritative);
+ item.Role = localRole;
+ }
}
// Go down hierarchy
@@ -976,9 +1023,15 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli
{
for (auto& e : Objects)
{
- if (e.Item.ParentId == item.ObjectId)
+ if (e.Item.ParentId == objectId)
SetObjectOwnership(e.Item.Object.Get(), ownerClientId, localRole, hierarchical);
}
+
+ for (const SpawnItem& spawnItem : SpawnQueue)
+ {
+ if (IsParentOf(spawnItem.Object, obj))
+ SetObjectOwnership(spawnItem.Object, ownerClientId, localRole, hierarchical);
+ }
}
}
@@ -1182,9 +1235,11 @@ void NetworkInternal::NetworkReplicatorUpdate()
{
if (!q.HasOwnership && IsParentOf(q.Object, e.Object))
{
+ // Inherit ownership
q.HasOwnership = true;
q.Role = e.Role;
q.OwnerClientId = e.OwnerClientId;
+ break;
}
}
}
@@ -1623,7 +1678,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
}
}
- // Setup all newly spawned objects
+ // Add all newly spawned objects
for (int32 i = 0; i < msgData.ItemsCount; i++)
{
auto& msgDataItem = msgDataItems[i];
@@ -1652,6 +1707,16 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
// Boost future lookups by using indirection
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remap object ID={} into object {}:{}", msgDataItem.ObjectId, item.ToString(), obj->GetType().ToString());
IdsRemappingTable.Add(msgDataItem.ObjectId, item.ObjectId);
+ }
+
+ // Spawn all newly spawned objects (ensure to have valid ownership hierarchy set before spawning object)
+ for (int32 i = 0; i < msgData.ItemsCount; i++)
+ {
+ auto& msgDataItem = msgDataItems[i];
+ ScriptingObject* obj = objects[i];
+ auto it = Objects.Find(obj->GetID());
+ auto& item = it->Item;
+ const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
// Automatic parenting for scene objects
auto sceneObject = ScriptingObject::Cast(obj);
diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h
index 389350e5f..0e9a2d0d8 100644
--- a/Source/Engine/Networking/NetworkReplicator.h
+++ b/Source/Engine/Networking/NetworkReplicator.h
@@ -68,7 +68,7 @@ public:
/// Does nothing if network is offline.
/// The object to replicate.
/// The parent of the object (eg. player that spawned it).
- API_FUNCTION() static void AddObject(ScriptingObject* obj, ScriptingObject* parent = nullptr);
+ API_FUNCTION() static void AddObject(ScriptingObject* obj, const ScriptingObject* parent = nullptr);
///
/// Removes the object from the network replication system.
@@ -80,14 +80,14 @@ public:
///
/// Spawns the object to the other clients. Can be spawned by the owner who locally created it (eg. from prefab).
///
- /// Does nothing if network is offline.
+ /// Does nothing if network is offline. Doesn't spawn actor in a level - but in network replication system.
/// The object to spawn on other clients.
API_FUNCTION() static void SpawnObject(ScriptingObject* obj);
///
/// Spawns the object to the other clients. Can be spawned by the owner who locally created it (eg. from prefab).
///
- /// Does nothing if network is offline.
+ /// Does nothing if network is offline. Doesn't spawn actor in a level - but in network replication system.
/// The object to spawn on other clients.
/// List with network client IDs that should receive network spawn event. Empty to spawn on all clients.
API_FUNCTION() static void SpawnObject(ScriptingObject* obj, const DataContainer& clientIds);
@@ -99,6 +99,13 @@ public:
/// The object to despawn on other clients.
API_FUNCTION() static void DespawnObject(ScriptingObject* obj);
+ ///
+ /// Checks if the network object is spawned or added to the network replication system.
+ ///
+ /// The network object.
+ /// True if object exists in networking, otherwise false.
+ API_FUNCTION() static bool HasObject(const ScriptingObject* obj);
+
///
/// Gets the Client Id of the network object owner.
///
diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h
index d3806f0b4..3f42fd396 100644
--- a/Source/Engine/Render2D/Font.h
+++ b/Source/Engine/Render2D/Font.h
@@ -17,17 +17,17 @@ class FontAsset;
///
/// The text range.
///
-API_STRUCT() struct TextRange
+API_STRUCT(NoDefault) struct TextRange
{
DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange);
///
- /// The start index.
+ /// The start index (inclusive).
///
API_FIELD() int32 StartIndex;
///
- /// The end index.
+ /// The end index (exclusive).
///
API_FIELD() int32 EndIndex;
@@ -70,7 +70,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange);
///
/// Gets the substring from the source text.
///
- /// The text.
+ /// The text.
/// The substring of the original text of the defined range.
StringView Substring(const StringView& text) const
{
@@ -87,7 +87,7 @@ struct TIsPODType
///
/// The font line info generated during text processing.
///
-API_STRUCT() struct FontLineCache
+API_STRUCT(NoDefault) struct FontLineCache
{
DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache);
@@ -151,7 +151,7 @@ struct TIsPODType
///
/// The cached font character entry (read for rendering and further processing).
///
-API_STRUCT() struct FontCharacterEntry
+API_STRUCT(NoDefault) struct FontCharacterEntry
{
DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry);
diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs
index da6fc1529..8ab6366eb 100644
--- a/Source/Engine/UI/GUI/Common/Button.cs
+++ b/Source/Engine/UI/GUI/Common/Button.cs
@@ -10,7 +10,7 @@ namespace FlaxEngine.GUI
public class Button : ContainerControl
{
///
- /// The default height fro the buttons.
+ /// The default height for the buttons.
///
public const float DefaultHeight = 24.0f;