Merge remote-tracking branch 'origin/master' into 1.6

This commit is contained in:
Wojtek Figat
2023-04-17 13:18:08 +02:00
39 changed files with 954 additions and 362 deletions

BIN
Content/Editor/Camera/O_Camera.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -222,7 +222,7 @@ namespace FlaxEditor.Content
/// <summary> /// <summary>
/// Gets a value indicating whether this item can be dragged and dropped. /// Gets a value indicating whether this item can be dragged and dropped.
/// </summary> /// </summary>
public virtual bool CanDrag => true; public virtual bool CanDrag => Root != null;
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="ContentItem"/> exists on drive. /// Gets a value indicating whether this <see cref="ContentItem"/> exists on drive.

View File

@@ -80,7 +80,8 @@ namespace FlaxEditor.Content
/// </summary> /// </summary>
/// <param name="model">The associated model.</param> /// <param name="model">The associated model.</param>
/// <param name="created">The action to call once the collision data gets created (or reused from existing).</param> /// <param name="created">The action to call once the collision data gets created (or reused from existing).</param>
public void CreateCollisionDataFromModel(Model model, Action<CollisionData> created = null) /// <param name="withRenaming">True if start initial item renaming by user, or tru to skip it.</param>
public void CreateCollisionDataFromModel(Model model, Action<CollisionData> created = null, bool withRenaming = true)
{ {
// Check if there already is collision data for that model to reuse // Check if there already is collision data for that model to reuse
var modelItem = (AssetItem)Editor.Instance.ContentDatabase.Find(model.ID); var modelItem = (AssetItem)Editor.Instance.ContentDatabase.Find(model.ID);
@@ -140,7 +141,7 @@ namespace FlaxEditor.Content
}); });
}; };
var initialName = (modelItem?.ShortName ?? Path.GetFileNameWithoutExtension(model.Path)) + " Collision"; var initialName = (modelItem?.ShortName ?? Path.GetFileNameWithoutExtension(model.Path)) + " Collision";
Editor.Instance.Windows.ContentWin.NewItem(this, null, create, initialName); Editor.Instance.Windows.ContentWin.NewItem(this, null, create, initialName, withRenaming);
} }
} }
} }

View File

@@ -47,9 +47,23 @@ namespace FlaxEditor.Content
menu.AddButton("Create collision data", () => menu.AddButton("Create collision data", () =>
{ {
var model = FlaxEngine.Content.LoadAsync<Model>(((ModelItem)item).ID);
var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy<CollisionData>(); var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy<CollisionData>();
var selection = Editor.Instance.Windows.ContentWin.View.Selection;
if (selection.Count > 1)
{
// Batch action
var items = selection.ToArray(); // Clone to prevent issue when iterating over and content window changes the selection
foreach (var contentItem in items)
{
if (contentItem is ModelItem modelItem)
collisionDataProxy.CreateCollisionDataFromModel(FlaxEngine.Content.LoadAsync<Model>(modelItem.ID), null, false);
}
}
else
{
var model = FlaxEngine.Content.LoadAsync<Model>(((ModelItem)item).ID);
collisionDataProxy.CreateCollisionDataFromModel(model); collisionDataProxy.CreateCollisionDataFromModel(model);
}
}); });
} }

View File

@@ -40,6 +40,11 @@ namespace FlaxEditor.GUI.Tree
private Margin _margin; private Margin _margin;
private bool _autoSize = true; private bool _autoSize = true;
/// <summary>
/// The TreeNode that is being dragged over. This could have a value when not dragging.
/// </summary>
public TreeNode DraggedOverNode = null;
/// <summary> /// <summary>
/// Action fired when tree nodes selection gets changed. /// Action fired when tree nodes selection gets changed.
/// </summary> /// </summary>

View File

