Merge remote-tracking branch 'origin/master' into 1.6
This commit is contained in:
BIN
Content/Editor/Camera/O_Camera.flax
(Stored with Git LFS)
BIN
Content/Editor/Camera/O_Camera.flax
(Stored with Git LFS)
Binary file not shown.
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ struct NetworkConnection;
|
|||||||
struct NetworkMessage;
|
struct NetworkMessage;
|
||||||
struct NetworkConfig;
|
struct NetworkConfig;
|
||||||
struct NetworkDriverStats;
|
struct NetworkDriverStats;
|
||||||
|
struct NetworkRpcParams;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -987,7 +987,7 @@ namespace FlaxEngine.GUI
|
|||||||
{
|
{
|
||||||
// Hide tooltip
|
// Hide tooltip
|
||||||
Tooltip?.Hide();
|
Tooltip?.Hide();
|
||||||
Root.DoDragDrop(data);
|
Root?.DoDragDrop(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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<*>">
|
||||||
|
<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>
|
||||||
|
|||||||
Reference in New Issue
Block a user