Merge remote-tracking branch 'origin/master' into 1.6
# Conflicts: # Source/Editor/CustomEditors/CustomEditorsUtil.cs # Source/Engine/Networking/NetworkReplicator.cpp # Source/Engine/Scripting/ManagedCLR/MUtils.cpp # Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
This commit is contained in:
@@ -360,7 +360,7 @@ namespace FlaxEditor.Content
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the tooltip text.
|
||||
/// Updates the tooltip text text.
|
||||
/// </summary>
|
||||
public virtual void UpdateTooltipText()
|
||||
{
|
||||
@@ -384,7 +384,8 @@ namespace FlaxEditor.Content
|
||||
protected virtual void OnBuildTooltipText(StringBuilder sb)
|
||||
{
|
||||
sb.Append("Type: ").Append(TypeDescription).AppendLine();
|
||||
sb.Append("Size: ").Append(Utilities.Utils.FormatBytesCount((int)new FileInfo(Path).Length)).AppendLine();
|
||||
if (File.Exists(Path))
|
||||
sb.Append("Size: ").Append(Utilities.Utils.FormatBytesCount((int)new FileInfo(Path).Length)).AppendLine();
|
||||
sb.Append("Path: ").Append(Utilities.Utils.GetAssetNamePathWithExt(Path)).AppendLine();
|
||||
}
|
||||
|
||||
@@ -718,7 +719,7 @@ namespace FlaxEditor.Content
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
Focus();
|
||||
|
||||
|
||||
// Open
|
||||
(Parent as ContentView).OnItemDoubleClick(this);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Content
|
||||
@@ -47,5 +48,11 @@ namespace FlaxEditor.Content
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool DrawShadow => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void UpdateTooltipText()
|
||||
{
|
||||
TooltipText = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace FlaxEditor.CustomEditors
|
||||
return;
|
||||
|
||||
// Special case for root objects to run normal layout build
|
||||
if (_presenter.Selection == Values)
|
||||
if (_presenter != null && _presenter.Selection == Values)
|
||||
{
|
||||
_presenter.BuildLayout();
|
||||
return;
|
||||
@@ -159,7 +159,7 @@ namespace FlaxEditor.CustomEditors
|
||||
var layout = _layout;
|
||||
var control = layout.ContainerControl;
|
||||
var parent = _parent;
|
||||
var parentScrollV = (_presenter.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;
|
||||
var parentScrollV = (_presenter?.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;
|
||||
|
||||
control.IsLayoutLocked = true;
|
||||
control.DisposeChildren();
|
||||
@@ -249,6 +249,28 @@ namespace FlaxEditor.CustomEditors
|
||||
|
||||
internal virtual void RefreshRootChild()
|
||||
{
|
||||
// Check if need to update value
|
||||
if (_hasValueDirty)
|
||||
{
|
||||
IsSettingValue = true;
|
||||
try
|
||||
{
|
||||
// Cleanup (won't retry update in case of exception)
|
||||
object val = _valueToSet;
|
||||
_hasValueDirty = false;
|
||||
_valueToSet = null;
|
||||
|
||||
// Assign value
|
||||
for (int i = 0; i < _values.Count; i++)
|
||||
_values[i] = val;
|
||||
}
|
||||
finally
|
||||
{
|
||||
OnUnDirty();
|
||||
IsSettingValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
Refresh();
|
||||
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.Marshalling;
|
||||
using FlaxEditor.CustomEditors.Dedicated;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
@@ -110,7 +111,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();
|
||||
@@ -121,6 +122,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();
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.Editors.GenericEditor" />
|
||||
[CustomEditor(typeof(Actor)), DefaultEditor]
|
||||
public class ActorEditor : GenericEditor
|
||||
public class ActorEditor : ScriptingObjectEditor
|
||||
{
|
||||
private Guid _linkedPrefabId;
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEngine.Networking;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="FlaxEngine.Object"/>.
|
||||
/// </summary>
|
||||
public class ScriptingObjectEditor : GenericEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.Editors.Float3Editor" />
|
||||
public class ScaleEditor : Float3Editor
|
||||
{
|
||||
private Image _linkImage;
|
||||
private Button _linkButton;
|
||||
|
||||
/// <inheritdoc />
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
@@ -46,8 +45,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
|
||||
{
|
||||
menu.AddSeparator();
|
||||
|
||||
if (menu.Items.Any())
|
||||
menu.AddSeparator();
|
||||
menu.AddButton("Remove", OnRemoveClicked).Enabled = !_editor._readOnly;
|
||||
menu.AddButton("Edit", OnEditClicked).Enabled = _editor._canEditKeys;
|
||||
}
|
||||
@@ -62,6 +61,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
var keyType = _editor.Values.Type.GetGenericArguments()[0];
|
||||
if (keyType == typeof(string) || keyType.IsPrimitive)
|
||||
{
|
||||
// Edit as text
|
||||
var popup = RenamePopup.Show(Parent, Rectangle.Margin(Bounds, Margin), Text, false);
|
||||
popup.Validate += (renamePopup, value) =>
|
||||
{
|
||||
@@ -79,7 +79,6 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
newKey = JsonSerializer.Deserialize(renamePopup.Text, keyType);
|
||||
else
|
||||
newKey = renamePopup.Text;
|
||||
|
||||
_editor.ChangeKey(_key, newKey);
|
||||
_key = newKey;
|
||||
Text = _key.ToString();
|
||||
@@ -87,6 +86,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
else if (keyType.IsEnum)
|
||||
{
|
||||
// Edit via enum picker
|
||||
var popup = RenamePopup.Show(Parent, Rectangle.Margin(Bounds, Margin), Text, false);
|
||||
var picker = new EnumComboBox(keyType)
|
||||
{
|
||||
@@ -109,7 +109,21 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("Missing editing for dictionary key type " + keyType);
|
||||
// Generic editor
|
||||
var popup = ContextMenuBase.ShowEmptyMenu(Parent, Rectangle.Margin(Bounds, Margin));
|
||||
var presenter = new CustomEditorPresenter(null);
|
||||
presenter.Panel.AnchorPreset = AnchorPresets.StretchAll;
|
||||
presenter.Panel.IsScrollable = false;
|
||||
presenter.Panel.Parent = popup;
|
||||
presenter.Select(_key);
|
||||
presenter.Modified += () =>
|
||||
{
|
||||
popup.Hide();
|
||||
object newKey = presenter.Selection[0];
|
||||
_editor.ChangeKey(_key, newKey);
|
||||
_key = newKey;
|
||||
Text = _key?.ToString();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +174,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
var argTypes = type.GetGenericArguments();
|
||||
var keyType = argTypes[0];
|
||||
var valueType = argTypes[1];
|
||||
_canEditKeys = keyType == typeof(string) || keyType.IsPrimitive || keyType.IsEnum;
|
||||
_canEditKeys = keyType == typeof(string) || keyType.IsPrimitive || keyType.IsEnum || keyType.IsValueType;
|
||||
_background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor;
|
||||
_readOnly = false;
|
||||
_notNullItems = false;
|
||||
@@ -383,6 +397,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
int newItemsLeft = newSize - oldSize;
|
||||
while (newItemsLeft-- > 0)
|
||||
{
|
||||
object newKey = null;
|
||||
if (keyType.IsPrimitive)
|
||||
{
|
||||
long uniqueKey = 0;
|
||||
@@ -401,8 +416,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
}
|
||||
} while (!isUnique);
|
||||
|
||||
newValues[Convert.ChangeType(uniqueKey, keyType)] = TypeUtils.GetDefaultValue(new ScriptType(valueType));
|
||||
newKey = Convert.ChangeType(uniqueKey, keyType);
|
||||
}
|
||||
else if (keyType.IsEnum)
|
||||
{
|
||||
@@ -422,8 +436,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
}
|
||||
} while (!isUnique && uniqueKeyIndex < enumValues.Length);
|
||||
|
||||
newValues[enumValues.GetValue(uniqueKeyIndex)] = TypeUtils.GetDefaultValue(new ScriptType(valueType));
|
||||
newKey = enumValues.GetValue(uniqueKeyIndex);
|
||||
}
|
||||
else if (keyType == typeof(string))
|
||||
{
|
||||
@@ -442,13 +455,13 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
}
|
||||
} while (!isUnique);
|
||||
|
||||
newValues[uniqueKey] = TypeUtils.GetDefaultValue(new ScriptType(valueType));
|
||||
newKey = uniqueKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
newKey = TypeUtils.GetDefaultValue(new ScriptType(keyType));
|
||||
}
|
||||
newValues[newKey] = TypeUtils.GetDefaultValue(new ScriptType(valueType));
|
||||
}
|
||||
|
||||
SetValue(newValues);
|
||||
|
||||
@@ -394,11 +394,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
if (_element != null)
|
||||
{
|
||||
_element.CustomControl.ValueChanged += () => SetValue(_element.CustomControl.Value.Type);
|
||||
|
||||
if (_element.CustomControl.Type == ScriptType.Object)
|
||||
{
|
||||
_element.CustomControl.Type = Values.Type.Type != typeof(object) || Values[0] == null ? ScriptType.Object : TypeUtils.GetObjectType(Values[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,9 +405,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
base.Refresh();
|
||||
|
||||
if (!HasDifferentValues)
|
||||
{
|
||||
_element.CustomControl.Value = new ScriptType(Values[0] as Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,9 +421,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
base.Initialize(layout);
|
||||
|
||||
if (_element != null)
|
||||
{
|
||||
_element.CustomControl.ValueChanged += () => SetValue(_element.CustomControl.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -437,9 +430,32 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
base.Refresh();
|
||||
|
||||
if (!HasDifferentValues)
|
||||
{
|
||||
_element.CustomControl.Value = (ScriptType)Values[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit reference to the <see cref="FlaxEngine.SoftTypeReference"/>. Used to pick classes.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(SoftTypeReference)), DefaultEditor]
|
||||
public class SoftTypeReferenceEditor : TypeEditorBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
if (_element != null)
|
||||
_element.CustomControl.ValueChanged += () => SetValue(new SoftTypeReference(_element.CustomControl.ValueTypeName));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (!HasDifferentValues)
|
||||
_element.CustomControl.ValueTypeName = ((SoftTypeReference)Values[0]).TypeName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,6 +110,25 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
_isSubMenu = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the empty menu popup o na screen.
|
||||
/// </summary>
|
||||
/// <param name="control">The target control.</param>
|
||||
/// <param name="area">The target control area to cover.</param>
|
||||
/// <returns>Created popup.</returns>
|
||||
public static ContextMenuBase ShowEmptyMenu(Control control, Rectangle area)
|
||||
{
|
||||
// Calculate the control size in the window space to handle scaled controls
|
||||
var upperLeft = control.PointToWindow(area.UpperLeft);
|
||||
var bottomRight = control.PointToWindow(area.BottomRight);
|
||||
var size = bottomRight - upperLeft;
|
||||
|
||||
var popup = new ContextMenuBase();
|
||||
popup.Size = size;
|
||||
popup.Show(control, area.Location + new Float2(0, (size.Y - popup.Height) * 0.5f));
|
||||
return popup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show context menu over given control.
|
||||
/// </summary>
|
||||
|
||||
@@ -542,7 +542,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)
|
||||
@@ -551,7 +551,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);
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace FlaxEditor.Windows.Profiler
|
||||
|
||||
private static string FormatSampleBytes(float v)
|
||||
{
|
||||
return (uint)v + " bytes";
|
||||
return Utilities.Utils.FormatBytesCount((ulong)v);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -214,7 +214,7 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the item.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
void Set(int32 index, bool value) const
|
||||
void Set(int32 index, bool value)
|
||||
{
|
||||
ASSERT(index >= 0 && index < _count);
|
||||
const ItemType offset = index / sizeof(ItemType);
|
||||
|
||||
@@ -203,23 +203,12 @@ public:
|
||||
return _function != nullptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the binded function if any has been assigned.
|
||||
/// </summary>
|
||||
/// <param name="params">A list of parameters for the function invocation.</param>
|
||||
/// <returns>Function result</returns>
|
||||
void TryCall(Params ... params) const
|
||||
{
|
||||
if (_function)
|
||||
_function(_callee, Forward<Params>(params)...);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the binded function (it must be assigned).
|
||||
/// </summary>
|
||||
/// <param name="params">A list of parameters for the function invocation.</param>
|
||||
/// <returns>Function result</returns>
|
||||
ReturnType operator()(Params ... params) const
|
||||
FORCE_INLINE ReturnType operator()(Params ... params) const
|
||||
{
|
||||
ASSERT(_function);
|
||||
return _function(_callee, Forward<Params>(params)...);
|
||||
|
||||
@@ -317,11 +317,12 @@ bool NetworkManager::StartHost()
|
||||
LocalClient = New<NetworkClient>(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Platform/CPUInfo.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -131,6 +132,7 @@ void NetworkPeer::Disconnect(const NetworkConnection& connection)
|
||||
|
||||
bool NetworkPeer::PopEvent(NetworkEvent& eventRef)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
return NetworkDriver->PopEvent(&eventRef);
|
||||
}
|
||||
|
||||
|
||||
205
Source/Engine/Networking/NetworkReplicationHierarchy.cpp
Normal file
205
Source/Engine/Networking/NetworkReplicationHierarchy.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "NetworkReplicationHierarchy.h"
|
||||
#include "NetworkManager.h"
|
||||
#include "Engine/Level/Actor.h"
|
||||
#include "Engine/Level/SceneObject.h"
|
||||
|
||||
uint16 NetworkReplicationNodeObjectCounter = 0;
|
||||
NetworkClientsMask NetworkClientsMask::All = { MAX_uint64, MAX_uint64 };
|
||||
|
||||
Actor* NetworkReplicationHierarchyObject::GetActor() const
|
||||
{
|
||||
auto* actor = ScriptingObject::Cast<Actor>(Object);
|
||||
if (!actor)
|
||||
{
|
||||
if (const auto* sceneObject = ScriptingObject::Cast<SceneObject>(Object))
|
||||
actor = sceneObject->GetParent();
|
||||
}
|
||||
return actor;
|
||||
}
|
||||
|
||||
void NetworkReplicationHierarchyUpdateResult::Init()
|
||||
{
|
||||
_clientsHaveLocation = false;
|
||||
_clients.Resize(NetworkManager::Clients.Count());
|
||||
_clientsMask = NetworkClientsMask();
|
||||
for (int32 i = 0; i < _clients.Count(); i++)
|
||||
_clientsMask.SetBit(i);
|
||||
_entries.Clear();
|
||||
ReplicationScale = 1.0f;
|
||||
}
|
||||
|
||||
void NetworkReplicationHierarchyUpdateResult::SetClientLocation(int32 clientIndex, const Vector3& location)
|
||||
{
|
||||
CHECK(clientIndex >= 0 && clientIndex < _clients.Count());
|
||||
_clientsHaveLocation = true;
|
||||
Client& client = _clients[clientIndex];
|
||||
client.HasLocation = true;
|
||||
client.Location = location;
|
||||
}
|
||||
|
||||
bool NetworkReplicationHierarchyUpdateResult::GetClientLocation(int32 clientIndex, Vector3& location) const
|
||||
{
|
||||
CHECK_RETURN(clientIndex >= 0 && clientIndex < _clients.Count(), false);
|
||||
const Client& client = _clients[clientIndex];
|
||||
location = client.Location;
|
||||
return client.HasLocation;
|
||||
}
|
||||
|
||||
void NetworkReplicationNode::AddObject(NetworkReplicationHierarchyObject obj)
|
||||
{
|
||||
if (obj.ReplicationFPS > 0.0f)
|
||||
{
|
||||
// Randomize initial replication update to spread rep rates more evenly for large scenes that register all objects within the same frame
|
||||
obj.ReplicationUpdatesLeft = NetworkReplicationNodeObjectCounter++ % Math::Clamp(Math::RoundToInt(NetworkManager::NetworkFPS / obj.ReplicationFPS), 1, 60);
|
||||
}
|
||||
|
||||
Objects.Add(obj);
|
||||
}
|
||||
|
||||
bool NetworkReplicationNode::RemoveObject(ScriptingObject* obj)
|
||||
{
|
||||
return !Objects.Remove(obj);
|
||||
}
|
||||
|
||||
bool NetworkReplicationNode::DirtyObject(ScriptingObject* obj)
|
||||
{
|
||||
const int32 index = Objects.Find(obj);
|
||||
if (index != -1)
|
||||
{
|
||||
NetworkReplicationHierarchyObject& e = Objects[index];
|
||||
e.ReplicationUpdatesLeft = 0;
|
||||
}
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
void NetworkReplicationNode::Update(NetworkReplicationHierarchyUpdateResult* result)
|
||||
{
|
||||
CHECK(result);
|
||||
const float networkFPS = NetworkManager::NetworkFPS / result->ReplicationScale;
|
||||
for (NetworkReplicationHierarchyObject& obj : Objects)
|
||||
{
|
||||
if (obj.ReplicationFPS <= 0.0f)
|
||||
{
|
||||
// Always relevant
|
||||
result->AddObject(obj.Object);
|
||||
}
|
||||
else if (obj.ReplicationUpdatesLeft > 0)
|
||||
{
|
||||
// Move to the next frame
|
||||
obj.ReplicationUpdatesLeft--;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkClientsMask targetClients = result->GetClientsMask();
|
||||
if (result->_clientsHaveLocation && obj.CullDistance > 0.0f)
|
||||
{
|
||||
// Cull object against viewers locations
|
||||
if (const Actor* actor = obj.GetActor())
|
||||
{
|
||||
const Vector3 objPosition = actor->GetPosition();
|
||||
const Real cullDistanceSq = Math::Square(obj.CullDistance);
|
||||
for (int32 clientIndex = 0; clientIndex < result->_clients.Count(); clientIndex++)
|
||||
{
|
||||
const auto& client = result->_clients[clientIndex];
|
||||
if (client.HasLocation)
|
||||
{
|
||||
const Real distanceSq = Vector3::DistanceSquared(objPosition, client.Location);
|
||||
// TODO: scale down replication FPS when object is far away from all clients (eg. by 10-50%)
|
||||
if (distanceSq >= cullDistanceSq)
|
||||
{
|
||||
// Object is too far from this viewer so don't send data to him
|
||||
targetClients.UnsetBit(clientIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetClients && obj.Object)
|
||||
{
|
||||
// Replicate this frame
|
||||
result->AddObject(obj.Object, targetClients);
|
||||
}
|
||||
|
||||
// Calculate frames until next replication
|
||||
obj.ReplicationUpdatesLeft = (uint16)Math::Clamp<int32>(Math::RoundToInt(networkFPS / obj.ReplicationFPS) - 1, 0, MAX_uint16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetworkReplicationGridNode::~NetworkReplicationGridNode()
|
||||
{
|
||||
for (const auto& e : _children)
|
||||
Delete(e.Value.Node);
|
||||
}
|
||||
|
||||
void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj)
|
||||
{
|
||||
// Chunk actors locations into a grid coordinates
|
||||
Int3 coord = Int3::Zero;
|
||||
if (const Actor* actor = obj.GetActor())
|
||||
{
|
||||
coord = actor->GetPosition() / CellSize;
|
||||
}
|
||||
|
||||
Cell* cell = _children.TryGet(coord);
|
||||
if (!cell)
|
||||
{
|
||||
// Allocate new cell
|
||||
cell = &_children[coord];
|
||||
cell->Node = New<NetworkReplicationNode>();
|
||||
cell->MinCullDistance = obj.CullDistance;
|
||||
}
|
||||
cell->Node->AddObject(obj);
|
||||
|
||||
// Cache minimum culling distance for a whole cell to skip it at once
|
||||
cell->MinCullDistance = Math::Min(cell->MinCullDistance, obj.CullDistance);
|
||||
}
|
||||
|
||||
bool NetworkReplicationGridNode::RemoveObject(ScriptingObject* obj)
|
||||
{
|
||||
for (const auto& e : _children)
|
||||
{
|
||||
if (e.Value.Node->RemoveObject(obj))
|
||||
{
|
||||
// TODO: remove empty cells?
|
||||
// TODO: update MinCullDistance for cell?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NetworkReplicationGridNode::Update(NetworkReplicationHierarchyUpdateResult* result)
|
||||
{
|
||||
CHECK(result);
|
||||
if (result->_clientsHaveLocation)
|
||||
{
|
||||
// Update only cells within a range
|
||||
const Real cellRadiusSq = Math::Square(CellSize * 1.414f);
|
||||
for (const auto& e : _children)
|
||||
{
|
||||
const Vector3 cellPosition = (e.Key * CellSize) + (CellSize * 0.5f);
|
||||
Real distanceSq = MAX_Real;
|
||||
for (auto& client : result->_clients)
|
||||
{
|
||||
if (client.HasLocation)
|
||||
distanceSq = Math::Min(distanceSq, Vector3::DistanceSquared(cellPosition, client.Location));
|
||||
}
|
||||
const Real minCullDistanceSq = Math::Square(e.Value.MinCullDistance);
|
||||
if (distanceSq < minCullDistanceSq + cellRadiusSq)
|
||||
{
|
||||
e.Value.Node->Update(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Brute-force over all cells
|
||||
for (const auto& e : _children)
|
||||
{
|
||||
e.Value.Node->Update(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Source/Engine/Networking/NetworkReplicationHierarchy.cs
Normal file
25
Source/Engine/Networking/NetworkReplicationHierarchy.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace FlaxEngine.Networking
|
||||
{
|
||||
partial struct NetworkReplicationHierarchyObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the actors context (object itself or parent actor).
|
||||
/// </summary>
|
||||
public Actor Actor
|
||||
{
|
||||
get
|
||||
{
|
||||
var actor = Object as Actor;
|
||||
if (actor == null)
|
||||
{
|
||||
var sceneObject = Object as SceneObject;
|
||||
if (sceneObject != null)
|
||||
actor = sceneObject.Parent;
|
||||
}
|
||||
return actor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
260
Source/Engine/Networking/NetworkReplicationHierarchy.h
Normal file
260
Source/Engine/Networking/NetworkReplicationHierarchy.h
Normal file
@@ -0,0 +1,260 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include "Engine/Core/Math/Vector3.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "Engine/Scripting/ScriptingObjectReference.h"
|
||||
|
||||
class Actor;
|
||||
|
||||
/// <summary>
|
||||
/// Network replication hierarchy object data.
|
||||
/// </summary>
|
||||
API_STRUCT(NoDefault, Namespace = "FlaxEngine.Networking") struct FLAXENGINE_API NetworkReplicationHierarchyObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkReplicationObjectInfo);
|
||||
|
||||
// The object to replicate.
|
||||
API_FIELD() ScriptingObjectReference<ScriptingObject> Object;
|
||||
// The target amount of the replication updates per second (frequency of the replication). Constrained by NetworkManager::NetworkFPS. Use 0 for 'always relevant' object.
|
||||
API_FIELD() float ReplicationFPS = 60;
|
||||
// The minimum distance from the player to the object at which it can process replication. For example, players further away won't receive object data. Use 0 if unused.
|
||||
API_FIELD() float CullDistance = 15000;
|
||||
// Runtime value for update frames left for the next replication of this object. Matches NetworkManager::NetworkFPS calculated from ReplicationFPS.
|
||||
API_FIELD(Attributes="HideInEditor") uint16 ReplicationUpdatesLeft = 0;
|
||||
|
||||
FORCE_INLINE NetworkReplicationHierarchyObject(const ScriptingObjectReference<ScriptingObject>& obj)
|
||||
: Object(obj.Get())
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE NetworkReplicationHierarchyObject(ScriptingObject* obj = nullptr)
|
||||
: Object(obj)
|
||||
{
|
||||
}
|
||||
|
||||
// Gets the actors context (object itself or parent actor).
|
||||
Actor* GetActor() const;
|
||||
|
||||
bool operator==(const NetworkReplicationHierarchyObject& other) const
|
||||
{
|
||||
return Object == other.Object;
|
||||
}
|
||||
|
||||
bool operator==(const ScriptingObject* other) const
|
||||
{
|
||||
return Object == other;
|
||||
}
|
||||
};
|
||||
|
||||
inline uint32 GetHash(const NetworkReplicationHierarchyObject& key)
|
||||
{
|
||||
return GetHash(key.Object);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bit mask for NetworkClient list (eg. to selectively send object replication).
|
||||
/// </summary>
|
||||
API_STRUCT(NoDefault, Namespace = "FlaxEngine.Networking") struct FLAXENGINE_API NetworkClientsMask
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkClientsMask);
|
||||
|
||||
// The first 64 bits (each for one client).
|
||||
API_FIELD() uint64 Word0 = 0;
|
||||
// The second 64 bits (each for one client).
|
||||
API_FIELD() uint64 Word1 = 0;
|
||||
|
||||
// All bits set for all clients.
|
||||
API_FIELD() static NetworkClientsMask All;
|
||||
|
||||
FORCE_INLINE bool HasBit(int32 bitIndex) const
|
||||
{
|
||||
const int32 wordIndex = bitIndex / 64;
|
||||
const uint64 wordMask = 1ull << (uint64)(bitIndex - wordIndex * 64);
|
||||
const uint64 word = *(&Word0 + wordIndex);
|
||||
return (word & wordMask) == wordMask;
|
||||
}
|
||||
|
||||
FORCE_INLINE void SetBit(int32 bitIndex)
|
||||
{
|
||||
const int32 wordIndex = bitIndex / 64;
|
||||
const uint64 wordMask = 1ull << (uint64)(bitIndex - wordIndex * 64);
|
||||
uint64& word = *(&Word0 + wordIndex);
|
||||
word |= wordMask;
|
||||
}
|
||||
|
||||
FORCE_INLINE void UnsetBit(int32 bitIndex)
|
||||
{
|
||||
const int32 wordIndex = bitIndex / 64;
|
||||
const uint64 wordMask = 1ull << (uint64)(bitIndex - wordIndex * 64);
|
||||
uint64& word = *(&Word0 + wordIndex);
|
||||
word &= ~wordMask;
|
||||
}
|
||||
|
||||
FORCE_INLINE operator bool() const
|
||||
{
|
||||
return Word0 + Word1 != 0;
|
||||
}
|
||||
|
||||
bool operator==(const NetworkClientsMask& other) const
|
||||
{
|
||||
return Word0 == other.Word0 && Word1 == other.Word1;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Network replication hierarchy output data to send.
|
||||
/// </summary>
|
||||
API_CLASS(Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationHierarchyUpdateResult : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(NetworkReplicationHierarchyUpdateResult, ScriptingObject);
|
||||
friend class NetworkInternal;
|
||||
friend class NetworkReplicationNode;
|
||||
friend class NetworkReplicationGridNode;
|
||||
|
||||
private:
|
||||
struct Client
|
||||
{
|
||||
bool HasLocation;
|
||||
Vector3 Location;
|
||||
};
|
||||
|
||||
struct Entry
|
||||
{
|
||||
ScriptingObject* Object;
|
||||
NetworkClientsMask TargetClients;
|
||||
};
|
||||
|
||||
bool _clientsHaveLocation;
|
||||
NetworkClientsMask _clientsMask;
|
||||
Array<Client> _clients;
|
||||
Array<Entry> _entries;
|
||||
|
||||
void Init();
|
||||
|
||||
public:
|
||||
// Scales the ReplicationFPS property of objects in hierarchy. Can be used to slow down or speed up replication rate.
|
||||
API_FIELD() float ReplicationScale = 1.0f;
|
||||
|
||||
// Adds object to the update results.
|
||||
API_FUNCTION() void AddObject(ScriptingObject* obj)
|
||||
{
|
||||
Entry& e = _entries.AddOne();
|
||||
e.Object = obj;
|
||||
e.TargetClients = NetworkClientsMask::All;
|
||||
}
|
||||
|
||||
// Adds object to the update results. Defines specific clients to receive the update (server-only, unused on client). Mask matches NetworkManager::Clients.
|
||||
API_FUNCTION() void AddObject(ScriptingObject* obj, NetworkClientsMask targetClients)
|
||||
{
|
||||
Entry& e = _entries.AddOne();
|
||||
e.Object = obj;
|
||||
e.TargetClients = targetClients;
|
||||
}
|
||||
|
||||
// Gets amount of the clients to use. Matches NetworkManager::Clients.
|
||||
API_PROPERTY() int32 GetClientsCount() const
|
||||
{
|
||||
return _clients.Count();
|
||||
}
|
||||
|
||||
// Gets mask with all client bits set. Matches NetworkManager::Clients.
|
||||
API_PROPERTY() NetworkClientsMask GetClientsMask() const
|
||||
{
|
||||
return _clientsMask;
|
||||
}
|
||||
|
||||
// Sets the viewer location for a certain client. Client index must match NetworkManager::Clients.
|
||||
API_FUNCTION() void SetClientLocation(int32 clientIndex, const Vector3& location);
|
||||
|
||||
// Gets the viewer location for a certain client. Client index must match NetworkManager::Clients. Returns true if got a location set, otherwise false.
|
||||
API_FUNCTION() bool GetClientLocation(int32 clientIndex, API_PARAM(out) Vector3& location) const;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Base class for the network objects replication hierarchy nodes. Contains a list of objects.
|
||||
/// </summary>
|
||||
API_CLASS(Abstract, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationNode : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(NetworkReplicationNode, ScriptingObject);
|
||||
|
||||
/// <summary>
|
||||
/// List with objects stored in this node.
|
||||
/// </summary>
|
||||
API_FIELD() Array<NetworkReplicationHierarchyObject> Objects;
|
||||
|
||||
/// <summary>
|
||||
/// Adds an object into the hierarchy.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to add.</param>
|
||||
API_FUNCTION() virtual void AddObject(NetworkReplicationHierarchyObject obj);
|
||||
|
||||
/// <summary>
|
||||
/// Removes object from the hierarchy.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to remove.</param>
|
||||
/// <returns>True on successful removal, otherwise false.</returns>
|
||||
API_FUNCTION() virtual bool RemoveObject(ScriptingObject* obj);
|
||||
|
||||
/// <summary>
|
||||
/// Force replicates the object during the next update. Resets any internal tracking state to force the synchronization.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to update.</param>
|
||||
/// <returns>True on successful update, otherwise false.</returns>
|
||||
API_FUNCTION() virtual bool DirtyObject(ScriptingObject* obj);
|
||||
|
||||
/// <summary>
|
||||
/// Iterates over all objects and adds them to the replication work.
|
||||
/// </summary>
|
||||
/// <param name="result">The update results container.</param>
|
||||
API_FUNCTION() virtual void Update(NetworkReplicationHierarchyUpdateResult* result);
|
||||
};
|
||||
|
||||
inline uint32 GetHash(const Int3& key)
|
||||
{
|
||||
uint32 hash = GetHash(key.X);
|
||||
CombineHash(hash, GetHash(key.Y));
|
||||
CombineHash(hash, GetHash(key.Z));
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Network replication hierarchy node with 3D grid spatialization. Organizes static objects into chunks to improve performance in large worlds.
|
||||
/// </summary>
|
||||
API_CLASS(Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationGridNode : public NetworkReplicationNode
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(NetworkReplicationGridNode, NetworkReplicationNode);
|
||||
~NetworkReplicationGridNode();
|
||||
|
||||
private:
|
||||
struct Cell
|
||||
{
|
||||
NetworkReplicationNode* Node;
|
||||
float MinCullDistance;
|
||||
};
|
||||
|
||||
Dictionary<Int3, Cell> _children;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Size of the grid cell (in world units). Used to chunk the space for separate nodes.
|
||||
/// </summary>
|
||||
API_FIELD() float CellSize = 10000.0f;
|
||||
|
||||
void AddObject(NetworkReplicationHierarchyObject obj) override;
|
||||
bool RemoveObject(ScriptingObject* obj) override;
|
||||
void Update(NetworkReplicationHierarchyUpdateResult* result) override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Defines the network objects replication hierarchy (tree structure) that controls chunking and configuration of the game objects replication.
|
||||
/// Contains only 'owned' objects. It's used by the networking system only on a main thread.
|
||||
/// </summary>
|
||||
API_CLASS(Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationHierarchy : public NetworkReplicationNode
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(NetworkReplicationHierarchy, NetworkReplicationNode);
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "NetworkRpc.h"
|
||||
#include "INetworkSerializable.h"
|
||||
#include "INetworkObject.h"
|
||||
#include "NetworkReplicationHierarchy.h"
|
||||
#include "Engine/Core/Collections/HashSet.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Core/Collections/ChunkedArray.h"
|
||||
@@ -199,6 +200,8 @@ namespace
|
||||
Dictionary<Guid, Guid> IdsRemappingTable;
|
||||
NetworkStream* CachedWriteStream = nullptr;
|
||||
NetworkStream* CachedReadStream = nullptr;
|
||||
NetworkReplicationHierarchyUpdateResult* CachedReplicationResult = nullptr;
|
||||
NetworkReplicationHierarchy* Hierarchy = nullptr;
|
||||
Array<NetworkClient*> NewClients;
|
||||
Array<NetworkConnection> CachedTargets;
|
||||
Dictionary<ScriptingTypeHandle, Serializer> SerializersTable;
|
||||
@@ -307,14 +310,15 @@ void BuildCachedTargets(const Array<NetworkClient*>& clients, const NetworkClien
|
||||
}
|
||||
}
|
||||
|
||||
void BuildCachedTargets(const Array<NetworkClient*>& clients, const DataContainer<uint32>& clientIds, const uint32 excludedClientId = NetworkManager::ServerClientId)
|
||||
void BuildCachedTargets(const Array<NetworkClient*>& clients, const DataContainer<uint32>& clientIds, const uint32 excludedClientId = NetworkManager::ServerClientId, const NetworkClientsMask clientsMask = NetworkClientsMask::All)
|
||||
{
|
||||
CachedTargets.Clear();
|
||||
if (clientIds.IsValid())
|
||||
{
|
||||
for (const NetworkClient* client : clients)
|
||||
for (int32 clientIndex = 0; clientIndex < clients.Count(); clientIndex++)
|
||||
{
|
||||
if (client->State == NetworkConnectionState::Connected && client->ClientId != excludedClientId)
|
||||
const NetworkClient* client = clients.Get()[clientIndex];
|
||||
if (client->State == NetworkConnectionState::Connected && client->ClientId != excludedClientId && clientsMask.HasBit(clientIndex))
|
||||
{
|
||||
for (int32 i = 0; i < clientIds.Length(); i++)
|
||||
{
|
||||
@@ -329,9 +333,10 @@ void BuildCachedTargets(const Array<NetworkClient*>& clients, const DataContaine
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const NetworkClient* client : clients)
|
||||
for (int32 clientIndex = 0; clientIndex < clients.Count(); clientIndex++)
|
||||
{
|
||||
if (client->State == NetworkConnectionState::Connected && client->ClientId != excludedClientId)
|
||||
const NetworkClient* client = clients.Get()[clientIndex];
|
||||
if (client->State == NetworkConnectionState::Connected && client->ClientId != excludedClientId && clientsMask.HasBit(clientIndex))
|
||||
CachedTargets.Add(client->Connection);
|
||||
}
|
||||
}
|
||||
@@ -377,10 +382,10 @@ void BuildCachedTargets(const Array<NetworkClient*>& clients, const DataContaine
|
||||
}
|
||||
}
|
||||
|
||||
FORCE_INLINE void BuildCachedTargets(const NetworkReplicatedObject& item)
|
||||
FORCE_INLINE void BuildCachedTargets(const NetworkReplicatedObject& item, const NetworkClientsMask clientsMask = NetworkClientsMask::All)
|
||||
{
|
||||
// By default send object to all connected clients excluding the owner but with optional TargetClientIds list
|
||||
BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, item.OwnerClientId);
|
||||
BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, item.OwnerClientId, clientsMask);
|
||||
}
|
||||
|
||||
FORCE_INLINE void GetNetworkName(char buffer[128], const StringAnsiView& name)
|
||||
@@ -561,9 +566,10 @@ void FindObjectsForSpawn(SpawnGroup& group, ChunkedArray<SpawnItem, 256>& spawnI
|
||||
}
|
||||
}
|
||||
|
||||
void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject* obj)
|
||||
FORCE_INLINE void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject* obj)
|
||||
{
|
||||
// TODO: implement objects state replication frequency and dirtying
|
||||
if (Hierarchy)
|
||||
Hierarchy->DirtyObject(obj);
|
||||
}
|
||||
|
||||
template<typename MessageType>
|
||||
@@ -703,6 +709,34 @@ StringAnsiView NetworkReplicator::GetCSharpCachedName(const StringAnsiView& name
|
||||
|
||||
#endif
|
||||
|
||||
NetworkReplicationHierarchy* NetworkReplicator::GetHierarchy()
|
||||
{
|
||||
return Hierarchy;
|
||||
}
|
||||
|
||||
void NetworkReplicator::SetHierarchy(NetworkReplicationHierarchy* value)
|
||||
{
|
||||
ScopeLock lock(ObjectsLock);
|
||||
if (Hierarchy == value)
|
||||
return;
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Set hierarchy to '{}'", value ? value->ToString() : String::Empty);
|
||||
if (Hierarchy)
|
||||
{
|
||||
// Clear old hierarchy
|
||||
Delete(Hierarchy);
|
||||
}
|
||||
Hierarchy = value;
|
||||
if (value)
|
||||
{
|
||||
// Add all owned objects to the hierarchy
|
||||
for (auto& e : Objects)
|
||||
{
|
||||
if (e.Item.Object && e.Item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
value->AddObject(e.Item.Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkReplicator::AddSerializer(const ScriptingTypeHandle& typeHandle, SerializeFunc serialize, SerializeFunc deserialize, void* serializeTag, void* deserializeTag)
|
||||
{
|
||||
if (!typeHandle)
|
||||
@@ -745,7 +779,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,7 +808,22 @@ 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));
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->AddObject(obj);
|
||||
}
|
||||
|
||||
void NetworkReplicator::RemoveObject(ScriptingObject* obj)
|
||||
@@ -788,6 +837,8 @@ void NetworkReplicator::RemoveObject(ScriptingObject* obj)
|
||||
|
||||
// Remove object from the list
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remove object {}, owned by {}", obj->GetID().ToString(), it->Item.ParentId.ToString());
|
||||
if (Hierarchy && it->Item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->RemoveObject(obj);
|
||||
Objects.Remove(it);
|
||||
}
|
||||
|
||||
@@ -857,14 +908,33 @@ void NetworkReplicator::DespawnObject(ScriptingObject* obj)
|
||||
DespawnedObjects.Add(item.ObjectId);
|
||||
if (item.AsNetworkObject)
|
||||
item.AsNetworkObject->OnNetworkDespawn();
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->RemoveObject(obj);
|
||||
Objects.Remove(it);
|
||||
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 +948,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 +966,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 +980,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 +997,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 +1029,37 @@ 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);
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->RemoveObject(obj);
|
||||
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);
|
||||
if (Hierarchy && it->Item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->RemoveObject(obj);
|
||||
item.Role = localRole;
|
||||
}
|
||||
}
|
||||
|
||||
// Go down hierarchy
|
||||
@@ -976,9 +1067,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1054,6 +1151,8 @@ void NetworkInternal::NetworkReplicatorClientDisconnected(NetworkClient* client)
|
||||
|
||||
// Delete object locally
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Despawn object {}", item.ObjectId);
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->RemoveObject(obj);
|
||||
if (item.AsNetworkObject)
|
||||
item.AsNetworkObject->OnNetworkDespawn();
|
||||
DeleteNetworkObject(obj);
|
||||
@@ -1068,6 +1167,7 @@ void NetworkInternal::NetworkReplicatorClear()
|
||||
|
||||
// Cleanup
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Shutdown");
|
||||
NetworkReplicator::SetHierarchy(nullptr);
|
||||
for (auto it = Objects.Begin(); it.IsNotEnd(); ++it)
|
||||
{
|
||||
auto& item = it->Item;
|
||||
@@ -1087,6 +1187,7 @@ void NetworkInternal::NetworkReplicatorClear()
|
||||
IdsRemappingTable.Clear();
|
||||
SAFE_DELETE(CachedWriteStream);
|
||||
SAFE_DELETE(CachedReadStream);
|
||||
SAFE_DELETE(CachedReplicationResult);
|
||||
NewClients.Clear();
|
||||
CachedTargets.Clear();
|
||||
DespawnedObjects.Clear();
|
||||
@@ -1182,9 +1283,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1213,7 +1316,14 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
|
||||
if (e.HasOwnership)
|
||||
{
|
||||
item.Role = e.Role;
|
||||
if (item.Role != e.Role)
|
||||
{
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->RemoveObject(obj);
|
||||
item.Role = e.Role;
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->AddObject(obj);
|
||||
}
|
||||
item.OwnerClientId = e.OwnerClientId;
|
||||
if (e.HierarchicalOwnership)
|
||||
NetworkReplicator::SetObjectOwnership(obj, e.OwnerClientId, e.Role, true);
|
||||
@@ -1247,65 +1357,104 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
}
|
||||
|
||||
// Apply parts replication
|
||||
for (int32 i = ReplicationParts.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
auto& e = ReplicationParts[i];
|
||||
if (e.PartsLeft > 0)
|
||||
PROFILE_CPU_NAMED("ReplicationParts");
|
||||
for (int32 i = ReplicationParts.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
// TODO: remove replication items after some TTL to prevent memory leaks
|
||||
continue;
|
||||
}
|
||||
ScriptingObject* obj = e.Object.Get();
|
||||
if (obj)
|
||||
{
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
if (it != Objects.End())
|
||||
auto& e = ReplicationParts[i];
|
||||
if (e.PartsLeft > 0)
|
||||
{
|
||||
auto& item = it->Item;
|
||||
|
||||
// Replicate from all collected parts data
|
||||
InvokeObjectReplication(item, e.OwnerFrame, e.Data.Get(), e.Data.Count(), e.OwnerClientId);
|
||||
// TODO: remove replication items after some TTL to prevent memory leaks
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ScriptingObject* obj = e.Object.Get();
|
||||
if (obj)
|
||||
{
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
if (it != Objects.End())
|
||||
{
|
||||
auto& item = it->Item;
|
||||
|
||||
ReplicationParts.RemoveAt(i);
|
||||
// Replicate from all collected parts data
|
||||
InvokeObjectReplication(item, e.OwnerFrame, e.Data.Get(), e.Data.Count(), e.OwnerClientId);
|
||||
}
|
||||
}
|
||||
|
||||
ReplicationParts.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Brute force synchronize all networked objects with clients
|
||||
if (CachedWriteStream == nullptr)
|
||||
CachedWriteStream = New<NetworkStream>();
|
||||
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)
|
||||
// Replicate all owned networked objects with other clients or server
|
||||
if (!CachedReplicationResult)
|
||||
CachedReplicationResult = New<NetworkReplicationHierarchyUpdateResult>();
|
||||
CachedReplicationResult->Init();
|
||||
if (!isClient && NetworkManager::Clients.IsEmpty())
|
||||
{
|
||||
auto& item = it->Item;
|
||||
ScriptingObject* obj = item.Object.Get();
|
||||
if (!obj)
|
||||
// No need to update replication when nobody's around
|
||||
}
|
||||
else if (Hierarchy)
|
||||
{
|
||||
// Tick using hierarchy
|
||||
PROFILE_CPU_NAMED("ReplicationHierarchyUpdate");
|
||||
Hierarchy->Update(CachedReplicationResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tick all owned objects
|
||||
PROFILE_CPU_NAMED("ReplicationUpdate");
|
||||
for (auto it = Objects.Begin(); it.IsNotEnd(); ++it)
|
||||
{
|
||||
// Object got deleted
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remove object {}, owned by {}", item.ToString(), item.ParentId.ToString());
|
||||
Objects.Remove(it);
|
||||
continue;
|
||||
auto& item = it->Item;
|
||||
ScriptingObject* obj = item.Object.Get();
|
||||
if (!obj)
|
||||
{
|
||||
// Object got deleted
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remove object {}, owned by {}", item.ToString(), item.ParentId.ToString());
|
||||
Objects.Remove(it);
|
||||
continue;
|
||||
}
|
||||
if (item.Role != NetworkObjectRole::OwnedAuthoritative)
|
||||
continue; // Send replication messages of only owned objects or from other client objects
|
||||
CachedReplicationResult->AddObject(obj);
|
||||
}
|
||||
if (item.Role != NetworkObjectRole::OwnedAuthoritative && (!isClient && item.OwnerClientId != NetworkManager::LocalClientId))
|
||||
continue; // Send replication messages of only owned objects or from other client objects
|
||||
|
||||
if (item.AsNetworkObject)
|
||||
item.AsNetworkObject->OnNetworkSerialize();
|
||||
|
||||
// Serialize object
|
||||
stream->Initialize();
|
||||
const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, true);
|
||||
if (failed)
|
||||
}
|
||||
if (CachedReplicationResult->_entries.HasItems())
|
||||
{
|
||||
PROFILE_CPU_NAMED("Replication");
|
||||
if (CachedWriteStream == nullptr)
|
||||
CachedWriteStream = New<NetworkStream>();
|
||||
NetworkStream* stream = CachedWriteStream;
|
||||
stream->SenderId = NetworkManager::LocalClientId;
|
||||
// TODO: use Job System when replicated objects count is large
|
||||
for (auto& e : CachedReplicationResult->_entries)
|
||||
{
|
||||
//NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString());
|
||||
continue;
|
||||
}
|
||||
ScriptingObject* obj = e.Object;
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
if (it.IsEnd())
|
||||
continue;
|
||||
auto& item = it->Item;
|
||||
|
||||
// Send object to clients
|
||||
{
|
||||
// Skip serialization of objects that none will receive
|
||||
if (!isClient)
|
||||
{
|
||||
BuildCachedTargets(item, e.TargetClients);
|
||||
if (CachedTargets.Count() == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.AsNetworkObject)
|
||||
item.AsNetworkObject->OnNetworkSerialize();
|
||||
|
||||
// Serialize object
|
||||
stream->Initialize();
|
||||
const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, true);
|
||||
if (failed)
|
||||
{
|
||||
//NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Send object to clients
|
||||
const uint32 size = stream->GetPosition();
|
||||
ASSERT(size <= MAX_uint16)
|
||||
NetworkMessageObjectReplicate msgData;
|
||||
@@ -1344,11 +1493,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
if (isClient)
|
||||
peer->EndSendMessage(NetworkChannelType::Unreliable, msg);
|
||||
else
|
||||
{
|
||||
// TODO: per-object relevancy for connected clients (eg. skip replicating actor to far players)
|
||||
BuildCachedTargets(item);
|
||||
peer->EndSendMessage(NetworkChannelType::Unreliable, msg, CachedTargets);
|
||||
}
|
||||
|
||||
// Send all other parts
|
||||
for (uint32 partIndex = 1; partIndex < partsCount; partIndex++)
|
||||
@@ -1376,49 +1521,52 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
}
|
||||
|
||||
// Invoke RPCs
|
||||
for (auto& e : RpcQueue)
|
||||
{
|
||||
ScriptingObject* obj = e.Object.Get();
|
||||
if (!obj)
|
||||
continue;
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
if (it == Objects.End())
|
||||
continue;
|
||||
auto& item = it->Item;
|
||||
PROFILE_CPU_NAMED("Rpc");
|
||||
for (auto& e : RpcQueue)
|
||||
{
|
||||
ScriptingObject* obj = e.Object.Get();
|
||||
if (!obj)
|
||||
continue;
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
if (it == Objects.End())
|
||||
continue;
|
||||
auto& item = it->Item;
|
||||
|
||||
// Send RPC message
|
||||
//NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Rpc {}::{} object ID={}", e.Name.First.ToString(), String(e.Name.Second), item.ToString());
|
||||
NetworkMessageObjectRpc msgData;
|
||||
msgData.ObjectId = item.ObjectId;
|
||||
if (isClient)
|
||||
{
|
||||
// Remap local client object ids into server ids
|
||||
IdsRemappingTable.KeyOf(msgData.ObjectId, &msgData.ObjectId);
|
||||
}
|
||||
GetNetworkName(msgData.RpcTypeName, e.Name.First.GetType().Fullname);
|
||||
GetNetworkName(msgData.RpcName, e.Name.Second);
|
||||
msgData.ArgsSize = (uint16)e.ArgsData.Length();
|
||||
NetworkMessage msg = peer->BeginSendMessage();
|
||||
msg.WriteStructure(msgData);
|
||||
msg.WriteBytes(e.ArgsData.Get(), e.ArgsData.Length());
|
||||
NetworkChannelType channel = (NetworkChannelType)e.Info.Channel;
|
||||
if (e.Info.Server && isClient)
|
||||
{
|
||||
// Client -> Server
|
||||
// Send RPC message
|
||||
//NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Rpc {}::{} object ID={}", e.Name.First.ToString(), String(e.Name.Second), item.ToString());
|
||||
NetworkMessageObjectRpc msgData;
|
||||
msgData.ObjectId = item.ObjectId;
|
||||
if (isClient)
|
||||
{
|
||||
// Remap local client object ids into server ids
|
||||
IdsRemappingTable.KeyOf(msgData.ObjectId, &msgData.ObjectId);
|
||||
}
|
||||
GetNetworkName(msgData.RpcTypeName, e.Name.First.GetType().Fullname);
|
||||
GetNetworkName(msgData.RpcName, e.Name.Second);
|
||||
msgData.ArgsSize = (uint16)e.ArgsData.Length();
|
||||
NetworkMessage msg = peer->BeginSendMessage();
|
||||
msg.WriteStructure(msgData);
|
||||
msg.WriteBytes(e.ArgsData.Get(), e.ArgsData.Length());
|
||||
NetworkChannelType channel = (NetworkChannelType)e.Info.Channel;
|
||||
if (e.Info.Server && isClient)
|
||||
{
|
||||
// Client -> Server
|
||||
#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());
|
||||
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, e.Targets, NetworkManager::LocalClientId);
|
||||
peer->EndSendMessage(channel, msg, CachedTargets);
|
||||
peer->EndSendMessage(channel, msg);
|
||||
}
|
||||
else if (e.Info.Client && (isServer || isHost))
|
||||
{
|
||||
// Server -> Client(s)
|
||||
BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, e.Targets, NetworkManager::LocalClientId);
|
||||
peer->EndSendMessage(channel, msg, CachedTargets);
|
||||
}
|
||||
}
|
||||
RpcQueue.Clear();
|
||||
}
|
||||
RpcQueue.Clear();
|
||||
|
||||
// Clear networked objects mapping table
|
||||
Scripting::ObjectsLookupIdMapping.Set(nullptr);
|
||||
@@ -1426,6 +1574,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
|
||||
void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
NetworkMessageObjectReplicate msgData;
|
||||
event.Message.ReadStructure(msgData);
|
||||
ScopeLock lock(ObjectsLock);
|
||||
@@ -1457,6 +1606,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo
|
||||
|
||||
void NetworkInternal::OnNetworkMessageObjectReplicatePart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
NetworkMessageObjectReplicatePart msgData;
|
||||
event.Message.ReadStructure(msgData);
|
||||
ScopeLock lock(ObjectsLock);
|
||||
@@ -1469,6 +1619,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicatePart(NetworkEvent& event, N
|
||||
|
||||
void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
NetworkMessageObjectSpawn msgData;
|
||||
event.Message.ReadStructure(msgData);
|
||||
auto* msgDataItems = (NetworkMessageObjectSpawnItem*)event.Message.SkipBytes(msgData.ItemsCount * sizeof(NetworkMessageObjectSpawnItem));
|
||||
@@ -1493,7 +1644,11 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
|
||||
// Server always knows the best so update ownership of the existing object
|
||||
item.OwnerClientId = msgData.OwnerClientId;
|
||||
if (item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
{
|
||||
if (Hierarchy)
|
||||
Hierarchy->AddObject(item.Object);
|
||||
item.Role = NetworkObjectRole::Replicated;
|
||||
}
|
||||
}
|
||||
else if (item.OwnerClientId != msgData.OwnerClientId)
|
||||
{
|
||||
@@ -1623,7 +1778,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];
|
||||
@@ -1648,10 +1803,22 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
|
||||
item.Spawned = true;
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Add new object {}:{}, parent {}:{}", item.ToString(), obj->GetType().ToString(), item.ParentId.ToString(), parent ? parent->Object->GetType().ToString() : String::Empty);
|
||||
Objects.Add(MoveTemp(item));
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->AddObject(obj);
|
||||
|
||||
// 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<SceneObject>(obj);
|
||||
@@ -1666,7 +1833,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
|
||||
#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"))
|
||||
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());
|
||||
}
|
||||
@@ -1687,6 +1854,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
|
||||
|
||||
void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
NetworkMessageObjectDespawn msgData;
|
||||
event.Message.ReadStructure(msgData);
|
||||
ScopeLock lock(ObjectsLock);
|
||||
@@ -1704,6 +1872,8 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network
|
||||
|
||||
// Remove object
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Despawn object {}", msgData.ObjectId);
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->RemoveObject(obj);
|
||||
DespawnedObjects.Add(msgData.ObjectId);
|
||||
if (item.AsNetworkObject)
|
||||
item.AsNetworkObject->OnNetworkDespawn();
|
||||
@@ -1718,6 +1888,7 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network
|
||||
|
||||
void NetworkInternal::OnNetworkMessageObjectRole(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
NetworkMessageObjectRole msgData;
|
||||
event.Message.ReadStructure(msgData);
|
||||
ScopeLock lock(ObjectsLock);
|
||||
@@ -1739,12 +1910,16 @@ void NetworkInternal::OnNetworkMessageObjectRole(NetworkEvent& event, NetworkCli
|
||||
if (item.OwnerClientId == NetworkManager::LocalClientId)
|
||||
{
|
||||
// Upgrade ownership automatically
|
||||
if (Hierarchy && item.Role != NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->AddObject(obj);
|
||||
item.Role = NetworkObjectRole::OwnedAuthoritative;
|
||||
item.LastOwnerFrame = 0;
|
||||
}
|
||||
else if (item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
{
|
||||
// Downgrade ownership automatically
|
||||
if (Hierarchy)
|
||||
Hierarchy->RemoveObject(obj);
|
||||
item.Role = NetworkObjectRole::Replicated;
|
||||
}
|
||||
if (!NetworkManager::IsClient())
|
||||
@@ -1761,6 +1936,7 @@ void NetworkInternal::OnNetworkMessageObjectRole(NetworkEvent& event, NetworkCli
|
||||
|
||||
void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
NetworkMessageObjectRpc msgData;
|
||||
event.Message.ReadStructure(msgData);
|
||||
ScopeLock lock(ObjectsLock);
|
||||
|
||||
@@ -42,6 +42,17 @@ public:
|
||||
API_FIELD() static bool EnableLog;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the network replication hierarchy.
|
||||
/// </summary>
|
||||
API_PROPERTY() static NetworkReplicationHierarchy* GetHierarchy();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the network replication hierarchy.
|
||||
/// </summary>
|
||||
API_PROPERTY() static void SetHierarchy(NetworkReplicationHierarchy* value);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Adds the network replication serializer for a given type.
|
||||
/// </summary>
|
||||
@@ -68,7 +79,7 @@ public:
|
||||
/// <remarks>Does nothing if network is offline.</remarks>
|
||||
/// <param name="obj">The object to replicate.</param>
|
||||
/// <param name="parent">The parent of the object (eg. player that spawned it).</param>
|
||||
API_FUNCTION() static void AddObject(ScriptingObject* obj, ScriptingObject* parent = nullptr);
|
||||
API_FUNCTION() static void AddObject(ScriptingObject* obj, const ScriptingObject* parent = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the object from the network replication system.
|
||||
@@ -80,14 +91,14 @@ public:
|
||||
/// <summary>
|
||||
/// Spawns the object to the other clients. Can be spawned by the owner who locally created it (eg. from prefab).
|
||||
/// </summary>
|
||||
/// <remarks>Does nothing if network is offline.</remarks>
|
||||
/// <remarks>Does nothing if network is offline. Doesn't spawn actor in a level - but in network replication system.</remarks>
|
||||
/// <param name="obj">The object to spawn on other clients.</param>
|
||||
API_FUNCTION() static void SpawnObject(ScriptingObject* obj);
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the object to the other clients. Can be spawned by the owner who locally created it (eg. from prefab).
|
||||
/// </summary>
|
||||
/// <remarks>Does nothing if network is offline.</remarks>
|
||||
/// <remarks>Does nothing if network is offline. Doesn't spawn actor in a level - but in network replication system.</remarks>
|
||||
/// <param name="obj">The object to spawn on other clients.</param>
|
||||
/// <param name="clientIds">List with network client IDs that should receive network spawn event. Empty to spawn on all clients.</param>
|
||||
API_FUNCTION() static void SpawnObject(ScriptingObject* obj, const DataContainer<uint32>& clientIds);
|
||||
@@ -99,6 +110,13 @@ public:
|
||||
/// <param name="obj">The object to despawn on other clients.</param>
|
||||
API_FUNCTION() static void DespawnObject(ScriptingObject* obj);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the network object is spawned or added to the network replication system.
|
||||
/// </summary>
|
||||
/// <param name="obj">The network object.</param>
|
||||
/// <returns>True if object exists in networking, otherwise false.</returns>
|
||||
API_FUNCTION() static bool HasObject(const ScriptingObject* obj);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Client Id of the network object owner.
|
||||
/// </summary>
|
||||
|
||||
@@ -11,6 +11,7 @@ class INetworkSerializable;
|
||||
class NetworkPeer;
|
||||
class NetworkClient;
|
||||
class NetworkStream;
|
||||
class NetworkReplicationHierarchy;
|
||||
|
||||
struct NetworkEvent;
|
||||
struct NetworkConnection;
|
||||
|
||||
@@ -17,17 +17,17 @@ class FontAsset;
|
||||
/// <summary>
|
||||
/// The text range.
|
||||
/// </summary>
|
||||
API_STRUCT() struct TextRange
|
||||
API_STRUCT(NoDefault) struct TextRange
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange);
|
||||
|
||||
/// <summary>
|
||||
/// The start index.
|
||||
/// The start index (inclusive).
|
||||
/// </summary>
|
||||
API_FIELD() int32 StartIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The end index.
|
||||
/// The end index (exclusive).
|
||||
/// </summary>
|
||||
API_FIELD() int32 EndIndex;
|
||||
|
||||
@@ -70,7 +70,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange);
|
||||
/// <summary>
|
||||
/// Gets the substring from the source text.
|
||||
/// </summary>
|
||||
/// <param name="other">The text.</param>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <returns>The substring of the original text of the defined range.</returns>
|
||||
StringView Substring(const StringView& text) const
|
||||
{
|
||||
@@ -87,7 +87,7 @@ struct TIsPODType<TextRange>
|
||||
/// <summary>
|
||||
/// The font line info generated during text processing.
|
||||
/// </summary>
|
||||
API_STRUCT() struct FontLineCache
|
||||
API_STRUCT(NoDefault) struct FontLineCache
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache);
|
||||
|
||||
@@ -151,7 +151,7 @@ struct TIsPODType<FontLineCache>
|
||||
/// <summary>
|
||||
/// The cached font character entry (read for rendering and further processing).
|
||||
/// </summary>
|
||||
API_STRUCT() struct FontCharacterEntry
|
||||
API_STRUCT(NoDefault) struct FontCharacterEntry
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry);
|
||||
|
||||
|
||||
87
Source/Engine/Scripting/SoftTypeReference.cs
Normal file
87
Source/Engine/Scripting/SoftTypeReference.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// The soft reference to the scripting type contained in the scripting assembly.
|
||||
/// </summary>
|
||||
public struct SoftTypeReference : IComparable, IComparable<SoftTypeReference>
|
||||
{
|
||||
private string _typeName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type full name (eg. FlaxEngine.Actor).
|
||||
/// </summary>
|
||||
public string TypeName
|
||||
{
|
||||
get => _typeName;
|
||||
set => _typeName = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type (resolves soft reference).
|
||||
/// </summary>
|
||||
public Type Type
|
||||
{
|
||||
get => _typeName != null ? Type.GetType(_typeName) : null;
|
||||
set => _typeName = value?.FullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SoftTypeReference"/>.
|
||||
/// </summary>
|
||||
/// <param name="typeName">The type name.</param>
|
||||
public SoftTypeReference(string typeName)
|
||||
{
|
||||
_typeName = typeName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the soft type reference from full name.
|
||||
/// </summary>
|
||||
/// <param name="s">The type name.</param>
|
||||
/// <returns>The soft type reference.</returns>
|
||||
public static implicit operator SoftTypeReference(string s)
|
||||
{
|
||||
return new SoftTypeReference { _typeName = s };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the soft type reference from runtime type.
|
||||
/// </summary>
|
||||
/// <param name="s">The type.</param>
|
||||
/// <returns>The soft type reference.</returns>
|
||||
public static implicit operator SoftTypeReference(Type s)
|
||||
{
|
||||
return new SoftTypeReference { _typeName = s?.FullName };
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return _typeName;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _typeName?.GetHashCode() ?? 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is SoftTypeReference other)
|
||||
return CompareTo(other);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CompareTo(SoftTypeReference other)
|
||||
{
|
||||
return string.Compare(_typeName, other._typeName, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
151
Source/Engine/Scripting/SoftTypeReference.h
Normal file
151
Source/Engine/Scripting/SoftTypeReference.h
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Scripting.h"
|
||||
#include "ScriptingObject.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Serialization/SerializationFwd.h"
|
||||
|
||||
/// <summary>
|
||||
/// The soft reference to the scripting type contained in the scripting assembly.
|
||||
/// </summary>
|
||||
template<typename T = ScriptingObject>
|
||||
API_STRUCT(InBuild) struct SoftTypeReference
|
||||
{
|
||||
protected:
|
||||
StringAnsi _typeName;
|
||||
|
||||
public:
|
||||
SoftTypeReference() = default;
|
||||
|
||||
SoftTypeReference(const SoftTypeReference& s)
|
||||
: _typeName(s._typeName)
|
||||
{
|
||||
}
|
||||
|
||||
SoftTypeReference(SoftTypeReference&& s) noexcept
|
||||
: _typeName(MoveTemp(s._typeName))
|
||||
{
|
||||
}
|
||||
|
||||
SoftTypeReference(const StringView& s)
|
||||
: _typeName(s)
|
||||
{
|
||||
}
|
||||
|
||||
SoftTypeReference(const StringAnsiView& s)
|
||||
: _typeName(s)
|
||||
{
|
||||
}
|
||||
|
||||
SoftTypeReference(const char* s)
|
||||
: _typeName(s)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
FORCE_INLINE SoftTypeReference& operator=(SoftTypeReference&& s) noexcept
|
||||
{
|
||||
_typeName = MoveTemp(s._typeName);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FORCE_INLINE SoftTypeReference& operator=(StringAnsi&& s) noexcept
|
||||
{
|
||||
_typeName = MoveTemp(s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FORCE_INLINE SoftTypeReference& operator=(const SoftTypeReference& s)
|
||||
{
|
||||
_typeName = s._typeName;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FORCE_INLINE SoftTypeReference& operator=(const StringAnsiView& s)
|
||||
{
|
||||
_typeName = s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator==(const SoftTypeReference& other) const
|
||||
{
|
||||
return _typeName == other._typeName;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator!=(const SoftTypeReference& other) const
|
||||
{
|
||||
return _typeName != other._typeName;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator==(const StringAnsiView& other) const
|
||||
{
|
||||
return _typeName == other;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator!=(const StringAnsiView& other) const
|
||||
{
|
||||
return _typeName != other;
|
||||
}
|
||||
|
||||
FORCE_INLINE operator bool() const
|
||||
{
|
||||
return _typeName.HasChars();
|
||||
}
|
||||
|
||||
public:
|
||||
// Gets the type full name (eg. FlaxEngine.Actor).
|
||||
StringAnsiView GetTypeName() const
|
||||
{
|
||||
return StringAnsiView(_typeName);
|
||||
}
|
||||
|
||||
// Gets the type (resolves soft reference).
|
||||
ScriptingTypeHandle GetType() const
|
||||
{
|
||||
return Scripting::FindScriptingType(_typeName);
|
||||
}
|
||||
|
||||
// Creates a new objects of that type (or of type T if failed to solve typename).
|
||||
T* NewObject() const
|
||||
{
|
||||
const ScriptingTypeHandle type = Scripting::FindScriptingType(_typeName);
|
||||
auto obj = ScriptingObject::NewObject<T>(type);
|
||||
if (!obj)
|
||||
{
|
||||
if (_typeName.HasChars())
|
||||
LOG(Error, "Unknown or invalid type {0}", String(_typeName));
|
||||
obj = ScriptingObject::NewObject<T>();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
uint32 GetHash(const SoftTypeReference<T>& key)
|
||||
{
|
||||
return GetHash(key.GetTypeName());
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
namespace Serialization
|
||||
{
|
||||
template<typename T>
|
||||
bool ShouldSerialize(const SoftTypeReference<T>& v, const void* otherObj)
|
||||
{
|
||||
return !otherObj || v != *(SoftTypeReference<T>*)otherObj;
|
||||
}
|
||||
template<typename T>
|
||||
void Serialize(ISerializable::SerializeStream& stream, const SoftTypeReference<T>& v, const void* otherObj)
|
||||
{
|
||||
stream.String(v.GetTypeName());
|
||||
}
|
||||
template<typename T>
|
||||
void Deserialize(ISerializable::DeserializeStream& stream, SoftTypeReference<T>& v, ISerializeModifier* modifier)
|
||||
{
|
||||
v = stream.GetTextAnsi();
|
||||
}
|
||||
}
|
||||
// @formatter:on
|
||||
@@ -7,7 +7,7 @@ using Newtonsoft.Json;
|
||||
namespace FlaxEngine.Json
|
||||
{
|
||||
/// <summary>
|
||||
/// Serialize references to the FlaxEngine.Object as Guid.
|
||||
/// Serialize references to the <see cref="FlaxEngine.Object"/> as Guid.
|
||||
/// </summary>
|
||||
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
|
||||
internal class FlaxObjectConverter : JsonConverter
|
||||
@@ -46,7 +46,7 @@ namespace FlaxEngine.Json
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize SceneReference as Guid in internal format.
|
||||
/// Serialize <see cref="SceneReference"/> as Guid in internal format.
|
||||
/// </summary>
|
||||
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
|
||||
internal class SceneReferenceConverter : JsonConverter
|
||||
@@ -79,7 +79,7 @@ namespace FlaxEngine.Json
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize SoftObjectReference as Guid in internal format.
|
||||
/// Serialize <see cref="SoftObjectReference"/> as Guid in internal format.
|
||||
/// </summary>
|
||||
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
|
||||
internal class SoftObjectReferenceConverter : JsonConverter
|
||||
@@ -111,7 +111,36 @@ namespace FlaxEngine.Json
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize SoftObjectReference as Guid in internal format.
|
||||
/// Serialize <see cref="SoftTypeReference"/> as typename string in internal format.
|
||||
/// </summary>
|
||||
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
|
||||
internal class SoftTypeReferenceConverter : JsonConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(((SoftTypeReference)value).TypeName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
|
||||
{
|
||||
var result = new SoftTypeReference();
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
result.TypeName = (string)reader.Value;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(SoftTypeReference);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize <see cref="Margin"/> as Guid in internal format.
|
||||
/// </summary>
|
||||
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
|
||||
internal class MarginConverter : JsonConverter
|
||||
@@ -237,7 +266,7 @@ namespace FlaxEngine.Json
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize LocalizedString as inlined text is not using localization (Id member is empty).
|
||||
/// Serialize <see cref="LocalizedString"/> as inlined text is not using localization (Id member is empty).
|
||||
/// </summary>
|
||||
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
|
||||
internal class LocalizedStringConverter : JsonConverter
|
||||
|
||||
@@ -123,6 +123,7 @@ namespace FlaxEngine.Json
|
||||
settings.Converters.Add(ObjectConverter);
|
||||
settings.Converters.Add(new SceneReferenceConverter());
|
||||
settings.Converters.Add(new SoftObjectReferenceConverter());
|
||||
settings.Converters.Add(new SoftTypeReferenceConverter());
|
||||
settings.Converters.Add(new MarginConverter());
|
||||
settings.Converters.Add(new VersionConverter());
|
||||
settings.Converters.Add(new LocalizedStringConverter());
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace FlaxEngine.GUI
|
||||
public class Button : ContainerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// The default height fro the buttons.
|
||||
/// The default height for the buttons.
|
||||
/// </summary>
|
||||
public const float DefaultHeight = 24.0f;
|
||||
|
||||
@@ -195,7 +195,7 @@ namespace FlaxEngine.GUI
|
||||
/// Sets the button colors palette based on a given main color.
|
||||
/// </summary>
|
||||
/// <param name="color">The main color.</param>
|
||||
public void SetColors(Color color)
|
||||
public virtual void SetColors(Color color)
|
||||
{
|
||||
BackgroundColor = color;
|
||||
BorderColor = color.RGBMultiplied(0.5f);
|
||||
|
||||
1
Source/ThirdParty/tracy/tracy.Build.cs
vendored
1
Source/ThirdParty/tracy/tracy.Build.cs
vendored
@@ -57,6 +57,7 @@ public class tracy : ThirdPartyModule
|
||||
|
||||
files.Add(Path.Combine(FolderPath, "tracy", "Tracy.hpp"));
|
||||
files.Add(Path.Combine(FolderPath, "common", "TracySystem.hpp"));
|
||||
files.Add(Path.Combine(FolderPath, "common", "TracyQueue.hpp"));
|
||||
files.Add(Path.Combine(FolderPath, "client", "TracyCallstack.h"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,7 +541,7 @@ namespace Flax.Build.Bindings
|
||||
var separator = false;
|
||||
if (!functionInfo.IsStatic)
|
||||
{
|
||||
contents.Append("IntPtr obj");
|
||||
contents.Append("IntPtr __obj");
|
||||
separator = true;
|
||||
}
|
||||
|
||||
@@ -1511,7 +1511,8 @@ namespace Flax.Build.Bindings
|
||||
|
||||
if (fieldInfo.Type.IsObjectRef)
|
||||
{
|
||||
toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{fieldInfo.Type.GenericArgs[0].Type}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null");
|
||||
var managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type.GenericArgs[0], structureInfo);
|
||||
toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null");
|
||||
toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero");
|
||||
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
|
||||
|
||||
|
||||
@@ -1017,7 +1017,7 @@ namespace Flax.Build.Bindings
|
||||
var signatureStart = contents.Length;
|
||||
if (!functionInfo.IsStatic)
|
||||
{
|
||||
contents.Append(caller.Name).Append("* obj");
|
||||
contents.Append(caller.Name).Append("* __obj");
|
||||
separator = true;
|
||||
}
|
||||
|
||||
@@ -1127,7 +1127,7 @@ namespace Flax.Build.Bindings
|
||||
contents.Append(indent).AppendLine($"MSVC_FUNC_EXPORT(\"{libraryEntryPoint}\")"); // Export generated function binding under the C# name
|
||||
#endif
|
||||
if (!functionInfo.IsStatic)
|
||||
contents.Append(indent).AppendLine("if (obj == nullptr) DebugLog::ThrowNullReference();");
|
||||
contents.Append(indent).AppendLine("if (__obj == nullptr) DebugLog::ThrowNullReference();");
|
||||
|
||||
string callBegin = indent;
|
||||
if (functionInfo.Glue.UseReferenceForResult)
|
||||
@@ -1165,7 +1165,7 @@ namespace Flax.Build.Bindings
|
||||
else
|
||||
{
|
||||
// Call native member method
|
||||
call = $"obj->{functionInfo.Name}";
|
||||
call = $"__obj->{functionInfo.Name}";
|
||||
}
|
||||
string callParams = string.Empty;
|
||||
separator = false;
|
||||
@@ -1925,7 +1925,7 @@ namespace Flax.Build.Bindings
|
||||
continue;
|
||||
var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0;
|
||||
CppIncludeFiles.Add("Engine/Profiler/ProfilerCPU.h");
|
||||
var bindPrefix = eventInfo.IsStatic ? classTypeNameNative + "::" : "obj->";
|
||||
var bindPrefix = eventInfo.IsStatic ? classTypeNameNative + "::" : "__obj->";
|
||||
|
||||
if (useCSharp)
|
||||
{
|
||||
@@ -2006,7 +2006,7 @@ namespace Flax.Build.Bindings
|
||||
if (buildData.Toolchain?.Compiler == TargetCompiler.Clang)
|
||||
useSeparateImpl = true; // DLLEXPORT doesn't properly export function thus separate implementation from declaration
|
||||
if (!eventInfo.IsStatic)
|
||||
contents.AppendFormat("{0}* obj, ", classTypeNameNative);
|
||||
contents.AppendFormat("{0}* __obj, ", classTypeNameNative);
|
||||
contents.Append("bool bind)");
|
||||
var contentsPrev = contents;
|
||||
var indent = " ";
|
||||
@@ -2034,7 +2034,7 @@ namespace Flax.Build.Bindings
|
||||
if (eventInfo.IsStatic)
|
||||
contents.Append(indent).AppendFormat(" f.Bind<{0}_ManagedWrapper>();", eventInfo.Name).AppendLine();
|
||||
else
|
||||
contents.Append(indent).AppendFormat(" f.Bind<{1}, &{1}::{0}_ManagedWrapper>(({1}*)obj);", eventInfo.Name, internalTypeName).AppendLine();
|
||||
contents.Append(indent).AppendFormat(" f.Bind<{1}, &{1}::{0}_ManagedWrapper>(({1}*)__obj);", eventInfo.Name, internalTypeName).AppendLine();
|
||||
contents.Append(indent).Append(" if (bind)").AppendLine();
|
||||
contents.Append(indent).AppendFormat(" {0}{1}.Bind(f);", bindPrefix, eventInfo.Name).AppendLine();
|
||||
contents.Append(indent).Append(" else").AppendLine();
|
||||
@@ -2074,7 +2074,7 @@ namespace Flax.Build.Bindings
|
||||
|
||||
// Scripting event wrapper binding method (binds/unbinds generic wrapper to C++ delegate)
|
||||
contents.AppendFormat(" static void {0}_Bind(", eventInfo.Name);
|
||||
contents.AppendFormat("{0}* obj, void* instance, bool bind)", classTypeNameNative).AppendLine();
|
||||
contents.AppendFormat("{0}* __obj, void* instance, bool bind)", classTypeNameNative).AppendLine();
|
||||
contents.Append(" {").AppendLine();
|
||||
contents.Append(" Function<void(");
|
||||
for (var i = 0; i < paramsCount; i++)
|
||||
@@ -2526,7 +2526,7 @@ namespace Flax.Build.Bindings
|
||||
contents.AppendLine(" auto typeHandle = Object->GetTypeHandle();");
|
||||
contents.AppendLine(" while (typeHandle)");
|
||||
contents.AppendLine(" {");
|
||||
contents.AppendLine($" auto method = typeHandle.Module->FindMethod(typeHandle, \"{functionInfo.Name}\", {functionInfo.Parameters.Count});");
|
||||
contents.AppendLine($" auto method = typeHandle.Module->FindMethod(typeHandle, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), {functionInfo.Parameters.Count});");
|
||||
contents.AppendLine(" if (method)");
|
||||
contents.AppendLine(" {");
|
||||
contents.AppendLine(" Variant __result;");
|
||||
@@ -2565,10 +2565,10 @@ namespace Flax.Build.Bindings
|
||||
contents.AppendLine(" }").AppendLine();
|
||||
|
||||
// Interface implementation wrapper accessor for scripting types
|
||||
contents.AppendLine(" static void* GetInterfaceWrapper(ScriptingObject* obj)");
|
||||
contents.AppendLine(" static void* GetInterfaceWrapper(ScriptingObject* __obj)");
|
||||
contents.AppendLine(" {");
|
||||
contents.AppendLine($" auto wrapper = New<{interfaceTypeNameInternal}Wrapper>();");
|
||||
contents.AppendLine(" wrapper->Object = obj;");
|
||||
contents.AppendLine(" wrapper->Object = __obj;");
|
||||
contents.AppendLine(" return wrapper;");
|
||||
contents.AppendLine(" }");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user