@@ -659,7 +659,7 @@ namespace FlaxEditor.GUI.Tree
Render2D.DrawText(TextFont.GetFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); Render2D.DrawText(TextFont.GetFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center);
// Draw drag and drop effect // Draw drag and drop effect
if (IsDragOver) if (IsDragOver && _tree.DraggedOverNode == this)
{ {
Color dragOverColor = style.BackgroundSelected * 0.6f; Color dragOverColor = style.BackgroundSelected * 0.6f;
Rectangle rect; Rectangle rect;
@@ -669,10 +669,10 @@ namespace FlaxEditor.GUI.Tree
rect = textRect; rect = textRect;
break; break;
case DragItemPositioning.Above: case DragItemPositioning.Above:
rect = new Rectangle(textRect.X, textRect.Y - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, textRect.Width, DefaultDragInsertPositionMargin * 2.0f); rect = new Rectangle(textRect.X, textRect.Top - DefaultDragInsertPositionMargin - DefaultNodeOffsetY - _margin.Top, textRect.Width, DefaultDragInsertPositionMargin * 2.0f);
break; break;
case DragItemPositioning.Below: case DragItemPositioning.Below:
rect = new Rectangle(textRect.X, textRect.Bottom - DefaultDragInsertPositionMargin, textRect.Width, DefaultDragInsertPositionMargin * 2.0f); rect = new Rectangle(textRect.X, textRect.Bottom + _margin.Bottom - DefaultDragInsertPositionMargin, textRect.Width, DefaultDragInsertPositionMargin * 2.0f);
break; break;
default: default:
rect = Rectangle.Empty; rect = Rectangle.Empty;
@@ -922,6 +922,8 @@ namespace FlaxEditor.GUI.Tree
if (result == DragDropEffect.None) if (result == DragDropEffect.None)
{ {
UpdateDrawPositioning(ref location); UpdateDrawPositioning(ref location);
if (ParentTree != null)
ParentTree.DraggedOverNode = this;
// Check if mouse is over header // Check if mouse is over header
_isDragOverHeader = TestHeaderHit(ref location); _isDragOverHeader = TestHeaderHit(ref location);
@@ -999,6 +1001,8 @@ namespace FlaxEditor.GUI.Tree
// Clear cache // Clear cache
_isDragOverHeader = false; _isDragOverHeader = false;
_dragOverMode = DragItemPositioning.None; _dragOverMode = DragItemPositioning.None;
if (ParentTree != null)
ParentTree.DraggedOverNode = null;
return result; return result;
} }

View File

@@ -707,6 +707,8 @@ namespace FlaxEditor.SceneGraph.GUI
{ {
DragData data; DragData data;
var tree = ParentTree; var tree = ParentTree;
if (tree.Selection.Count == 1)
Select();
// Check if this node is selected // Check if this node is selected
if (tree.Selection.Contains(this)) if (tree.Selection.Contains(this))
@@ -716,6 +718,11 @@ namespace FlaxEditor.SceneGraph.GUI
for (var i = 0; i < tree.Selection.Count; i++) for (var i = 0; i < tree.Selection.Count; i++)
{ {
var e = tree.Selection[i]; var e = tree.Selection[i];
// Skip if parent is already selected to keep correct parenting
if (tree.Selection.Contains(e.Parent))
continue;
if (e is ActorTreeNode node && node.ActorNode.CanDrag) if (e is ActorTreeNode node && node.ActorNode.CanDrag)
actors.Add(node.ActorNode); actors.Add(node.ActorNode);
} }

View File

@@ -320,6 +320,7 @@ namespace FlaxEditor.Windows
/// Shows popup dialog with UI to rename content item. /// Shows popup dialog with UI to rename content item.
/// </summary> /// </summary>
/// <param name="item">The item to rename.</param> /// <param name="item">The item to rename.</param>
/// <returns>The created renaming popup.</returns>
public void Rename(ContentItem item) public void Rename(ContentItem item)
{ {
// Show element in the view // Show element in the view
@@ -337,7 +338,21 @@ namespace FlaxEditor.Windows
popup.Tag = item; popup.Tag = item;
popup.Validate += OnRenameValidate; popup.Validate += OnRenameValidate;
popup.Renamed += renamePopup => Rename((ContentItem)renamePopup.Tag, renamePopup.Text); popup.Renamed += renamePopup => Rename((ContentItem)renamePopup.Tag, renamePopup.Text);
popup.Closed += renamePopup => popup.Closed += OnRenameClosed;
// For new asset we want to mock the initial value so user can press just Enter to use default name
if (_newElement != null)
{
popup.InitialValue = "?";
}
}
private bool OnRenameValidate(RenamePopup popup, string value)
{
return Editor.ContentEditing.IsValidAssetName((ContentItem)popup.Tag, value, out _);
}
private void OnRenameClosed(RenamePopup popup)
{ {
// Restore scrolling in content view // Restore scrolling in content view
if (_contentViewPanel.VScrollBar != null) if (_contentViewPanel.VScrollBar != null)
@@ -354,18 +369,6 @@ namespace FlaxEditor.Windows
_newElement.Dispose(); _newElement.Dispose();
_newElement = null; _newElement = null;
} }
};
// For new asset we want to mock the initial value so user can press just Enter to use default name
if (_newElement != null)
{
popup.InitialValue = "?";
}
}
private bool OnRenameValidate(RenamePopup popup, string value)
{
return Editor.ContentEditing.IsValidAssetName((ContentItem)popup.Tag, value, out _);
} }
/// <summary> /// <summary>
@@ -486,7 +489,7 @@ namespace FlaxEditor.Windows
/// <param name="item">The item to delete.</param> /// <param name="item">The item to delete.</param>
public void Delete(ContentItem item) public void Delete(ContentItem item)
{ {
Delete(new List<ContentItem>(1) { item }); Delete(Editor.Instance.Windows.ContentWin.View.Selection);
} }
/// <summary> /// <summary>
@@ -537,7 +540,7 @@ namespace FlaxEditor.Windows
destinationName = Utilities.Utils.IncrementNameNumber(item.ShortName, x => !File.Exists(StringUtils.CombinePaths(sourceFolder, x + extension))) + extension; destinationName = Utilities.Utils.IncrementNameNumber(item.ShortName, x => !File.Exists(StringUtils.CombinePaths(sourceFolder, x + extension))) + extension;
} }
return StringUtils.CombinePaths(sourceFolder, destinationName); return StringUtils.NormalizePath(StringUtils.CombinePaths(sourceFolder, destinationName));
} }
/// <summary> /// <summary>
@@ -564,6 +567,7 @@ namespace FlaxEditor.Windows
// Start renaming it // Start renaming it
if (targetItem != null) if (targetItem != null)
{ {
Select(targetItem);
Rename(targetItem); Rename(targetItem);
} }
} }
@@ -645,7 +649,8 @@ namespace FlaxEditor.Windows
/// <param name="argument">The argument passed to the proxy for the item creation. In most cases it is null.</param> /// <param name="argument">The argument passed to the proxy for the item creation. In most cases it is null.</param>
/// <param name="created">The event called when the item is crated by the user. The argument is the new item.</param> /// <param name="created">The event called when the item is crated by the user. The argument is the new item.</param>
/// <param name="initialName">The initial item name.</param> /// <param name="initialName">The initial item name.</param>
public void NewItem(ContentProxy proxy, object argument = null, Action<ContentItem> created = null, string initialName = null) /// <param name="withRenaming">True if start initial item renaming by user, or tru to skip it.</param>
public void NewItem(ContentProxy proxy, object argument = null, Action<ContentItem> created = null, string initialName = null, bool withRenaming = true)
{ {
Assert.IsNull(_newElement); Assert.IsNull(_newElement);
if (proxy == null) if (proxy == null)
@@ -667,6 +672,8 @@ namespace FlaxEditor.Windows
} while (parentFolder.FindChild(path) != null); } while (parentFolder.FindChild(path) != null);
} }
if (withRenaming)
{
// Create new asset proxy, add to view and rename it // Create new asset proxy, add to view and rename it
_newElement = new NewItem(path, proxy, argument) _newElement = new NewItem(path, proxy, argument)
{ {
@@ -676,6 +683,42 @@ namespace FlaxEditor.Windows
RefreshView(); RefreshView();
Rename(_newElement); Rename(_newElement);
} }
else
{
// Create new asset
try
{
Editor.Log(string.Format("Creating asset {0} in {1}", proxy.Name, path));
proxy.Create(path, argument);
}
catch (Exception ex)
{
Editor.LogWarning(ex);
Editor.LogError("Failed to create asset.");
return;
}
// Focus content window
Focus();
RootWindow?.Focus();
// Refresh database and view now
Editor.ContentDatabase.RefreshFolder(parentFolder, false);
RefreshView();
var newItem = parentFolder.FindChild(path);
if (newItem == null)
{
Editor.LogWarning("Failed to find the created new item.");
return;
}
// Auto-select item
Select(newItem, true);
// Custom post-action
created?.Invoke(newItem);
}
}
private void ContentDatabaseOnItemRemoved(ContentItem contentItem) private void ContentDatabaseOnItemRemoved(ContentItem contentItem)
{ {

View File

@@ -93,6 +93,7 @@ namespace FlaxEditor.Windows.Profiler
_liveRecordingButton = toolstrip.AddButton(editor.Icons.Play64); _liveRecordingButton = toolstrip.AddButton(editor.Icons.Play64);
_liveRecordingButton.LinkTooltip("Live profiling events recording"); _liveRecordingButton.LinkTooltip("Live profiling events recording");
_liveRecordingButton.AutoCheck = true; _liveRecordingButton.AutoCheck = true;
_liveRecordingButton.Clicked += () => _liveRecordingButton.Icon = LiveRecording ? editor.Icons.Stop64 : editor.Icons.Play64;
_clearButton = toolstrip.AddButton(editor.Icons.Rotate32, Clear); _clearButton = toolstrip.AddButton(editor.Icons.Rotate32, Clear);
_clearButton.LinkTooltip("Clear data"); _clearButton.LinkTooltip("Clear data");
toolstrip.AddSeparator(); toolstrip.AddSeparator();

View File

@@ -269,7 +269,7 @@ namespace FlaxEngine
public void Normalize() public void Normalize()
{ {
double length = Length; double length = Length;
if (!Mathd.IsZero(length)) if (length >= Mathd.Epsilon)
{ {
double inv = 1.0 / length; double inv = 1.0 / length;
X *= inv; X *= inv;

View File

@@ -346,7 +346,7 @@ namespace FlaxEngine
public void Normalize() public void Normalize()
{ {
double length = Length; double length = Length;
if (!Mathd.IsZero(length)) if (length >= Mathd.Epsilon)
{ {
double inv = 1.0 / length; double inv = 1.0 / length;
X *= inv; X *= inv;

View File

@@ -323,7 +323,7 @@ namespace FlaxEngine
public void Normalize() public void Normalize()
{ {
double length = Length; double length = Length;
if (!Mathd.IsZero(length)) if (length >= Mathd.Epsilon)
{ {
double inverse = 1.0 / length; double inverse = 1.0 / length;
X *= inverse; X *= inverse;

View File

@@ -287,7 +287,7 @@ namespace FlaxEngine
public void Normalize() public void Normalize()
{ {
float length = Length; float length = Length;
if (!Mathf.IsZero(length)) if (length >= Mathf.Epsilon)
{ {
float inv = 1.0f / length; float inv = 1.0f / length;
X *= inv; X *= inv;

View File

@@ -340,7 +340,7 @@ namespace FlaxEngine
public void Normalize() public void Normalize()
{ {
float length = Length; float length = Length;
if (!Mathf.IsZero(length)) if (length >= Mathf.Epsilon)
{ {
float inv = 1.0f / length; float inv = 1.0f / length;
X *= inv; X *= inv;

View File

@@ -305,7 +305,7 @@ namespace FlaxEngine
public void Normalize() public void Normalize()
{ {
float length = Length; float length = Length;
if (!Mathf.IsZero(length)) if (length >= Mathf.Epsilon)
{ {
float inverse = 1.0f / length; float inverse = 1.0f / length;
X *= inverse; X *= inverse;

View File

@@ -2,8 +2,10 @@
#if USE_LARGE_WORLDS #if USE_LARGE_WORLDS
using Real = System.Double; using Real = System.Double;
using Mathr = FlaxEngine.Mathd;
#else #else
using Real = System.Single; using Real = System.Single;
using Mathr = FlaxEngine.Mathf;
#endif #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -175,7 +177,7 @@ namespace FlaxEngine
public void Normalize() public void Normalize()
{ {
Real length = Normal.Length; Real length = Normal.Length;
if (!Mathf.IsZero(length)) if (length >= Mathr.Epsilon)
{ {
Real rcp = 1.0f / length; Real rcp = 1.0f / length;
Normal.X *= rcp; Normal.X *= rcp;

View File

@@ -337,7 +337,7 @@ namespace FlaxEngine
public void Normalize() public void Normalize()
{ {
float length = Length; float length = Length;
if (!Mathf.IsZero(length)) if (length >= Mathf.Epsilon)
{ {
float inverse = 1.0f / length; float inverse = 1.0f / length;
X *= inverse; X *= inverse;

View File

@@ -2,8 +2,10 @@
#if USE_LARGE_WORLDS #if USE_LARGE_WORLDS
using Real = System.Double; using Real = System.Double;
using Mathr = FlaxEngine.Mathd;
#else #else
using Real = System.Single; using Real = System.Single;
using Mathr = FlaxEngine.Mathf;
#endif #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -204,22 +206,22 @@ namespace FlaxEngine
/// <summary> /// <summary>
/// Gets a value indicting whether this instance is normalized. /// Gets a value indicting whether this instance is normalized.
/// </summary> /// </summary>
public bool IsNormalized => Mathf.IsOne(X * X + Y * Y); public bool IsNormalized => Mathr.IsOne(X * X + Y * Y);
/// <summary> /// <summary>
/// Gets a value indicting whether this vector is zero /// Gets a value indicting whether this vector is zero
/// </summary> /// </summary>
public bool IsZero => Mathf.IsZero(X) && Mathf.IsZero(Y); public bool IsZero => Mathr.IsZero(X) && Mathr.IsZero(Y);
/// <summary> /// <summary>
/// Gets a minimum component value /// Gets a minimum component value
/// </summary> /// </summary>
public Real MinValue => Mathf.Min(X, Y); public Real MinValue => Mathr.Min(X, Y);
/// <summary> /// <summary>
/// Gets a maximum component value /// Gets a maximum component value
/// </summary> /// </summary>
public Real MaxValue => Mathf.Max(X, Y); public Real MaxValue => Mathr.Max(X, Y);
/// <summary> /// <summary>
/// Gets an arithmetic average value of all vector components. /// Gets an arithmetic average value of all vector components.
@@ -234,7 +236,7 @@ namespace FlaxEngine
/// <summary> /// <summary>
/// Gets a vector with values being absolute values of that vector. /// Gets a vector with values being absolute values of that vector.
/// </summary> /// </summary>
public Vector2 Absolute => new Vector2(Mathf.Abs(X), Mathf.Abs(Y)); public Vector2 Absolute => new Vector2(Mathr.Abs(X), Mathr.Abs(Y));
/// <summary> /// <summary>
/// Gets a vector with values being opposite to values of that vector. /// Gets a vector with values being opposite to values of that vector.
@@ -293,8 +295,8 @@ namespace FlaxEngine
/// </summary> /// </summary>
public void Normalize() public void Normalize()
{ {
Real length = Length; Real length = (Real)Math.Sqrt(X * X + Y * Y);
if (!Mathf.IsZero(length)) if (length >= Mathr.Epsilon)
{ {
Real inv = 1.0f / length; Real inv = 1.0f / length;
X *= inv; X *= inv;
@@ -905,8 +907,8 @@ namespace FlaxEngine
/// <remarks>Passing <paramref name="amount" /> a value of 0 will cause <paramref name="start" /> to be returned; a value of 1 will cause <paramref name="end" /> to be returned.</remarks> /// <remarks>Passing <paramref name="amount" /> a value of 0 will cause <paramref name="start" /> to be returned; a value of 1 will cause <paramref name="end" /> to be returned.</remarks>
public static void Lerp(ref Vector2 start, ref Vector2 end, float amount, out Vector2 result) public static void Lerp(ref Vector2 start, ref Vector2 end, float amount, out Vector2 result)
{ {
result.X = Mathf.Lerp(start.X, end.X, amount); result.X = Mathr.Lerp(start.X, end.X, amount);
result.Y = Mathf.Lerp(start.Y, end.Y, amount); result.Y = Mathr.Lerp(start.Y, end.Y, amount);
} }
/// <summary> /// <summary>
@@ -933,8 +935,8 @@ namespace FlaxEngine
/// <remarks>Passing <paramref name="amount" /> a value of 0 will cause <paramref name="start" /> to be returned; a value of 1 will cause <paramref name="end" /> to be returned.</remarks> /// <remarks>Passing <paramref name="amount" /> a value of 0 will cause <paramref name="start" /> to be returned; a value of 1 will cause <paramref name="end" /> to be returned.</remarks>
public static void Lerp(ref Vector2 start, ref Vector2 end, ref Vector2 amount, out Vector2 result) public static void Lerp(ref Vector2 start, ref Vector2 end, ref Vector2 amount, out Vector2 result)
{ {
result.X = Mathf.Lerp(start.X, end.X, amount.X); result.X = Mathr.Lerp(start.X, end.X, amount.X);
result.Y = Mathf.Lerp(start.Y, end.Y, amount.Y); result.Y = Mathr.Lerp(start.Y, end.Y, amount.Y);
} }
/// <summary> /// <summary>
@@ -960,7 +962,7 @@ namespace FlaxEngine
/// <param name="result">When the method completes, contains the cubic interpolation of the two vectors.</param> /// <param name="result">When the method completes, contains the cubic interpolation of the two vectors.</param>
public static void SmoothStep(ref Vector2 start, ref Vector2 end, float amount, out Vector2 result) public static void SmoothStep(ref Vector2 start, ref Vector2 end, float amount, out Vector2 result)
{ {
amount = Mathf.SmoothStep(amount); amount = Mathr.SmoothStep(amount);
Lerp(ref start, ref end, amount, out result); Lerp(ref start, ref end, amount, out result);
} }
@@ -1553,7 +1555,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Vector2 left, Vector2 right) public static bool operator ==(Vector2 left, Vector2 right)
{ {
return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y); return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y);
} }
/// <summary> /// <summary>
@@ -1565,7 +1567,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Vector2 left, Vector2 right) public static bool operator !=(Vector2 left, Vector2 right)
{ {
return !Mathf.NearEqual(left.X, right.X) || !Mathf.NearEqual(left.Y, right.Y); return !Mathr.NearEqual(left.X, right.X) || !Mathr.NearEqual(left.Y, right.Y);
} }
/// <summary> /// <summary>
@@ -1671,7 +1673,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Vector2 other) public bool Equals(ref Vector2 other)
{ {
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y); return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y);
} }
/// <summary> /// <summary>
@@ -1679,7 +1681,7 @@ namespace FlaxEngine
/// </summary> /// </summary>
public static bool Equals(ref Vector2 a, ref Vector2 b) public static bool Equals(ref Vector2 a, ref Vector2 b)
{ {
return Mathf.NearEqual(a.X, b.X) && Mathf.NearEqual(a.Y, b.Y); return Mathr.NearEqual(a.X, b.X) && Mathr.NearEqual(a.Y, b.Y);
} }
/// <summary> /// <summary>
@@ -1690,7 +1692,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Vector2 other) public bool Equals(Vector2 other)
{ {
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y); return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y);
} }
/// <summary> /// <summary>
@@ -1700,7 +1702,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value) public override bool Equals(object value)
{ {
return value is Vector2 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y); return value is Vector2 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y);
} }
} }
} }

View File

@@ -240,9 +240,9 @@ public:
void Normalize() void Normalize()
{ {
const T length = Math::Sqrt(X * X + Y * Y); const T length = Math::Sqrt(X * X + Y * Y);
if (!Math::IsZero(length)) if (length >= ZeroTolerance)
{ {
const T invLength = 1.0f / length; const T invLength = (T)1.0f / length;
X *= invLength; X *= invLength;
Y *= invLength; Y *= invLength;
} }
@@ -547,9 +547,9 @@ public:
{ {
Vector2Base r = v; Vector2Base r = v;
const T length = Math::Sqrt(r.X * r.X + r.Y * r.Y); const T length = Math::Sqrt(r.X * r.X + r.Y * r.Y);
if (Math::Abs(length) >= ZeroTolerance) if (length >= ZeroTolerance)
{ {
const T inv = 1.0f / length; const T inv = (T)1.0f / length;
r.X *= inv; r.X *= inv;
r.Y *= inv; r.Y *= inv;
} }

View File

@@ -2,8 +2,10 @@
#if USE_LARGE_WORLDS #if USE_LARGE_WORLDS
using Real = System.Double; using Real = System.Double;
using Mathr = FlaxEngine.Mathd;
#else #else
using Real = System.Single; using Real = System.Single;
using Mathr = FlaxEngine.Mathf;
#endif #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -253,7 +255,7 @@ namespace FlaxEngine
/// <summary> /// <summary>
/// Gets a value indicting whether this instance is normalized. /// Gets a value indicting whether this instance is normalized.
/// </summary> /// </summary>
public bool IsNormalized => Mathf.IsOne(X * X + Y * Y + Z * Z); public bool IsNormalized => Mathr.IsOne(X * X + Y * Y + Z * Z);
/// <summary> /// <summary>
/// Gets the normalized vector. Returned vector has length equal 1. /// Gets the normalized vector. Returned vector has length equal 1.
@@ -271,22 +273,22 @@ namespace FlaxEngine
/// <summary> /// <summary>
/// Gets a value indicting whether this vector is zero /// Gets a value indicting whether this vector is zero
/// </summary> /// </summary>
public bool IsZero => Mathf.IsZero(X) && Mathf.IsZero(Y) && Mathf.IsZero(Z); public bool IsZero => Mathr.IsZero(X) && Mathr.IsZero(Y) && Mathr.IsZero(Z);
/// <summary> /// <summary>
/// Gets a value indicting whether this vector is one /// Gets a value indicting whether this vector is one
/// </summary> /// </summary>
public bool IsOne => Mathf.IsOne(X) && Mathf.IsOne(Y) && Mathf.IsOne(Z); public bool IsOne => Mathr.IsOne(X) && Mathr.IsOne(Y) && Mathr.IsOne(Z);
/// <summary> /// <summary>
/// Gets a minimum component value /// Gets a minimum component value
/// </summary> /// </summary>
public Real MinValue => Mathf.Min(X, Mathf.Min(Y, Z)); public Real MinValue => Mathr.Min(X, Mathr.Min(Y, Z));
/// <summary> /// <summary>
/// Gets a maximum component value /// Gets a maximum component value
/// </summary> /// </summary>
public Real MaxValue => Mathf.Max(X, Mathf.Max(Y, Z)); public Real MaxValue => Mathr.Max(X, Mathr.Max(Y, Z));
/// <summary> /// <summary>
/// Gets an arithmetic average value of all vector components. /// Gets an arithmetic average value of all vector components.
@@ -301,7 +303,7 @@ namespace FlaxEngine
/// <summary> /// <summary>
/// Gets a vector with values being absolute values of that vector. /// Gets a vector with values being absolute values of that vector.
/// </summary> /// </summary>
public Vector3 Absolute => new Vector3(Mathf.Abs(X), Mathf.Abs(Y), Mathf.Abs(Z)); public Vector3 Absolute => new Vector3(Mathr.Abs(X), Mathr.Abs(Y), Mathr.Abs(Z));
/// <summary> /// <summary>
/// Gets a vector with values being opposite to values of that vector. /// Gets a vector with values being opposite to values of that vector.
@@ -364,8 +366,8 @@ namespace FlaxEngine
/// </summary> /// </summary>
public void Normalize() public void Normalize()
{ {
Real length = Length; Real length = (Real)Math.Sqrt(X * X + Y * Y + Z * Z);
if (!Mathf.IsZero(length)) if (length >= Mathr.Epsilon)
{ {
Real inv = 1.0f / length; Real inv = 1.0f / length;
X *= inv; X *= inv;
@@ -1021,9 +1023,9 @@ namespace FlaxEngine
/// <remarks>Passing <paramref name="amount" /> a value of 0 will cause <paramref name="start" /> to be returned; a value of 1 will cause <paramref name="end" /> to be returned.</remarks> /// <remarks>Passing <paramref name="amount" /> a value of 0 will cause <paramref name="start" /> to be returned; a value of 1 will cause <paramref name="end" /> to be returned.</remarks>
public static void Lerp(ref Vector3 start, ref Vector3 end, float amount, out Vector3 result) public static void Lerp(ref Vector3 start, ref Vector3 end, float amount, out Vector3 result)
{ {
result.X = Mathf.Lerp(start.X, end.X, amount); result.X = Mathr.Lerp(start.X, end.X, amount);
result.Y = Mathf.Lerp(start.Y, end.Y, amount); result.Y = Mathr.Lerp(start.Y, end.Y, amount);
result.Z = Mathf.Lerp(start.Z, end.Z, amount); result.Z = Mathr.Lerp(start.Z, end.Z, amount);
} }
/// <summary> /// <summary>
@@ -1049,7 +1051,7 @@ namespace FlaxEngine
/// <param name="result">When the method completes, contains the cubic interpolation of the two vectors.</param> /// <param name="result">When the method completes, contains the cubic interpolation of the two vectors.</param>
public static void SmoothStep(ref Vector3 start, ref Vector3 end, float amount, out Vector3 result) public static void SmoothStep(ref Vector3 start, ref Vector3 end, float amount, out Vector3 result)
{ {
amount = Mathf.SmoothStep(amount); amount = Mathr.SmoothStep(amount);
Lerp(ref start, ref end, amount, out result); Lerp(ref start, ref end, amount, out result);
} }
@@ -1211,7 +1213,7 @@ namespace FlaxEngine
public static Vector3 Project(Vector3 vector, Vector3 onNormal) public static Vector3 Project(Vector3 vector, Vector3 onNormal)
{ {
Real sqrMag = Dot(onNormal, onNormal); Real sqrMag = Dot(onNormal, onNormal);
if (sqrMag < Mathf.Epsilon) if (sqrMag < Mathr.Epsilon)
return Zero; return Zero;
return onNormal * Dot(vector, onNormal) / sqrMag; return onNormal * Dot(vector, onNormal) / sqrMag;
} }
@@ -1235,10 +1237,10 @@ namespace FlaxEngine
/// <returns>The angle (in degrees).</returns> /// <returns>The angle (in degrees).</returns>
public static Real Angle(Vector3 from, Vector3 to) public static Real Angle(Vector3 from, Vector3 to)
{ {
Real dot = Mathf.Clamp(Dot(from.Normalized, to.Normalized), -1.0f, 1.0f); Real dot = Mathr.Clamp(Dot(from.Normalized, to.Normalized), -1.0f, 1.0f);
if (Mathf.Abs(dot) > (1.0f - Mathf.Epsilon)) if (Mathr.Abs(dot) > (1.0f - Mathr.Epsilon))
return dot > 0.0f ? 0.0f : 180.0f; return dot > 0.0f ? 0.0f : 180.0f;
return (Real)Math.Acos(dot) * Mathf.RadiansToDegrees; return (Real)Math.Acos(dot) * Mathr.RadiansToDegrees;
} }
/// <summary> /// <summary>
@@ -1826,7 +1828,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Vector3 left, Vector3 right) public static bool operator ==(Vector3 left, Vector3 right)
{ {
return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y) && Mathf.NearEqual(left.Z, right.Z); return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y) && Mathr.NearEqual(left.Z, right.Z);
} }
/// <summary> /// <summary>
@@ -1838,7 +1840,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Vector3 left, Vector3 right) public static bool operator !=(Vector3 left, Vector3 right)
{ {
return !Mathf.NearEqual(left.X, right.X) || !Mathf.NearEqual(left.Y, right.Y) || !Mathf.NearEqual(left.Z, right.Z); return !Mathr.NearEqual(left.X, right.X) || !Mathr.NearEqual(left.Y, right.Y) || !Mathr.NearEqual(left.Z, right.Z);
} }
/// <summary> /// <summary>
@@ -1947,7 +1949,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Vector3 other) public bool Equals(ref Vector3 other)
{ {
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z); return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z);
} }
/// <summary> /// <summary>
@@ -1958,7 +1960,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Vector3 other) public bool Equals(Vector3 other)
{ {
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z); return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z);
} }
/// <summary> /// <summary>
@@ -1968,7 +1970,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value) public override bool Equals(object value)
{ {
return value is Vector3 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z); return value is Vector3 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z);
} }
} }
} }

View File

@@ -266,9 +266,9 @@ public:
void Normalize() void Normalize()
{ {
const T length = Math::Sqrt(X * X + Y * Y + Z * Z); const T length = Math::Sqrt(X * X + Y * Y + Z * Z);
if (Math::Abs(length) >= ZeroTolerance) if (length >= ZeroTolerance)
{ {
const T inv = 1.0f / length; const T inv = (T)1.0f / length;
X *= inv; X *= inv;
Y *= inv; Y *= inv;
Z *= inv; Z *= inv;
@@ -566,6 +566,56 @@ public:
result = Vector3Base(Math::Clamp(v.X, min.X, max.X), Math::Clamp(v.Y, min.Y, max.Y), Math::Clamp(v.Z, min.Z, max.Z)); result = Vector3Base(Math::Clamp(v.X, min.X, max.X), Math::Clamp(v.Y, min.Y, max.Y), Math::Clamp(v.Z, min.Z, max.Z));
} }
/// <summary>
/// Makes sure that Length of the output vector is always below max and above 0.
/// </summary>
/// <param name="v">Input Vector.</param>
/// <param name="max">Max Length</param>
static Vector3Base ClampLength(const Vector3Base& v, float max)
{
return ClampLength(v, 0, max);
}
/// <summary>
/// Makes sure that Length of the output vector is always below max and above min.
/// </summary>
/// <param name="v">Input Vector.</param>
/// <param name="min">Min Length</param>
/// <param name="max">Max Length</param>
static Vector3Base ClampLength(const Vector3Base& v, float min, float max)
{
Vector3Base result;
ClampLength(v, min, max, result);
return result;
}
/// <summary>
/// Makes sure that Length of the output vector is always below max and above min.
/// </summary>
/// <param name="v">Input Vector.</param>
/// <param name="min">Min Length</param>
/// <param name="max">Max Length</param>
/// <param name="result">The result vector.</param>
static void ClampLength(const Vector3Base& v, float min, float max, Vector3Base& result)
{
result = v;
T lenSq = result.LengthSquared();
if (lenSq > max * max)
{
T scaleFactor = max / (T)Math::Sqrt(lenSq);
result.X *= scaleFactor;
result.Y *= scaleFactor;
result.Z *= scaleFactor;
}
if (lenSq < min * min)
{
T scaleFactor = min / (T)Math::Sqrt(lenSq);
result.X *= scaleFactor;
result.Y *= scaleFactor;
result.Z *= scaleFactor;
}
}
// Calculates the distance between two vectors // Calculates the distance between two vectors
// @param a The first vector // @param a The first vector
// @param b The second vector // @param b The second vector
@@ -595,9 +645,9 @@ public:
{ {
Vector3Base r = v; Vector3Base r = v;
const T length = Math::Sqrt(r.X * r.X + r.Y * r.Y + r.Z * r.Z); const T length = Math::Sqrt(r.X * r.X + r.Y * r.Y + r.Z * r.Z);
if (Math::Abs(length) >= ZeroTolerance) if (length >= ZeroTolerance)
{ {
const T inv = 1.0f / length; const T inv = (T)1.0f / length;
r.X *= inv; r.X *= inv;
r.Y *= inv; r.Y *= inv;
r.Z *= inv; r.Z *= inv;

View File

@@ -2,8 +2,10 @@
#if USE_LARGE_WORLDS #if USE_LARGE_WORLDS
using Real = System.Double; using Real = System.Double;
using Mathr = FlaxEngine.Mathd;
#else #else
using Real = System.Single; using Real = System.Single;
using Mathr = FlaxEngine.Mathf;
#endif #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -256,27 +258,27 @@ namespace FlaxEngine
/// <summary> /// <summary>
/// Gets a value indicting whether this instance is normalized. /// Gets a value indicting whether this instance is normalized.
/// </summary> /// </summary>
public bool IsNormalized => Mathf.IsOne(X * X + Y * Y + Z * Z + W * W); public bool IsNormalized => Mathr.IsOne(X * X + Y * Y + Z * Z + W * W);
/// <summary> /// <summary>
/// Gets a value indicting whether this vector is zero /// Gets a value indicting whether this vector is zero
/// </summary> /// </summary>
public bool IsZero => Mathf.IsZero(X) && Mathf.IsZero(Y) && Mathf.IsZero(Z) && Mathf.IsZero(W); public bool IsZero => Mathr.IsZero(X) && Mathr.IsZero(Y) && Mathr.IsZero(Z) && Mathr.IsZero(W);
/// <summary> /// <summary>
/// Gets a value indicting whether this vector is one /// Gets a value indicting whether this vector is one
/// </summary> /// </summary>
public bool IsOne => Mathf.IsOne(X) && Mathf.IsOne(Y) && Mathf.IsOne(Z) && Mathf.IsOne(W); public bool IsOne => Mathr.IsOne(X) && Mathr.IsOne(Y) && Mathr.IsOne(Z) && Mathr.IsOne(W);
/// <summary> /// <summary>
/// Gets a minimum component value /// Gets a minimum component value
/// </summary> /// </summary>
public Real MinValue => Mathf.Min(X, Mathf.Min(Y, Mathf.Min(Z, W))); public Real MinValue => Mathr.Min(X, Mathr.Min(Y, Mathr.Min(Z, W)));
/// <summary> /// <summary>
/// Gets a maximum component value /// Gets a maximum component value
/// </summary> /// </summary>
public Real MaxValue => Mathf.Max(X, Mathf.Max(Y, Mathf.Max(Z, W))); public Real MaxValue => Mathr.Max(X, Mathr.Max(Y, Mathr.Max(Z, W)));
/// <summary> /// <summary>
/// Gets an arithmetic average value of all vector components. /// Gets an arithmetic average value of all vector components.
@@ -291,7 +293,7 @@ namespace FlaxEngine
/// <summary> /// <summary>
/// Gets a vector with values being absolute values of that vector. /// Gets a vector with values being absolute values of that vector.
/// </summary> /// </summary>
public Vector4 Absolute => new Vector4(Mathf.Abs(X), Mathf.Abs(Y), Mathf.Abs(Z), Mathf.Abs(W)); public Vector4 Absolute => new Vector4(Mathr.Abs(X), Mathr.Abs(Y), Mathr.Abs(Z), Mathr.Abs(W));
/// <summary> /// <summary>
/// Gets a vector with values being opposite to values of that vector. /// Gets a vector with values being opposite to values of that vector.
@@ -358,8 +360,8 @@ namespace FlaxEngine
/// </summary> /// </summary>
public void Normalize() public void Normalize()
{ {
Real length = Length; Real length = (Real)Math.Sqrt(X * X + Y * Y + Z * Z + W * W);
if (!Mathf.IsZero(length)) if (length >= Mathr.Epsilon)
{ {
Real inverse = 1.0f / length; Real inverse = 1.0f / length;
X *= inverse; X *= inverse;
@@ -856,10 +858,10 @@ namespace FlaxEngine
/// <remarks>Passing <paramref name="amount" /> a value of 0 will cause <paramref name="start" /> to be returned; a value of 1 will cause <paramref name="end" /> to be returned.</remarks> /// <remarks>Passing <paramref name="amount" /> a value of 0 will cause <paramref name="start" /> to be returned; a value of 1 will cause <paramref name="end" /> to be returned.</remarks>
public static void Lerp(ref Vector4 start, ref Vector4 end, Real amount, out Vector4 result) public static void Lerp(ref Vector4 start, ref Vector4 end, Real amount, out Vector4 result)
{ {
result.X = Mathf.Lerp(start.X, end.X, amount); result.X = Mathr.Lerp(start.X, end.X, amount);
result.Y = Mathf.Lerp(start.Y, end.Y, amount); result.Y = Mathr.Lerp(start.Y, end.Y, amount);
result.Z = Mathf.Lerp(start.Z, end.Z, amount); result.Z = Mathr.Lerp(start.Z, end.Z, amount);
result.W = Mathf.Lerp(start.W, end.W, amount); result.W = Mathr.Lerp(start.W, end.W, amount);
} }
/// <summary> /// <summary>
@@ -885,7 +887,7 @@ namespace FlaxEngine
/// <param name="result">When the method completes, contains the cubic interpolation of the two vectors.</param> /// <param name="result">When the method completes, contains the cubic interpolation of the two vectors.</param>
public static void SmoothStep(ref Vector4 start, ref Vector4 end, Real amount, out Vector4 result) public static void SmoothStep(ref Vector4 start, ref Vector4 end, Real amount, out Vector4 result)
{ {
amount = Mathf.SmoothStep(amount); amount = Mathr.SmoothStep(amount);
Lerp(ref start, ref end, amount, out result); Lerp(ref start, ref end, amount, out result);
} }
@@ -1360,7 +1362,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Vector4 left, Vector4 right) public static bool operator ==(Vector4 left, Vector4 right)
{ {
return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y) && Mathf.NearEqual(left.Z, right.Z) && Mathf.NearEqual(left.W, right.W); return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y) && Mathr.NearEqual(left.Z, right.Z) && Mathr.NearEqual(left.W, right.W);
} }
/// <summary> /// <summary>
@@ -1481,7 +1483,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="Vector4" /> is equal to this instance; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the specified <see cref="Vector4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(ref Vector4 other) public bool Equals(ref Vector4 other)
{ {
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W);
} }
/// <summary> /// <summary>
@@ -1492,7 +1494,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Vector4 other) public bool Equals(Vector4 other)
{ {
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W);
} }
/// <summary> /// <summary>
@@ -1502,7 +1504,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value) public override bool Equals(object value)
{ {
return value is Vector4 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); return value is Vector4 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W);
} }
} }
} }

View File

@@ -8,7 +8,7 @@
/// Universal representation of a contiguous region of arbitrary memory. /// Universal representation of a contiguous region of arbitrary memory.
/// </summary> /// </summary>
template<typename T> template<typename T>
class Span API_CLASS(InBuild) class Span
{ {
protected: protected:
T* _data; T* _data;

View File

@@ -156,7 +156,6 @@ void NetworkTransform::Serialize(NetworkStream* stream)
transform = Transform::Identity; transform = Transform::Identity;
// Encode data // Encode data
const NetworkObjectRole role = NetworkReplicator::GetObjectRole(this);
Data data; Data data;
data.LocalSpace = LocalSpace; data.LocalSpace = LocalSpace;
data.HasSequenceIndex = Mode == ReplicationModes::Prediction; data.HasSequenceIndex = Mode == ReplicationModes::Prediction;

View File

@@ -236,6 +236,16 @@ NetworkClient* NetworkManager::GetClient(const NetworkConnection& connection)
return nullptr; return nullptr;
} }
NetworkClient* NetworkManager::GetClient(uint32 clientId)
{
for (NetworkClient* client : Clients)
{
if (client->ClientId == clientId)
return client;
}
return nullptr;
}
bool NetworkManager::StartServer() bool NetworkManager::StartServer()
{ {
PROFILE_CPU(); PROFILE_CPU();

View File

@@ -153,6 +153,13 @@ public:
/// <returns>Found client or null.</returns> /// <returns>Found client or null.</returns>
API_FUNCTION() static NetworkClient* GetClient(API_PARAM(Ref) const NetworkConnection& connection); API_FUNCTION() static NetworkClient* GetClient(API_PARAM(Ref) const NetworkConnection& connection);
/// <summary>
/// Gets the network client with a given identifier. Returns null if failed to find it.
/// </summary>
/// <param name="clientId">Network client identifier (synchronized on all peers).</param>
/// <returns>Found client or null.</returns>
API_FUNCTION() static NetworkClient* GetClient(uint32 clientId);
public: public:
/// <summary> /// <summary>
/// Starts the network in server mode. Returns true if failed (eg. invalid config). /// Starts the network in server mode. Returns true if failed (eg. invalid config).

View File

@@ -29,12 +29,12 @@
#include "Engine/Threading/Threading.h" #include "Engine/Threading/Threading.h"
#include "Engine/Threading/ThreadLocal.h" #include "Engine/Threading/ThreadLocal.h"
// Enables verbose logging for Network Replicator actions (dev-only) #if !BUILD_RELEASE
#define NETWORK_REPLICATOR_DEBUG_LOG 0 bool NetworkReplicator::EnableLog = false;
#if NETWORK_REPLICATOR_DEBUG_LOG
#include "Engine/Core/Log.h" #include "Engine/Core/Log.h"
#define NETWORK_REPLICATOR_LOG(messageType, format, ...) LOG(messageType, format, ##__VA_ARGS__) #include "Engine/Content/Content.h"
#define NETWORK_REPLICATOR_LOG(messageType, format, ...) if (NetworkReplicator::EnableLog) { LOG(messageType, format, ##__VA_ARGS__); }
#define USE_NETWORK_REPLICATOR_LOG 1
#else #else
#define NETWORK_REPLICATOR_LOG(messageType, format, ...) #define NETWORK_REPLICATOR_LOG(messageType, format, ...)
#endif #endif
@@ -107,10 +107,15 @@ struct NetworkReplicatedObject
uint32 OwnerClientId; uint32 OwnerClientId;
uint32 LastOwnerFrame = 0; uint32 LastOwnerFrame = 0;
NetworkObjectRole Role; NetworkObjectRole Role;
uint8 Spawned = false; uint8 Spawned : 1;
DataContainer<uint32> TargetClientIds; DataContainer<uint32> TargetClientIds;
INetworkObject* AsNetworkObject; INetworkObject* AsNetworkObject;
NetworkReplicatedObject()
{
Spawned = 0;
}
bool operator==(const NetworkReplicatedObject& other) const bool operator==(const NetworkReplicatedObject& other) const
{ {
return Object == other.Object; return Object == other.Object;
@@ -149,6 +154,7 @@ struct ReplicateItem
Guid ObjectId; Guid ObjectId;
uint16 PartsLeft; uint16 PartsLeft;
uint32 OwnerFrame; uint32 OwnerFrame;
uint32 OwnerClientId;
Array<byte> Data; Array<byte> Data;
}; };
@@ -179,6 +185,7 @@ struct RpcItem
NetworkRpcName Name; NetworkRpcName Name;
NetworkRpcInfo Info; NetworkRpcInfo Info;
BytesContainer ArgsData; BytesContainer ArgsData;
DataContainer<uint32> Targets;
}; };
namespace namespace
@@ -330,6 +337,46 @@ void BuildCachedTargets(const Array<NetworkClient*>& clients, const DataContaine
} }
} }
void BuildCachedTargets(const Array<NetworkClient*>& clients, const DataContainer<uint32>& clientIds1, const Span<uint32>& clientIds2, const uint32 excludedClientId = NetworkManager::ServerClientId)
{
CachedTargets.Clear();
if (clientIds1.IsValid())
{
if (clientIds2.IsValid())
{
for (const NetworkClient* client : clients)
{
if (client->State == NetworkConnectionState::Connected && client->ClientId != excludedClientId)
{
for (int32 i = 0; i < clientIds1.Length(); i++)
{
if (clientIds1[i] == client->ClientId)
{
for (int32 j = 0; j < clientIds2.Length(); j++)
{
if (clientIds2[j] == client->ClientId)
{
CachedTargets.Add(client->Connection);
break;
}
}
break;
}
}
}
}
}
else
{
BuildCachedTargets(clients, clientIds1, excludedClientId);
}
}
else
{
BuildCachedTargets(clients, clientIds2, excludedClientId);
}
}
FORCE_INLINE void BuildCachedTargets(const NetworkReplicatedObject& item) FORCE_INLINE void BuildCachedTargets(const NetworkReplicatedObject& item)
{ {
// By default send object to all connected clients excluding the owner but with optional TargetClientIds list // By default send object to all connected clients excluding the owner but with optional TargetClientIds list
@@ -484,13 +531,43 @@ void SetupObjectSpawnGroupItem(ScriptingObject* obj, Array<SpawnGroup, InlinedAl
group->Items.Add(&spawnItem); group->Items.Add(&spawnItem);
} }
void FindObjectsForSpawn(SpawnGroup& group, ChunkedArray<SpawnItem, 256>& spawnItems, ScriptingObject* obj)
{
// Add any registered network objects
auto it = Objects.Find(obj->GetID());
if (it != Objects.End())
{
auto& item = it->Item;
if (!item.Spawned)
{
// One of the parents of this object is being spawned so spawn it too
item.Spawned = true;
auto& spawnItem = spawnItems.AddOne();
spawnItem.Object = obj;
spawnItem.Targets.Link(item.TargetClientIds);
spawnItem.OwnerClientId = item.OwnerClientId;
spawnItem.Role = item.Role;
group.Items.Add(&spawnItem);
}
}
// Iterate over children
if (auto* actor = ScriptingObject::Cast<Actor>(obj))
{
for (auto* script : actor->Scripts)
FindObjectsForSpawn(group, spawnItems, script);
for (auto* child : actor->Children)
FindObjectsForSpawn(group, spawnItems, child);
}
}
void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject* obj) void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject* obj)
{ {
// TODO: implement objects state replication frequency and dirtying // TODO: implement objects state replication frequency and dirtying
} }
template<typename MessageType> template<typename MessageType>
ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& msgData, uint16 partStart, uint16 partSize) ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& msgData, uint16 partStart, uint16 partSize, uint32 senderClientId)
{ {
// Reuse or add part item // Reuse or add part item
ReplicateItem* replicateItem = nullptr; ReplicateItem* replicateItem = nullptr;
@@ -510,6 +587,7 @@ ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& ms
replicateItem->ObjectId = msgData.ObjectId; replicateItem->ObjectId = msgData.ObjectId;
replicateItem->PartsLeft = msgData.PartsCount; replicateItem->PartsLeft = msgData.PartsCount;
replicateItem->OwnerFrame = msgData.OwnerFrame; replicateItem->OwnerFrame = msgData.OwnerFrame;
replicateItem->OwnerClientId = senderClientId;
replicateItem->Data.Resize(msgData.DataSize); replicateItem->Data.Resize(msgData.DataSize);
} }
@@ -523,7 +601,7 @@ ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& ms
return replicateItem; return replicateItem;
} }
void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, byte* data, uint32 dataSize) void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, byte* data, uint32 dataSize, uint32 senderClientId)
{ {
ScriptingObject* obj = item.Object.Get(); ScriptingObject* obj = item.Object.Get();
if (!obj) if (!obj)
@@ -543,6 +621,7 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b
CachedReadStream = New<NetworkStream>(); CachedReadStream = New<NetworkStream>();
NetworkStream* stream = CachedReadStream; NetworkStream* stream = CachedReadStream;
stream->Initialize(data, dataSize); stream->Initialize(data, dataSize);
stream->SenderId = senderClientId;
// Deserialize object // Deserialize object
const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, false); const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, false);
@@ -559,6 +638,11 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b
DirtyObjectImpl(item, obj); DirtyObjectImpl(item, obj);
} }
NetworkRpcParams::NetworkRpcParams(const NetworkStream* stream)
: SenderId(stream->SenderId)
{
}
#if !COMPILE_WITHOUT_CSHARP #if !COMPILE_WITHOUT_CSHARP
#include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Scripting/ManagedCLR/MUtils.h"
@@ -600,9 +684,9 @@ void NetworkReplicator::AddRPC(const ScriptingTypeHandle& typeHandle, const Stri
NetworkRpcInfo::RPCsTable[rpcName] = rpcInfo; NetworkRpcInfo::RPCsTable[rpcName] = rpcInfo;
} }
void NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream) void NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MonoArray* targetIds)
{ {
EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream); EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan<uint32>(targetIds));
} }
StringAnsiView NetworkReplicator::GetCSharpCachedName(const StringAnsiView& name) StringAnsiView NetworkReplicator::GetCSharpCachedName(const StringAnsiView& name)
@@ -881,10 +965,11 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC()
if (CachedWriteStream == nullptr) if (CachedWriteStream == nullptr)
CachedWriteStream = New<NetworkStream>(); CachedWriteStream = New<NetworkStream>();
CachedWriteStream->Initialize(); CachedWriteStream->Initialize();
CachedWriteStream->SenderId = NetworkManager::LocalClientId;
return CachedWriteStream; return CachedWriteStream;
} }
void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream) void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds)
{ {
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name)); const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name));
if (!info || !obj || NetworkManager::IsOffline()) if (!info || !obj || NetworkManager::IsOffline())
@@ -895,8 +980,8 @@ void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa
rpc.Name.First = type; rpc.Name.First = type;
rpc.Name.Second = name; rpc.Name.Second = name;
rpc.Info = *info; rpc.Info = *info;
const Span<byte> argsData(argsStream->GetBuffer(), argsStream->GetPosition()); rpc.ArgsData.Copy(Span<byte>(argsStream->GetBuffer(), argsStream->GetPosition()));
rpc.ArgsData.Copy(argsData); rpc.Targets.Copy(targetIds);
#if USE_EDITOR || !BUILD_RELEASE #if USE_EDITOR || !BUILD_RELEASE
auto it = Objects.Find(obj->GetID()); auto it = Objects.Find(obj->GetID());
if (it == Objects.End()) if (it == Objects.End())
@@ -983,12 +1068,9 @@ void NetworkInternal::NetworkReplicatorUpdate()
ScopeLock lock(ObjectsLock); ScopeLock lock(ObjectsLock);
if (Objects.Count() == 0) if (Objects.Count() == 0)
return; return;
if (CachedWriteStream == nullptr)
CachedWriteStream = New<NetworkStream>();
const bool isClient = NetworkManager::IsClient(); const bool isClient = NetworkManager::IsClient();
const bool isServer = NetworkManager::IsServer(); const bool isServer = NetworkManager::IsServer();
const bool isHost = NetworkManager::IsHost(); const bool isHost = NetworkManager::IsHost();
NetworkStream* stream = CachedWriteStream;
NetworkPeer* peer = NetworkManager::Peer; NetworkPeer* peer = NetworkManager::Peer;
if (!isClient && NewClients.Count() != 0) if (!isClient && NewClients.Count() != 0)
@@ -1114,9 +1196,16 @@ void NetworkInternal::NetworkReplicatorUpdate()
} }
// Spawn groups of objects // Spawn groups of objects
ChunkedArray<SpawnItem, 256> spawnItems;
for (SpawnGroup& g : spawnGroups) for (SpawnGroup& g : spawnGroups)
{ {
// Include any added objects within spawn group that were not spawned manually (eg. AddObject for script/actor attached to spawned actor)
ScriptingObject* groupRoot = g.Items[0]->Object.Get();
FindObjectsForSpawn(g, spawnItems, groupRoot);
SendObjectSpawnMessage(g, NetworkManager::Clients); SendObjectSpawnMessage(g, NetworkManager::Clients);
spawnItems.Clear();
} }
SpawnQueue.Clear(); SpawnQueue.Clear();
} }
@@ -1139,7 +1228,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
auto& item = it->Item; auto& item = it->Item;
// Replicate from all collected parts data // Replicate from all collected parts data
InvokeObjectReplication(item, e.OwnerFrame, e.Data.Get(), e.Data.Count()); InvokeObjectReplication(item, e.OwnerFrame, e.Data.Get(), e.Data.Count(), e.OwnerClientId);
} }
} }
@@ -1147,6 +1236,10 @@ void NetworkInternal::NetworkReplicatorUpdate()
} }
// Brute force synchronize all networked objects with clients // 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: 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) // 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) for (auto it = Objects.Begin(); it.IsNotEnd(); ++it)
@@ -1276,12 +1369,16 @@ void NetworkInternal::NetworkReplicatorUpdate()
if (e.Info.Server && isClient) if (e.Info.Server && isClient)
{ {
// Client -> Server // 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());
#endif
peer->EndSendMessage(channel, msg); peer->EndSendMessage(channel, msg);
} }
else if (e.Info.Client && (isServer || isHost)) else if (e.Info.Client && (isServer || isHost))
{ {
// Server -> Client(s) // Server -> Client(s)
BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, NetworkManager::LocalClientId); BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, e.Targets, NetworkManager::LocalClientId);
peer->EndSendMessage(channel, msg, CachedTargets); peer->EndSendMessage(channel, msg, CachedTargets);
} }
} }
@@ -1307,16 +1404,17 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo
if (client && item.OwnerClientId != client->ClientId) if (client && item.OwnerClientId != client->ClientId)
return; return;
const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId;
if (msgData.PartsCount == 1) if (msgData.PartsCount == 1)
{ {
// Replicate // Replicate
InvokeObjectReplication(item, msgData.OwnerFrame, event.Message.Buffer + event.Message.Position, msgData.DataSize); InvokeObjectReplication(item, msgData.OwnerFrame, event.Message.Buffer + event.Message.Position, msgData.DataSize, senderClientId);
} }
else else
{ {
// Add to replication from multiple parts // Add to replication from multiple parts
const uint16 msgMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicate); const uint16 msgMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicate);
ReplicateItem* replicateItem = AddObjectReplicateItem(event, msgData, 0, msgMaxData); ReplicateItem* replicateItem = AddObjectReplicateItem(event, msgData, 0, msgMaxData, senderClientId);
replicateItem->Object = e->Object; replicateItem->Object = e->Object;
} }
} }
@@ -1329,7 +1427,8 @@ void NetworkInternal::OnNetworkMessageObjectReplicatePart(NetworkEvent& event, N
if (DespawnedObjects.Contains(msgData.ObjectId)) if (DespawnedObjects.Contains(msgData.ObjectId))
return; // Skip replicating not-existing objects return; // Skip replicating not-existing objects
AddObjectReplicateItem(event, msgData, msgData.PartStart, msgData.PartSize); const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId;
AddObjectReplicateItem(event, msgData, msgData.PartStart, msgData.PartSize, senderClientId);
} }
void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
@@ -1465,10 +1564,6 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
if (!obj->IsRegistered()) if (!obj->IsRegistered())
obj->RegisterObject(); obj->RegisterObject();
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId); const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
if (!parent && msgDataItem.ParentId.IsValid())
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
}
// Add object to the list // Add object to the list
NetworkReplicatedObject item; NetworkReplicatedObject item;
@@ -1499,6 +1594,21 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
sceneObject->SetParent(parent->Object.As<Actor>()); sceneObject->SetParent(parent->Object.As<Actor>());
else if (auto* parentActor = Scripting::TryFindObject<Actor>(msgDataItem.ParentId)) else if (auto* parentActor = Scripting::TryFindObject<Actor>(msgDataItem.ParentId))
sceneObject->SetParent(parentActor); sceneObject->SetParent(parentActor);
else if (msgDataItem.ParentId.IsValid())
{
#if USE_NETWORK_REPLICATOR_LOG
// Ignore case when parent object in a message was a scene (eg. that is already unloaded on a client)
AssetInfo assetInfo;
if (!Content::GetAssetInfo(msgDataItem.ParentId, assetInfo) || assetInfo.TypeName == TEXT("FlaxEngine.SceneAsset"))
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
}
#endif
}
}
else if (!parent && msgDataItem.ParentId.IsValid())
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
} }
if (item.AsNetworkObject) if (item.AsNetworkObject)
@@ -1602,7 +1712,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name); const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name);
if (!info) if (!info)
{ {
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgData.ObjectId, String(msgData.RpcTypeName), String(msgData.RpcName)); NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(msgData.RpcTypeName), String(msgData.RpcName), msgData.ObjectId);
return; return;
} }
@@ -1622,6 +1732,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
if (CachedReadStream == nullptr) if (CachedReadStream == nullptr)
CachedReadStream = New<NetworkStream>(); CachedReadStream = New<NetworkStream>();
NetworkStream* stream = CachedReadStream; NetworkStream* stream = CachedReadStream;
stream->SenderId = client ? client->ClientId : NetworkManager::ServerClientId;
stream->Initialize(event.Message.Buffer + event.Message.Position, msgData.ArgsSize); stream->Initialize(event.Message.Buffer + event.Message.Position, msgData.ArgsSize);
// Execute RPC // Execute RPC

View File

@@ -119,10 +119,11 @@ namespace FlaxEngine.Networking
/// <param name="type">The RPC type.</param> /// <param name="type">The RPC type.</param>
/// <param name="name">The RPC name.</param> /// <param name="name">The RPC name.</param>
/// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param> /// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param>
/// <param name="targetIds">Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.</param>
[Unmanaged] [Unmanaged]
public static void EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream) public static void EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream, uint[] targetIds = null)
{ {
Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream)); Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream), targetIds);
} }
/// <summary> /// <summary>

View File

@@ -3,6 +3,7 @@
#pragma once #pragma once
#include "Types.h" #include "Types.h"
#include "Engine/Core/Types/Span.h"
#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/ScriptingType.h" #include "Engine/Scripting/ScriptingType.h"
@@ -34,6 +35,13 @@ API_CLASS(static, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API Netw
typedef void (*SerializeFunc)(void* instance, NetworkStream* stream, void* tag); typedef void (*SerializeFunc)(void* instance, NetworkStream* stream, void* tag);
public: public:
#if !BUILD_RELEASE
/// <summary>
/// Enables verbose logging of the networking runtime. Can be used to debug problems of missing RPC invoke or object replication issues.
/// </summary>
API_FIELD() static bool EnableLog;
#endif
/// <summary> /// <summary>
/// Adds the network replication serializer for a given type. /// Adds the network replication serializer for a given type.
/// </summary> /// </summary>
@@ -168,13 +176,14 @@ public:
/// <param name="type">The RPC type.</param> /// <param name="type">The RPC type.</param>
/// <param name="name">The RPC name.</param> /// <param name="name">The RPC name.</param>
/// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param> /// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param>
static void EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream); /// <param name="targetIds">Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.</param>
static void EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds = Span<uint32>());
private: private:
#if !COMPILE_WITHOUT_CSHARP #if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& typeHandle, const Function<void(void*, void*)>& serialize, const Function<void(void*, void*)>& deserialize); API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& typeHandle, const Function<void(void*, void*)>& serialize, const Function<void(void*, void*)>& deserialize);
API_FUNCTION(NoProxy) static void AddRPC(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, const Function<void(void*, void*)>& execute, bool isServer, bool isClient, NetworkChannelType channel); API_FUNCTION(NoProxy) static void AddRPC(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, const Function<void(void*, void*)>& execute, bool isServer, bool isClient, NetworkChannelType channel);
API_FUNCTION(NoProxy) static void CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream); API_FUNCTION(NoProxy) static void CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MonoArray* targetIds);
static StringAnsiView GetCSharpCachedName(const StringAnsiView& name); static StringAnsiView GetCSharpCachedName(const StringAnsiView& name);
#endif #endif
}; };

View File

@@ -5,6 +5,7 @@
#include "Engine/Core/Log.h" #include "Engine/Core/Log.h"
#include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/Pair.h" #include "Engine/Core/Types/Pair.h"
#include "Engine/Core/Types/Span.h"
#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Scripting/ScriptingType.h" #include "Engine/Scripting/ScriptingType.h"
@@ -12,6 +13,24 @@
class NetworkStream; class NetworkStream;
// Additional context parameters for Network RPC execution (eg. to identify who sends the data).
API_STRUCT(NoDefault, Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkRpcParams
{
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkRpcParams);
NetworkRpcParams() = default;
NetworkRpcParams(const NetworkStream* stream);
/// <summary>
/// The ClientId of the network client that is a data sender. Can be used to detect who send the incoming RPC or replication data. Ignored when sending data.
/// </summary>
API_FIELD() uint32 SenderId = 0;
/// <summary>
/// The list of ClientId of the network clients that should receive RPC. Can be used to send RPC to a specific client(s). Ignored when receiving data.
/// </summary>
API_FIELD() Span<uint32> TargetIds;
};
// Network RPC identifier name (pair of type and function name) // Network RPC identifier name (pair of type and function name)
typedef Pair<ScriptingTypeHandle, StringAnsiView> NetworkRpcName; typedef Pair<ScriptingTypeHandle, StringAnsiView> NetworkRpcName;

View File

@@ -23,6 +23,11 @@ private:
public: public:
~NetworkStream(); ~NetworkStream();
/// <summary>
/// The ClientId of the network client that is a data sender. Can be used to detect who send the incoming RPC or replication data. Set to the current client when writing data.
/// </summary>
API_FIELD(ReadOnly) uint32 SenderId = 0;
/// <summary> /// <summary>
/// Gets the pointer to the native stream memory buffer. /// Gets the pointer to the native stream memory buffer.
/// </summary> /// </summary>

View File

@@ -17,3 +17,4 @@ struct NetworkConnection;
struct NetworkMessage; struct NetworkMessage;
struct NetworkConfig; struct NetworkConfig;
struct NetworkDriverStats; struct NetworkDriverStats;
struct NetworkRpcParams;

View File

@@ -122,6 +122,12 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines
float lastWhitespaceX = 0; float lastWhitespaceX = 0;
bool lastMoveLine = false; bool lastMoveLine = false;
int32 lastUpperIndex = INVALID_INDEX;
float lastUpperX = 0;
int32 lastUnderscoreIndex = INVALID_INDEX;
float lastUnderscoreX = 0;
// Process each character to split text into single lines // Process each character to split text into single lines
for (int32 currentIndex = 0; currentIndex < textLength;) for (int32 currentIndex = 0; currentIndex < textLength;)
{ {
@@ -141,6 +147,22 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines
lastWhitespaceX = cursorX; lastWhitespaceX = cursorX;
} }
// Check if character is an upper case letter
const bool isUpper = StringUtils::IsUpper(currentChar);
if (isUpper && currentIndex != 0)
{
lastUpperIndex = currentIndex;
lastUpperX = cursorX;
}
// Check if character is an underscore
const bool isUnderscore = currentChar == '_';
if (isUnderscore)
{
lastUnderscoreIndex = currentIndex;
lastUnderscoreX = cursorX;
}
// Check if it's a newline character // Check if it's a newline character
if (currentChar == '\n') if (currentChar == '\n')
{ {
@@ -185,6 +207,20 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines
currentIndex = lastWhitespaceIndex + 1; currentIndex = lastWhitespaceIndex + 1;
nextCharIndex = currentIndex; nextCharIndex = currentIndex;
} }
else if (lastUpperIndex != INVALID_INDEX)
{
cursorX = lastUpperX;
tmpLine.LastCharIndex = lastUpperIndex - 1;
currentIndex = lastUpperIndex;
nextCharIndex = currentIndex;
}
else if (lastUnderscoreIndex != INVALID_INDEX)
{
cursorX = lastUnderscoreX;
tmpLine.LastCharIndex = lastUnderscoreIndex;
currentIndex = lastUnderscoreIndex + 1;
nextCharIndex = currentIndex;
}
else else
{ {
nextCharIndex = currentIndex; nextCharIndex = currentIndex;
@@ -224,6 +260,12 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines
lastWhitespaceIndex = INVALID_INDEX; lastWhitespaceIndex = INVALID_INDEX;
lastWhitespaceX = 0; lastWhitespaceX = 0;
lastUpperIndex = INVALID_INDEX;
lastUpperX = 0;
lastUnderscoreIndex = INVALID_INDEX;
lastUnderscoreX = 0;
previous.IsValid = false; previous.IsValid = false;
} }

View File

@@ -448,8 +448,8 @@ namespace MUtils
/// </summary> /// </summary>
/// <param name="data">The native array object.</param> /// <param name="data">The native array object.</param>
/// <returns>The output array pointer and size.</returns> /// <returns>The output array pointer and size.</returns>
template<typename T> template<typename T, typename AllocationType = HeapAllocation>
FORCE_INLINE Span<T> ToSpan(const Array<T>& data) FORCE_INLINE Span<T> ToSpan(const Array<T, AllocationType>& data)
{ {
return Span<T>(data.Get(), data.Count()); return Span<T>(data.Get(), data.Count());
} }

View File

@@ -987,7 +987,7 @@ namespace FlaxEngine.GUI
{ {
// Hide tooltip // Hide tooltip
Tooltip?.Hide(); Tooltip?.Hide();
Root.DoDragDrop(data); Root?.DoDragDrop(data);
} }
#endregion #endregion

View File

@@ -175,6 +175,10 @@ namespace Flax.Build.Bindings
CppVariantFromTypes[wrapperName] = typeInfo; CppVariantFromTypes[wrapperName] = typeInfo;
return $"VariantFrom{wrapperName}Dictionary({value})"; return $"VariantFrom{wrapperName}Dictionary({value})";
} }
if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null)
{
return "Variant()"; // TODO: Span to Variant converting (use utility method the same way as for arrays)
}
var apiType = FindApiTypeInfo(buildData, typeInfo, caller); var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
if (apiType != null) if (apiType != null)
@@ -221,6 +225,10 @@ namespace Flax.Build.Bindings
CppVariantToTypes.Add(typeInfo); CppVariantToTypes.Add(typeInfo);
return $"MoveTemp(VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}({value}))"; return $"MoveTemp(VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}({value}))";
} }
if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null)
{
return $"{typeInfo}()"; // Cannot be implemented since Variant stores array of Variants thus cannot get linear span of data
}
var apiType = FindApiTypeInfo(buildData, typeInfo, caller); var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
if (apiType != null) if (apiType != null)
@@ -487,7 +495,7 @@ namespace Flax.Build.Bindings
return "{0}.GetManagedInstance()"; return "{0}.GetManagedInstance()";
} }
// Array or Span or DataContainer // Array or DataContainer
if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null)
{ {
#if USE_NETCORE #if USE_NETCORE
@@ -502,6 +510,13 @@ namespace Flax.Build.Bindings
return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")"; return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")";
} }
// Span
if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null)
{
type = "MonoArray*";
return "MUtils::Span({0}, " + GenerateCppGetNativeClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")";
}
// BytesContainer // BytesContainer
if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null) if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null)
{ {

View File

@@ -231,9 +231,16 @@ namespace Flax.Build.Plugins
var arg = functionInfo.Parameters[i]; var arg = functionInfo.Parameters[i];
if (i != 0) if (i != 0)
argNames += ", "; argNames += ", ";
argNames += arg.Name;
// Special handling of Rpc Params
if (!arg.Type.IsPtr && arg.Type.Type == "NetworkRpcParams")
{
argNames += "NetworkRpcParams(stream)";
continue;
}
// Deserialize arguments // Deserialize arguments
argNames += arg.Name;
contents.AppendLine($" {arg.Type.Type} {arg.Name};"); contents.AppendLine($" {arg.Type.Type} {arg.Name};");
contents.AppendLine($" stream->Read({arg.Name});"); contents.AppendLine($" stream->Read({arg.Name});");
} }
@@ -250,16 +257,24 @@ namespace Flax.Build.Plugins
contents.Append(" static void ").Append(functionInfo.Name).AppendLine("_Invoke(ScriptingObject* obj, void** args)"); contents.Append(" static void ").Append(functionInfo.Name).AppendLine("_Invoke(ScriptingObject* obj, void** args)");
contents.AppendLine(" {"); contents.AppendLine(" {");
contents.AppendLine(" NetworkStream* stream = NetworkReplicator::BeginInvokeRPC();"); contents.AppendLine(" NetworkStream* stream = NetworkReplicator::BeginInvokeRPC();");
contents.AppendLine(" Span<uint32> targetIds;");
for (int i = 0; i < functionInfo.Parameters.Count; i++) for (int i = 0; i < functionInfo.Parameters.Count; i++)
{ {
var arg = functionInfo.Parameters[i]; var arg = functionInfo.Parameters[i];
// Special handling of Rpc Params
if (!arg.Type.IsPtr && arg.Type.Type == "NetworkRpcParams")
{
contents.AppendLine($" targetIds = ((NetworkRpcParams*)args[{i}])->TargetIds;");
continue;
}
// Serialize arguments // Serialize arguments
contents.AppendLine($" stream->Write(*({arg.Type.Type}*)args[{i}]);"); contents.AppendLine($" stream->Write(*({arg.Type.Type}*)args[{i}]);");
} }
// Invoke RPC // Invoke RPC
contents.AppendLine($" NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream);"); contents.AppendLine($" NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream, targetIds);");
contents.AppendLine(" }"); contents.AppendLine(" }");
} }
contents.AppendLine(); contents.AppendLine();
@@ -332,14 +347,14 @@ namespace Flax.Build.Plugins
contents.AppendLine(); contents.AppendLine();
} }
private bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo type) private static bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo type)
{ {
// TODO: what if type fields have custom replication settings (eg. compression)? // TODO: what if type fields have custom replication settings (eg. compression)?
type.EnsureInited(buildData); type.EnsureInited(buildData);
return type.IsPod; return type.IsPod;
} }
private bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo caller, ApiTypeInfo apiType, TypeInfo type) private static bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo caller, ApiTypeInfo apiType, TypeInfo type)
{ {
if (type.IsPod(buildData, caller)) if (type.IsPod(buildData, caller))
{ {
@@ -350,6 +365,12 @@ namespace Flax.Build.Plugins
return false; return false;
} }
private static bool IsRawPOD(TypeReference type)
{
// TODO:
return type.IsValueType;
}
private void OnGenerateCppTypeSerializeData(Builder.BuildData buildData, ApiTypeInfo caller, StringBuilder contents, TypeInfo type, string name, bool serialize) private void OnGenerateCppTypeSerializeData(Builder.BuildData buildData, ApiTypeInfo caller, StringBuilder contents, TypeInfo type, string name, bool serialize)
{ {
var apiType = BindingsGenerator.FindApiTypeInfo(buildData, type, caller); var apiType = BindingsGenerator.FindApiTypeInfo(buildData, type, caller);
@@ -557,10 +578,9 @@ namespace Flax.Build.Plugins
if (context.AddSerializers.Count != 0 || context.MethodRPCs.Count != 0) if (context.AddSerializers.Count != 0 || context.MethodRPCs.Count != 0)
{ {
// Create class // Create class
var name = "Initializer"; var name = "NetworkingPlugin";
var idx = 0; if (module.Types.Any(x => x.Name == name))
while (module.Types.Any(x => x.Name == name)) throw new Exception($"Failed to generate network replication for assembly '{Path.GetFileName(assemblyPath)}' that already has net code generated. Rebuild project.");
name = "Initializer" + idx++;
var c = new TypeDefinition("", name, TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit); var c = new TypeDefinition("", name, TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit);
module.GetType("System.Object", out var objectType); module.GetType("System.Object", out var objectType);
c.BaseType = module.ImportReference(objectType); c.BaseType = module.ImportReference(objectType);
@@ -774,7 +794,7 @@ namespace Flax.Build.Plugins
{ {
if (!f.HasAttribute(NetworkReplicatedAttribute)) if (!f.HasAttribute(NetworkReplicatedAttribute))
continue; continue;
GenerateSerializerType(ref context, type, serialize, f, null, f.FieldType, il); GenerateSerializerType(ref context, type, serialize, f.FieldType, il, new DotnetValueContext(f));
} }
// Serialize all type properties marked with NetworkReplicated attribute // Serialize all type properties marked with NetworkReplicated attribute
@@ -782,7 +802,7 @@ namespace Flax.Build.Plugins
{ {
if (!p.HasAttribute(NetworkReplicatedAttribute)) if (!p.HasAttribute(NetworkReplicatedAttribute))
continue; continue;
GenerateSerializerType(ref context, type, serialize, null, p, p.PropertyType, il); GenerateSerializerType(ref context, type, serialize, p.PropertyType, il, new DotnetValueContext(p));
} }
if (serialize) if (serialize)
@@ -862,53 +882,384 @@ namespace Flax.Build.Plugins
} }
} }
private static void GenerateSerializerType(ref DotnetContext context, TypeDefinition type, bool serialize, FieldReference field, PropertyDefinition property, TypeReference valueType, ILProcessor il) private struct DotnetValueContext
{
public FieldReference Field;
public PropertyDefinition Property;
public int LocalVarIndex;
public OpCode PropertyGetOpCode
{
get
{ {
if (field == null && property == null)
throw new ArgumentException();
TypeDefinition networkStreamType = context.NetworkStreamType.Resolve();
var propertyGetOpCode = OpCodes.Call; var propertyGetOpCode = OpCodes.Call;
var propertySetOpCode = OpCodes.Call; if (Property != null && Property.GetMethod.IsVirtual)
if (property != null)
{
if (property.GetMethod == null)
{
MonoCecil.CompilationError($"Missing getter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", property);
context.Failed = true;
return;
}
if (property.SetMethod == null)
{
MonoCecil.CompilationError($"Missing setter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", property);
context.Failed = true;
return;
}
if (property.GetMethod.IsVirtual)
propertyGetOpCode = OpCodes.Callvirt; propertyGetOpCode = OpCodes.Callvirt;
if (property.SetMethod.IsVirtual) return propertyGetOpCode;
propertySetOpCode = OpCodes.Callvirt; }
}
public OpCode PropertySetOpCode
{
get
{
var propertyGetOpCode = OpCodes.Call;
if (Property != null && Property.GetMethod.IsVirtual)
propertyGetOpCode = OpCodes.Callvirt;
return propertyGetOpCode;
}
}
public DotnetValueContext(FieldDefinition field)
{
Field = field;
Property = null;
LocalVarIndex = -1;
}
public DotnetValueContext(PropertyDefinition property)
{
Field = null;
Property = property;
LocalVarIndex = -1;
}
public DotnetValueContext(int localVarIndex)
{
Field = null;
Property = null;
LocalVarIndex = localVarIndex;
}
public void GetProperty(ILProcessor il, int propertyVar)
{
if (Property != null)
{
// <elementType>[] array = ArrayProperty;
il.Emit(OpCodes.Ldarg_0);
il.Emit(PropertyGetOpCode, Property.GetMethod);
il.Emit(OpCodes.Stloc, propertyVar);
LocalVarIndex = propertyVar;
}
}
public void SetProperty(ILProcessor il)
{
if (Property != null)
{
// ArrayProperty = array
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldloc, LocalVarIndex);
il.Emit(PropertySetOpCode, Property.SetMethod);
}
}
public void Load(ILProcessor il)
{
if (Field != null)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, Field);
}
else if (Property != null && LocalVarIndex == -1)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(PropertyGetOpCode, Property.GetMethod);
}
else
{
il.Emit(OpCodes.Ldloc, LocalVarIndex);
}
}
public void LoadAddress(ILProcessor il)
{
if (Field != null)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldflda, Field);
}
else if (Property != null && LocalVarIndex == -1)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(PropertyGetOpCode, Property.GetMethod);
}
else
{
il.Emit(OpCodes.Ldloca_S, (byte)LocalVarIndex);
}
}
public void Store(ILProcessor il)
{
if (Field != null)
{
il.Emit(OpCodes.Stfld, Field);
}
else if (Property != null)
{
il.Emit(PropertySetOpCode, Property.SetMethod);
}
else
{
il.Emit(OpCodes.Stloc, LocalVarIndex);
}
}
}
private static void GenerateSerializerType(ref DotnetContext context, TypeDefinition type, bool serialize, TypeReference valueType, ILProcessor il, DotnetValueContext valueContext)
{
if (valueContext.Property != null)
{
if (valueContext.Property.GetMethod == null)
{
MonoCecil.CompilationError($"Missing getter method for property '{valueContext.Property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", valueContext.Property);
context.Failed = true;
return;
}
if (valueContext.Property.SetMethod == null)
{
MonoCecil.CompilationError($"Missing setter method for property '{valueContext.Property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", valueContext.Property);
context.Failed = true;
return;
}
} }
ModuleDefinition module = type.Module; ModuleDefinition module = type.Module;
TypeDefinition valueTypeDef = valueType.Resolve(); TypeDefinition valueTypeDef = valueType.Resolve();
TypeDefinition networkStreamType = context.NetworkStreamType.Resolve();
// Ensure to have valid serialization already generated for that value type (eg. when using custom structure field serialization) // Ensure to have valid serialization already generated for that value type (eg. when using custom structure field serialization)
GenerateTypeSerialization(ref context, valueTypeDef); GenerateTypeSerialization(ref context, valueTypeDef);
if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) if (valueType.IsArray)
{
var elementType = valueType.GetElementType();
var isRawPod = IsRawPOD(elementType); // Whether to use raw memory copy (eg. int, enum, Vector2)
var varStart = il.Body.Variables.Count;
module.GetType("System.Int32", out var intType);
il.Body.Variables.Add(new VariableDefinition(intType)); // [0] int length
if (isRawPod)
{
il.Body.Variables.Add(new VariableDefinition(new PointerType(elementType))); // [1] <elementType>*
il.Body.Variables.Add(new VariableDefinition(new PinnedType(valueType))); // [2] <elementType>[] pinned
}
else
{
il.Body.Variables.Add(new VariableDefinition(intType)); // [1] int idx
il.Body.Variables.Add(new VariableDefinition(elementType)); // [2] <elementType>
}
if (valueContext.Property != null)
il.Body.Variables.Add(new VariableDefinition(valueType)); // [3] <elementType>[]
il.Body.InitLocals = true;
valueContext.GetProperty(il, varStart + 3);
if (serialize)
{
// <elementType>[] array = Array;
il.Emit(OpCodes.Nop);
valueContext.Load(il);
// int length = ((array != null) ? array.Length : 0);
il.Emit(OpCodes.Dup);
Instruction jmp1 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brtrue_S, jmp1);
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldc_I4_0);
Instruction jmp2 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Br_S, jmp2);
il.Append(jmp1);
il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Conv_I4);
il.Append(jmp2);
il.Emit(OpCodes.Stloc, varStart + 0);
// stream.WriteInt32(length);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldloc, varStart + 0);
var m = networkStreamType.GetMethod("WriteInt32");
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
il.Emit(OpCodes.Nop);
if (isRawPod)
{
// fixed (<elementType>* bytes2 = Array)
valueContext.Load(il);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, varStart + 2);
Instruction jmp3 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brfalse_S, jmp3);
il.Emit(OpCodes.Ldloc_2);
il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Conv_I4);
Instruction jmp4 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brtrue_S, jmp4);
il.Append(jmp3);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Conv_U);
il.Emit(OpCodes.Stloc, varStart + 1); // <elementType>*
Instruction jmp5 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Br_S, jmp5);
// stream.WriteBytes((byte*)bytes, length * sizeof(<elementType>)));
il.Append(jmp4);
il.Emit(OpCodes.Ldloc, varStart + 2);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldelema, elementType);
il.Emit(OpCodes.Conv_U);
il.Emit(OpCodes.Stloc, varStart + 1); // <elementType>*
il.Append(jmp5);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldloc, varStart + 1); // <elementType>*
il.Emit(OpCodes.Ldloc, varStart + 0);
il.Emit(OpCodes.Sizeof, elementType);
il.Emit(OpCodes.Mul);
m = networkStreamType.GetMethod("WriteBytes", 2);
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Stloc, varStart + 2);
}
else
{
// int idx = 0
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc, varStart + 1); // idx
Instruction jmp3 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Br_S, jmp3);
// <elementType> element = array[idx]
Instruction jmp4 = il.Create(OpCodes.Nop);
il.Append(jmp4);
valueContext.Load(il);
il.Emit(OpCodes.Ldloc, varStart + 1); // idx
il.Emit(OpCodes.Ldelem_Ref);
il.Emit(OpCodes.Stloc, varStart + 2); // <elementType>
// Serialize item value
il.Emit(OpCodes.Nop);
GenerateSerializerType(ref context, type, serialize, elementType, il, new DotnetValueContext(varStart + 2));
// idx++
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldloc, varStart + 1); // idx
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Stloc, varStart + 1); // idx
// idx < length
il.Append(jmp3);
il.Emit(OpCodes.Ldloc, varStart + 1); // idx
il.Emit(OpCodes.Ldloc, varStart + 0); // length
il.Emit(OpCodes.Clt);
il.Emit(OpCodes.Brtrue_S, jmp4);
}
}
else
{
// int length = stream.ReadInt32();
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_1);
var m = networkStreamType.GetMethod("ReadInt32");
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
il.Emit(OpCodes.Stloc, varStart + 0); // length
// System.Array.Resize(ref Array, length);
valueContext.LoadAddress(il);
il.Emit(OpCodes.Ldloc, varStart + 0); // length
module.TryGetTypeReference("System.Array", out var arrayType);
if (arrayType == null)
module.GetType("System.Array", out arrayType);
m = arrayType.Resolve().GetMethod("Resize", 2);
il.Emit(OpCodes.Call, module.ImportReference(m.InflateGeneric(elementType)));
il.Emit(OpCodes.Nop);
if (isRawPod)
{
// fixed (<elementType>* buffer = Array)
valueContext.Load(il);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, varStart + 2);
Instruction jmp1 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brfalse_S, jmp1);
il.Emit(OpCodes.Ldloc, varStart + 2);
il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Conv_I4);
Instruction jmp2 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brtrue_S, jmp2);
il.Append(jmp1);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Conv_U);
il.Emit(OpCodes.Stloc, varStart + 1); // <elementType>* buffer
Instruction jmp3 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Br_S, jmp3);
// stream.ReadBytes((byte*)buffer, length * sizeof(<elementType>));
il.Append(jmp2);
il.Emit(OpCodes.Ldloc, varStart + 2);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldelema, elementType);
il.Emit(OpCodes.Conv_U);
il.Emit(OpCodes.Stloc, varStart + 1); // <elementType>* buffer
il.Append(jmp3);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldloc, varStart + 1); // <elementType>* buffer
il.Emit(OpCodes.Ldloc, varStart + 0); // length
il.Emit(OpCodes.Sizeof, elementType);
il.Emit(OpCodes.Mul);
m = networkStreamType.GetMethod("ReadBytes", 2);
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Stloc, varStart + 2);
}
else
{
// int idx = 0
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc, varStart + 1); // idx
Instruction jmp3 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Br_S, jmp3);
// Deserialize item value
Instruction jmp4 = il.Create(OpCodes.Nop);
il.Append(jmp4);
GenerateSerializerType(ref context, type, serialize, elementType, il, new DotnetValueContext(varStart + 2));
// array[idx] = element
il.Emit(OpCodes.Nop);
valueContext.Load(il);
il.Emit(OpCodes.Ldloc, varStart + 1); // idx
il.Emit(OpCodes.Ldloc, varStart + 2); // <elementType>
il.Emit(OpCodes.Stelem_Ref);
// idx++
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldloc, varStart + 1); // idx
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Stloc, varStart + 1); // idx
// idx < length
il.Append(jmp3);
il.Emit(OpCodes.Ldloc, varStart + 1); // idx
il.Emit(OpCodes.Ldloc, varStart + 0); // length
il.Emit(OpCodes.Clt);
il.Emit(OpCodes.Brtrue_S, jmp4);
}
valueContext.SetProperty(il);
}
}
else if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer))
{ {
// Call NetworkStream method to write/read data // Call NetworkStream method to write/read data
MethodDefinition m; MethodDefinition m;
if (serialize) if (serialize)
{ {
il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_0); valueContext.Load(il);
if (field != null)
il.Emit(OpCodes.Ldfld, field);
else
il.Emit(propertyGetOpCode, property.GetMethod);
m = networkStreamType.GetMethod(serializer.WriteMethod); m = networkStreamType.GetMethod(serializer.WriteMethod);
} }
else else
@@ -921,10 +1272,7 @@ namespace Flax.Build.Plugins
il.Emit(OpCodes.Callvirt, module.ImportReference(m)); il.Emit(OpCodes.Callvirt, module.ImportReference(m));
if (!serialize) if (!serialize)
{ {
if (field != null) valueContext.Store(il);
il.Emit(OpCodes.Stfld, field);
else
il.Emit(propertySetOpCode, property.SetMethod);
} }
} }
else if (valueType.IsScriptingObject()) else if (valueType.IsScriptingObject())
@@ -935,11 +1283,7 @@ namespace Flax.Build.Plugins
if (serialize) if (serialize)
{ {
il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_0); valueContext.Load(il);
if (field != null)
il.Emit(OpCodes.Ldfld, field);
else
il.Emit(propertyGetOpCode, property.GetMethod);
il.Emit(OpCodes.Dup); il.Emit(OpCodes.Dup);
Instruction jmp1 = il.Create(OpCodes.Nop); Instruction jmp1 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brtrue_S, jmp1); il.Emit(OpCodes.Brtrue_S, jmp1);
@@ -957,17 +1301,16 @@ namespace Flax.Build.Plugins
} }
else else
{ {
var m = networkStreamType.GetMethod("ReadGuid");
module.GetType("System.Type", out var typeType);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
il.Emit(OpCodes.Stloc_0);
il.Emit(OpCodes.Ldarg_0);
var varStart = il.Body.Variables.Count; var varStart = il.Body.Variables.Count;
var reference = module.ImportReference(guidType); var reference = module.ImportReference(guidType);
reference.IsValueType = true; // Fix locals init to have valuetype for Guid instead of class reference.IsValueType = true; // Fix locals init to have valuetype for Guid instead of class
il.Body.Variables.Add(new VariableDefinition(reference)); il.Body.Variables.Add(new VariableDefinition(reference));
il.Body.InitLocals = true; il.Body.InitLocals = true;
var m = networkStreamType.GetMethod("ReadGuid");
module.GetType("System.Type", out var typeType);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
il.Emit(OpCodes.Stloc_S, (byte)varStart);
il.Emit(OpCodes.Ldloca_S, (byte)varStart); il.Emit(OpCodes.Ldloca_S, (byte)varStart);
il.Emit(OpCodes.Ldtoken, valueType); il.Emit(OpCodes.Ldtoken, valueType);
var getTypeFromHandle = typeType.Resolve().GetMethod("GetTypeFromHandle"); var getTypeFromHandle = typeType.Resolve().GetMethod("GetTypeFromHandle");
@@ -975,10 +1318,7 @@ namespace Flax.Build.Plugins
var tryFind = scriptingObjectType.Resolve().GetMethod("TryFind", 2); var tryFind = scriptingObjectType.Resolve().GetMethod("TryFind", 2);
il.Emit(OpCodes.Call, module.ImportReference(tryFind)); il.Emit(OpCodes.Call, module.ImportReference(tryFind));
il.Emit(OpCodes.Castclass, valueType); il.Emit(OpCodes.Castclass, valueType);
if (field != null) valueContext.Store(il);
il.Emit(OpCodes.Stfld, field);
else
il.Emit(propertySetOpCode, property.SetMethod);
} }
} }
else if (valueTypeDef.IsEnum) else if (valueTypeDef.IsEnum)
@@ -988,11 +1328,7 @@ namespace Flax.Build.Plugins
if (serialize) if (serialize)
{ {
il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_0); valueContext.Load(il);
if (field != null)
il.Emit(OpCodes.Ldfld, field);
else
il.Emit(propertyGetOpCode, property.GetMethod);
var m = networkStreamType.GetMethod("WriteUInt32"); var m = networkStreamType.GetMethod("WriteUInt32");
il.Emit(OpCodes.Callvirt, module.ImportReference(m)); il.Emit(OpCodes.Callvirt, module.ImportReference(m));
} }
@@ -1002,174 +1338,24 @@ namespace Flax.Build.Plugins
il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_1);
var m = networkStreamType.GetMethod("ReadUInt32"); var m = networkStreamType.GetMethod("ReadUInt32");
il.Emit(OpCodes.Callvirt, module.ImportReference(m)); il.Emit(OpCodes.Callvirt, module.ImportReference(m));
if (field != null) valueContext.Store(il);
il.Emit(OpCodes.Stfld, field);
else
il.Emit(propertySetOpCode, property.SetMethod);
} }
} }
else if (valueType.IsValueType) else if (valueType.IsValueType)
{ {
// Invoke structure generated serializer // Invoke structure generated serializer
// TODO: check if this type has generated serialization code valueContext.LoadAddress(il);
il.Emit(OpCodes.Ldarg_0);
if (field != null)
il.Emit(OpCodes.Ldflda, field);
else
il.Emit(propertyGetOpCode, property.GetMethod);
il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_1);
var m = valueTypeDef.GetMethod(serialize ? Thunk1 : Thunk2); var m = valueTypeDef.GetMethod(serialize ? Thunk1 : Thunk2);
il.Emit(OpCodes.Call, module.ImportReference(m)); il.Emit(OpCodes.Call, module.ImportReference(m));
} }
else if (valueType.IsArray && valueType.GetElementType().IsValueType)
{
// TODO: support any array type by iterating over elements (separate serialize for each one)
var elementType = valueType.GetElementType();
var varStart = il.Body.Variables.Count;
module.GetType("System.Int32", out var intType);
il.Body.Variables.Add(new VariableDefinition(intType));
il.Body.Variables.Add(new VariableDefinition(new PointerType(elementType)));
il.Body.Variables.Add(new VariableDefinition(new PinnedType(valueType)));
il.Body.InitLocals = true;
if (serialize)
{
// <elementType>[] array2 = Array1;
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
if (field != null)
il.Emit(OpCodes.Ldfld, field);
else
il.Emit(propertyGetOpCode, property.GetMethod);
// int num2 = ((array2 != null) ? array2.Length : 0);
il.Emit(OpCodes.Dup);
Instruction jmp1 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brtrue_S, jmp1);
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldc_I4_0);
Instruction jmp2 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Br_S, jmp2);
il.Append(jmp1);
il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Conv_I4);
il.Append(jmp2);
il.Emit(OpCodes.Stloc, varStart + 0);
// stream.WriteInt32(num2);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldloc, varStart + 0);
var m = networkStreamType.GetMethod("WriteInt32");
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
// fixed (<elementType>* bytes2 = Array1)
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
if (field != null)
il.Emit(OpCodes.Ldfld, field);
else
il.Emit(propertyGetOpCode, property.GetMethod);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, varStart + 2);
Instruction jmp3 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brfalse_S, jmp3);
il.Emit(OpCodes.Ldloc_2);
il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Conv_I4);
Instruction jmp4 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brtrue_S, jmp4);
il.Append(jmp3);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Conv_U);
il.Emit(OpCodes.Stloc, varStart + 1);
Instruction jmp5 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Br_S, jmp5);
// stream.WriteBytes((byte*)bytes, num * sizeof(<elementType>)));
il.Append(jmp4);
il.Emit(OpCodes.Ldloc, varStart + 2);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldelema, elementType);
il.Emit(OpCodes.Conv_U);
il.Emit(OpCodes.Stloc, varStart + 1);
il.Append(jmp5);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldloc, varStart + 1);
il.Emit(OpCodes.Ldloc, varStart + 0);
il.Emit(OpCodes.Sizeof, elementType);
il.Emit(OpCodes.Mul);
m = networkStreamType.GetMethod("WriteBytes", 2);
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Stloc, varStart + 2);
}
else
{
if (field == null)
throw new NotImplementedException("TODO: add support for array property replication");
// int num = stream.ReadInt32();
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_1);
var m = networkStreamType.GetMethod("ReadInt32");
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
il.Emit(OpCodes.Stloc, varStart + 0);
// System.Array.Resize(ref Array1, num);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldflda, field);
il.Emit(OpCodes.Ldloc, varStart + 0);
module.TryGetTypeReference("System.Array", out var arrayType);
m = arrayType.Resolve().GetMethod("Resize", 2);
il.Emit(OpCodes.Call, module.ImportReference(m.InflateGeneric(elementType)));
// fixed (int* buffer = Array1)
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, field);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, varStart + 2);
Instruction jmp1 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brfalse_S, jmp1);
il.Emit(OpCodes.Ldloc, varStart + 2);
il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Conv_I4);
Instruction jmp2 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brtrue_S, jmp2);
il.Append(jmp1);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Conv_U);
il.Emit(OpCodes.Stloc, varStart + 1);
Instruction jmp3 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Br_S, jmp3);
// stream.ReadBytes((byte*)buffer, num * sizeof(<elementType>));
il.Append(jmp2);
il.Emit(OpCodes.Ldloc, varStart + 2);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldelema, elementType);
il.Emit(OpCodes.Conv_U);
il.Emit(OpCodes.Stloc, varStart + 1);
il.Append(jmp3);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldloc, varStart + 1);
il.Emit(OpCodes.Ldloc, varStart + 0);
il.Emit(OpCodes.Sizeof, elementType);
il.Emit(OpCodes.Mul);
m = networkStreamType.GetMethod("ReadBytes", 2);
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Stloc, varStart + 2);
}
}
else else
{ {
// Unknown type // Unknown type
if (property != null) if (valueContext.Property != null)
MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {property.Name} in {type.FullName} for automatic replication.", property); MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {valueContext.Property.Name} in {type.FullName} for automatic replication.", valueContext.Property);
else if (field != null) else if (valueContext.Field != null)
MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {field.Name} in {type.FullName} for automatic replication.", field.Resolve()); MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {valueContext.Field.Name} in {type.FullName} for automatic replication.", valueContext.Field.Resolve());
else else
MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' for automatic replication."); MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' for automatic replication.");
context.Failed = true; context.Failed = true;
@@ -1184,7 +1370,13 @@ namespace Flax.Build.Plugins
// Ensure to have valid serialization already generated for that value type // Ensure to have valid serialization already generated for that value type
GenerateTypeSerialization(ref context, valueTypeDef); GenerateTypeSerialization(ref context, valueTypeDef);
if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) if (type.IsArray)
{
// TODO: refactor network stream read/write to share code between replication and rpcs
Log.Error($"Not supported type '{valueType.FullName}' for RPC parameter in {type.FullName}.");
context.Failed = true;
}
else if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer))
{ {
// Call NetworkStream method to write/read data // Call NetworkStream method to write/read data
if (serialize) if (serialize)
@@ -1402,6 +1594,22 @@ namespace Flax.Build.Plugins
{ {
var parameter = method.Parameters[i]; var parameter = method.Parameters[i];
var parameterType = parameter.ParameterType; var parameterType = parameter.ParameterType;
// Special handling of Rpc Params
if (string.Equals(parameterType.FullName, "FlaxEngine.Networking.NetworkRpcParams", StringComparison.OrdinalIgnoreCase))
{
// new NetworkRpcParams { SenderId = networkStream.SenderId }
il.Emit(OpCodes.Ldloca_S, (byte)(argsStart + i));
il.Emit(OpCodes.Initobj, parameterType);
il.Emit(OpCodes.Ldloca_S, (byte)(argsStart + i));
il.Emit(OpCodes.Ldloc_1);
var getSenderId = networkStreamType.Resolve().GetMethod("get_SenderId");
il.Emit(OpCodes.Callvirt, module.ImportReference(getSenderId));
var senderId = parameterType.Resolve().GetField("SenderId");
il.Emit(OpCodes.Stfld, module.ImportReference(senderId));
continue;
}
GenerateDotNetRPCSerializerType(ref context, type, false, argsStart + i, parameterType, il, networkStream.Resolve(), 1, null); GenerateDotNetRPCSerializerType(ref context, type, false, argsStart + i, parameterType, il, networkStream.Resolve(), 1, null);
} }
@@ -1431,6 +1639,8 @@ namespace Flax.Build.Plugins
il.Body.InitLocals = true; il.Body.InitLocals = true;
var varsStart = il.Body.Variables.Count; var varsStart = il.Body.Variables.Count;
il.InsertBefore(ilStart, il.Create(OpCodes.Nop));
// Is Server/Is Client boolean constants // Is Server/Is Client boolean constants
il.Body.Variables.Add(new VariableDefinition(module.ImportReference(boolType))); // [0] il.Body.Variables.Add(new VariableDefinition(module.ImportReference(boolType))); // [0]
il.Body.Variables.Add(new VariableDefinition(module.ImportReference(boolType))); // [1] il.Body.Variables.Add(new VariableDefinition(module.ImportReference(boolType))); // [1]
@@ -1475,14 +1685,25 @@ namespace Flax.Build.Plugins
il.InsertBefore(ilStart, il.Create(OpCodes.Stloc, streamLocalIndex)); // stream loc=3 il.InsertBefore(ilStart, il.Create(OpCodes.Stloc, streamLocalIndex)); // stream loc=3
// Serialize all RPC parameters // Serialize all RPC parameters
var targetIdsArgIndex = -1;
FieldDefinition targetIdsField = null;
for (int i = 0; i < method.Parameters.Count; i++) for (int i = 0; i < method.Parameters.Count; i++)
{ {
var parameter = method.Parameters[i]; var parameter = method.Parameters[i];
var parameterType = parameter.ParameterType; var parameterType = parameter.ParameterType;
// Special handling of Rpc Params
if (string.Equals(parameterType.FullName, "FlaxEngine.Networking.NetworkRpcParams", StringComparison.OrdinalIgnoreCase))
{
targetIdsArgIndex = i + 1; // NetworkRpcParams value argument index (starts at 1, 0 holds this)
targetIdsField = parameterType.Resolve().GetField("TargetIds");
continue;
}
GenerateDotNetRPCSerializerType(ref context, type, true, i + 1, parameterType, il, networkStream.Resolve(), streamLocalIndex, ilStart); GenerateDotNetRPCSerializerType(ref context, type, true, i + 1, parameterType, il, networkStream.Resolve(), streamLocalIndex, ilStart);
} }
// NetworkReplicator.EndInvokeRPC(this, typeof(<type>), "<name>", stream); // NetworkReplicator.EndInvokeRPC(this, typeof(<type>), "<name>", stream, targetIds);
il.InsertBefore(ilStart, il.Create(OpCodes.Nop)); il.InsertBefore(ilStart, il.Create(OpCodes.Nop));
il.InsertBefore(ilStart, il.Create(OpCodes.Ldarg_0)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldarg_0));
il.InsertBefore(ilStart, il.Create(OpCodes.Ldtoken, type)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldtoken, type));
@@ -1491,7 +1712,14 @@ namespace Flax.Build.Plugins
il.InsertBefore(ilStart, il.Create(OpCodes.Call, module.ImportReference(getTypeFromHandle))); il.InsertBefore(ilStart, il.Create(OpCodes.Call, module.ImportReference(getTypeFromHandle)));
il.InsertBefore(ilStart, il.Create(OpCodes.Ldstr, method.Name)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldstr, method.Name));
il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, streamLocalIndex)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, streamLocalIndex));
var endInvokeRPC = networkReplicatorType.Resolve().GetMethod("EndInvokeRPC", 4); if (targetIdsArgIndex != -1)
{
il.InsertBefore(ilStart, il.Create(OpCodes.Ldarg, targetIdsArgIndex));
il.InsertBefore(ilStart, il.Create(OpCodes.Ldfld, module.ImportReference(targetIdsField)));
}
else
il.InsertBefore(ilStart, il.Create(OpCodes.Ldnull));
var endInvokeRPC = networkReplicatorType.Resolve().GetMethod("EndInvokeRPC", 5);
il.InsertBefore(ilStart, il.Create(OpCodes.Call, module.ImportReference(endInvokeRPC))); il.InsertBefore(ilStart, il.Create(OpCodes.Call, module.ImportReference(endInvokeRPC)));
// if (server && networkMode == NetworkManagerMode.Client) return; // if (server && networkMode == NetworkManagerMode.Client) return;

View File

@@ -221,4 +221,16 @@
<DisplayString Condition="Index != 0">Tag={TagsListDebug[Index - 1]}</DisplayString> <DisplayString Condition="Index != 0">Tag={TagsListDebug[Index - 1]}</DisplayString>
</Type> </Type>
<!-- Span<T> -->
<Type Name="Span&lt;*&gt;">
<DisplayString>{{ Length={_length} }}</DisplayString>
<Expand>
<Item Name="[Length]" ExcludeView="simple">_length</Item>
<ArrayItems Condition="_data != nullptr">
<Size>_length</Size>
<ValuePointer>_data</ValuePointer>
</ArrayItems>
</Expand>
</Type>
</AutoVisualizer> </AutoVisualizer>