From a7c539764166439b49d37bc0ab043dd1601d1a4b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 1 Mar 2023 22:47:52 +0100 Subject: [PATCH 01/17] Fix content window auto scrolling regression 656fcf984721bed3d95b4f8249a416f2e85c04ce --- Source/Editor/Windows/ContentWindow.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 162382f03..0323b9730 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -325,10 +325,10 @@ namespace FlaxEditor.Windows Select(item, true); // Disable scrolling in content view - if (_split.Panel2.VScrollBar != null) - _split.Panel2.VScrollBar.ThumbEnabled = false; - if (_split.Panel2.HScrollBar != null) - _split.Panel2.HScrollBar.ThumbEnabled = false; + if (_contentViewPanel.VScrollBar != null) + _contentViewPanel.VScrollBar.ThumbEnabled = false; + if (_contentViewPanel.HScrollBar != null) + _contentViewPanel.HScrollBar.ThumbEnabled = false; ScrollingOnContentView(false); // Show rename popup @@ -339,10 +339,10 @@ namespace FlaxEditor.Windows popup.Closed += renamePopup => { // Restore scrolling in content view - if (_split.Panel2.VScrollBar != null) - _split.Panel2.VScrollBar.ThumbEnabled = true; - if (_split.Panel2.HScrollBar != null) - _split.Panel2.HScrollBar.ThumbEnabled = true; + if (_contentViewPanel.VScrollBar != null) + _contentViewPanel.VScrollBar.ThumbEnabled = true; + if (_contentViewPanel.HScrollBar != null) + _contentViewPanel.HScrollBar.ThumbEnabled = true; ScrollingOnContentView(true); // Check if was creating new element @@ -760,7 +760,7 @@ namespace FlaxEditor.Windows // Select and scroll to cover in view _view.Select(item); - _split.Panel2.ScrollViewTo(item, fastScroll); + _contentViewPanel.ScrollViewTo(item, fastScroll); // Focus _view.Focus(); From 38c027704ece4ea7ff9735982290492573edf440 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 3 Mar 2023 12:31:13 -0600 Subject: [PATCH 02/17] Better looking state machine states. --- .../Archetypes/Animation.StateMachine.cs | 93 ++++++++++++++++++- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index d1d8cbea1..69125c81e 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -288,6 +288,8 @@ namespace FlaxEditor.Surface.Archetypes private bool _isMouseDown; private Rectangle _textRect; private Rectangle _dragAreaRect; + private bool _cursorChanged = false; + private bool _textRectHovered = false; /// /// Gets or sets the first state node identifier for the state machine pointed by the entry node. @@ -336,7 +338,8 @@ namespace FlaxEditor.Surface.Archetypes var width = Mathf.Max(100, titleSize.X + 50); Resize(width, 0); titleSize.X += 8.0f; - _dragAreaRect = new Rectangle((Size - titleSize) * 0.5f, titleSize); + var padding = new Float2(8, 8); + _dragAreaRect = new Rectangle(padding, Size - padding * 2); } /// @@ -345,10 +348,19 @@ namespace FlaxEditor.Surface.Archetypes var style = Style.Current; // Paint Background - BackgroundColor = _isSelected ? Color.OrangeRed : style.BackgroundNormal; + if (_isSelected) + Render2D.DrawRectangle(_textRect, Color.Orange); + + BackgroundColor = style.BackgroundNormal; + var dragAreaColor = BackgroundColor / 2.0f; + if (IsMouseOver) BackgroundColor *= 1.2f; + if (_textRectHovered) + BackgroundColor *= 1.2f; + Render2D.FillRectangle(_textRect, BackgroundColor); + Render2D.FillRectangle(_dragAreaRect, dragAreaColor); // Push clipping mask if (ClipChildren) @@ -382,6 +394,7 @@ namespace FlaxEditor.Surface.Archetypes { _isMouseDown = true; Cursor = CursorType.Hand; + _cursorChanged = true; Focus(); return true; } @@ -399,6 +412,7 @@ namespace FlaxEditor.Surface.Archetypes { _isMouseDown = false; Cursor = CursorType.Default; + _cursorChanged = false; Surface.ConnectingEnd(this); } @@ -412,6 +426,25 @@ namespace FlaxEditor.Surface.Archetypes public override void OnMouseMove(Float2 location) { Surface.ConnectingOver(this); + if (_dragAreaRect.Contains(location) && !_cursorChanged && !Input.GetMouseButton(MouseButton.Left)) + { + Cursor = CursorType.SizeAll; + _cursorChanged = true; + } + else if (!_dragAreaRect.Contains(location) && _cursorChanged) + { + Cursor = CursorType.Default; + _cursorChanged = false; + } + + if (_textRect.Contains(location) && !_dragAreaRect.Contains(location) && !_textRectHovered) + { + _textRectHovered = true; + } + else if (_textRectHovered && (!_textRect.Contains(location) || _dragAreaRect.Contains(location))) + { + _textRectHovered = false; + } base.OnMouseMove(location); } @@ -420,6 +453,15 @@ namespace FlaxEditor.Surface.Archetypes { base.OnMouseLeave(); + if (_cursorChanged) + { + Cursor = CursorType.Default; + _cursorChanged = false; + } + + if (_textRectHovered) + _textRectHovered = false; + if (_isMouseDown) { _isMouseDown = false; @@ -606,6 +648,8 @@ namespace FlaxEditor.Surface.Archetypes private Rectangle _textRect; private Rectangle _dragAreaRect; private Rectangle _renameButtonRect; + private bool _cursorChanged = false; + private bool _textRectHovered = false; /// /// The transitions list from this state to the others. @@ -711,7 +755,8 @@ namespace FlaxEditor.Surface.Archetypes var width = Mathf.Max(100, titleSize.X + 50); Resize(width, 0); titleSize.X += 8.0f; - _dragAreaRect = new Rectangle((Size - titleSize) * 0.5f, titleSize); + var padding = new Float2(8, 8); + _dragAreaRect = new Rectangle(padding, Size - padding * 2); } /// @@ -1138,10 +1183,19 @@ namespace FlaxEditor.Surface.Archetypes var style = Style.Current; // Paint Background - BackgroundColor = _isSelected ? Color.OrangeRed : style.BackgroundNormal; + if (_isSelected) + Render2D.DrawRectangle(_textRect, Color.Orange); + + BackgroundColor = style.BackgroundNormal; + var dragAreaColor = BackgroundColor / 2.0f; + if (IsMouseOver) BackgroundColor *= 1.2f; + if (_textRectHovered) + BackgroundColor *= 1.2f; + Render2D.FillRectangle(_textRect, BackgroundColor); + Render2D.FillRectangle(_dragAreaRect, dragAreaColor); // Push clipping mask if (ClipChildren) @@ -1194,6 +1248,7 @@ namespace FlaxEditor.Surface.Archetypes { _isMouseDown = true; Cursor = CursorType.Hand; + _cursorChanged = true; Focus(); return true; } @@ -1211,6 +1266,7 @@ namespace FlaxEditor.Surface.Archetypes { _isMouseDown = false; Cursor = CursorType.Default; + _cursorChanged = false; Surface.ConnectingEnd(this); } @@ -1231,6 +1287,26 @@ namespace FlaxEditor.Surface.Archetypes public override void OnMouseMove(Float2 location) { Surface.ConnectingOver(this); + if (_dragAreaRect.Contains(location) && !_cursorChanged && !_renameButtonRect.Contains(location) && !_closeButtonRect.Contains(location) && !Input.GetMouseButton(MouseButton.Left)) + { + Cursor = CursorType.SizeAll; + _cursorChanged = true; + } + else if ((!_dragAreaRect.Contains(location) || _renameButtonRect.Contains(location) || _closeButtonRect.Contains(location)) && _cursorChanged) + { + Cursor = CursorType.Default; + _cursorChanged = false; + } + + if (_textRect.Contains(location) && !_dragAreaRect.Contains(location) && !_textRectHovered) + { + _textRectHovered = true; + } + else if (_textRectHovered && (!_textRect.Contains(location) || _dragAreaRect.Contains(location))) + { + _textRectHovered = false; + } + base.OnMouseMove(location); } @@ -1239,6 +1315,15 @@ namespace FlaxEditor.Surface.Archetypes { base.OnMouseLeave(); + if (_cursorChanged) + { + Cursor = CursorType.Default; + _cursorChanged = false; + } + + if (_textRectHovered) + _textRectHovered = false; + if (_isMouseDown) { _isMouseDown = false; From 70e9410da03207c5f40e28845249df9cf2dc3ec0 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 3 Mar 2023 13:25:53 -0600 Subject: [PATCH 03/17] Changed color of tangents when editing a spline to help the user know where the tangents are. --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 37a0f9b9a..d159a6251 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -178,13 +178,13 @@ namespace FlaxEditor.SceneGraph.Actors // Draw tangent points if (tangentIn != pos) { - DebugDraw.DrawLine(pos, tangentIn, Color.White.AlphaMultiplied(0.6f), 0, false); - DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, 4.0f), Color.White, 0, false); + DebugDraw.DrawLine(pos, tangentIn, Color.Blue.AlphaMultiplied(0.6f), 0, false); + DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, 4.0f), Color.Blue, 0, false); } if (tangentIn != pos) { - DebugDraw.DrawLine(pos, tangentOut, Color.White.AlphaMultiplied(0.6f), 0, false); - DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, 4.0f), Color.White, 0, false); + DebugDraw.DrawLine(pos, tangentOut, Color.Red.AlphaMultiplied(0.6f), 0, false); + DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, 4.0f), Color.Red, 0, false); } } From e6b0cd69aeaa55c28e0761a542553abea4ef19a9 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 3 Mar 2023 13:34:24 -0600 Subject: [PATCH 04/17] Small bug fix --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index d159a6251..5720ee150 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -181,7 +181,7 @@ namespace FlaxEditor.SceneGraph.Actors DebugDraw.DrawLine(pos, tangentIn, Color.Blue.AlphaMultiplied(0.6f), 0, false); DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, 4.0f), Color.Blue, 0, false); } - if (tangentIn != pos) + if (tangentOut != pos) { DebugDraw.DrawLine(pos, tangentOut, Color.Red.AlphaMultiplied(0.6f), 0, false); DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, 4.0f), Color.Red, 0, false); From 57014851a7e65cc716486e88103f20651a96ee5c Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 3 Mar 2023 16:46:22 -0600 Subject: [PATCH 05/17] Fix visuals of the `Blend with Mask` node --- Source/Editor/Surface/Archetypes/Animation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index b951f5f70..a83e60deb 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -484,7 +484,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Blend with Mask", Description = "Blend animation poses using skeleton mask", Flags = NodeFlags.AnimGraph, - Size = new Float2(180, 100), + Size = new Float2(180, 140), DefaultValues = new object[] { 0.0f, @@ -496,7 +496,7 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(0, "Pose A", true, typeof(void), 1), NodeElementArchetype.Factory.Input(1, "Pose B", true, typeof(void), 2), NodeElementArchetype.Factory.Input(2, "Alpha", true, typeof(float), 3, 0), - NodeElementArchetype.Factory.Asset(100, 20, 1, typeof(SkeletonMask)), + NodeElementArchetype.Factory.Asset(0, 70, 1, typeof(SkeletonMask)), } }, new NodeArchetype From 4265ba12d7b72ad307c960a05bc001498b060c0e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 5 Mar 2023 12:04:45 -0600 Subject: [PATCH 06/17] Fixed the Generic Json Proxy not showing up in the content cm. --- Source/Editor/Content/Proxy/JsonAssetProxy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Content/Proxy/JsonAssetProxy.cs b/Source/Editor/Content/Proxy/JsonAssetProxy.cs index 679c082fa..bd663e0c6 100644 --- a/Source/Editor/Content/Proxy/JsonAssetProxy.cs +++ b/Source/Editor/Content/Proxy/JsonAssetProxy.cs @@ -22,7 +22,6 @@ namespace FlaxEditor.Content /// Json assets proxy. /// /// - [ContentContextMenu("New/Json Asset")] public abstract class JsonAssetProxy : JsonAssetBaseProxy { /// @@ -127,6 +126,7 @@ namespace FlaxEditor.Content /// Generic Json assets proxy (supports all json assets that don't have dedicated proxy). /// /// + [ContentContextMenu("New/Json Asset")] public class GenericJsonAssetProxy : JsonAssetProxy { /// From 4cf20efe2e5fbdeeb9823d832eaaa28101ea0975 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 7 Mar 2023 08:51:24 -0600 Subject: [PATCH 07/17] Add Output folder to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 61e37b2ad..8ee95cf27 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ obj/ Cache/ Binaries/ +Output/ Logs/ Source/*.csproj /Package_*/ From 95748744a4801283dfa0976bf2939b68d82b3ee3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 13 Mar 2023 14:23:23 +0100 Subject: [PATCH 08/17] Add logging missing asset type on failed load --- Source/Engine/Content/Content.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 13b3249ea..a30fa7b78 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -997,7 +997,7 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& // Get cached asset info (from registry) if (!GetAssetInfo(id, assetInfo)) { - LOG(Warning, "Invalid asset ID ({0}).", id.ToString(Guid::FormatType::N)); + LOG(Warning, "Invalid or missing asset ({0}, {1}).", id.ToString(Guid::FormatType::N), type.ToString()); return nullptr; } From cfa7cac14936946f6247ef54dcbec446cc20b53c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 13 Mar 2023 14:24:16 +0100 Subject: [PATCH 09/17] Fix crash when loading scene with script which type is not a scene object --- Source/Engine/Level/SceneObjectsFactory.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 6c3576f35..35629dc6a 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -98,6 +98,12 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, ISerializable::Deseria const ScriptingTypeHandle type = Scripting::FindScriptingType(typeName); if (type) { + // TODO: cache per-type result in Context to boost loading of the large scenes + if (!SceneObject::TypeInitializer.IsAssignableFrom(type)) + { + LOG(Warning, "Invalid scene object type {0} (inherits from: {1}).", type.ToString(true), type.GetType().GetBaseType().ToString()); + return nullptr; + } const ScriptingObjectSpawnParams params(id, type); obj = (SceneObject*)type.GetType().Script.Spawn(params); if (obj == nullptr) @@ -160,6 +166,10 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, ISerializable::Deseria void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISerializable::DeserializeStream& stream) { +#if ENABLE_ASSERTION + CHECK(obj); +#endif + // Check for prefab instance Guid prefabObjectId; if (JsonTools::GetGuidIfValid(prefabObjectId, stream, "PrefabObjectID")) From 086c2f155ddc2d3a332fdfc54032852998e18df8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 13 Mar 2023 15:39:36 +0100 Subject: [PATCH 10/17] Fix error when using nested Visject Surface context during State Machines editing in Anim Graph --- .../Archetypes/Animation.StateMachine.cs | 9 +++ Source/Editor/Surface/ISurfaceContext.cs | 5 ++ .../Editor/Surface/Undo/ConnectBoxesAction.cs | 8 +++ Source/Editor/Surface/Undo/ContextHandle.cs | 70 ++++++++++++++++++- .../Editor/Surface/VisjectSurface.Context.cs | 17 +++-- Source/Editor/Surface/VisjectSurfaceWindow.cs | 3 + .../Viewport/Previews/MaterialPreview.cs | 3 + .../Windows/AssetReferencesGraphWindow.cs | 3 + .../Assets/VisjectFunctionSurfaceWindow.cs | 3 + .../Windows/Assets/VisualScriptWindow.cs | 3 + .../Windows/Search/ContentSearchWindow.cs | 3 + 11 files changed, 120 insertions(+), 7 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index d1d8cbea1..598adc22b 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -250,6 +250,9 @@ namespace FlaxEditor.Surface.Archetypes set => Values[1] = value; } + /// + public VisjectSurfaceContext ParentContext => Context; + /// public void OnContextCreated(VisjectSurfaceContext context) { @@ -1311,6 +1314,9 @@ namespace FlaxEditor.Surface.Archetypes set => Values[1] = value; } + /// + public VisjectSurfaceContext ParentContext => Context; + /// public void OnContextCreated(VisjectSurfaceContext context) { @@ -1682,6 +1688,9 @@ namespace FlaxEditor.Surface.Archetypes set => RuleGraph = value; } + /// + public VisjectSurfaceContext ParentContext => SourceState.Context; + /// public void OnContextCreated(VisjectSurfaceContext context) { diff --git a/Source/Editor/Surface/ISurfaceContext.cs b/Source/Editor/Surface/ISurfaceContext.cs index cade0b6f0..8e5f330cb 100644 --- a/Source/Editor/Surface/ISurfaceContext.cs +++ b/Source/Editor/Surface/ISurfaceContext.cs @@ -25,6 +25,11 @@ namespace FlaxEditor.Surface /// byte[] SurfaceData { get; set; } + /// + /// Gets the context which owns this surface context (null for root). + /// + VisjectSurfaceContext ParentContext { get; } + /// /// Called when Visject Surface context gets created for this surface data source. Can be used to link for some events. /// diff --git a/Source/Editor/Surface/Undo/ConnectBoxesAction.cs b/Source/Editor/Surface/Undo/ConnectBoxesAction.cs index 7fa2476cb..72b9242a4 100644 --- a/Source/Editor/Surface/Undo/ConnectBoxesAction.cs +++ b/Source/Editor/Surface/Undo/ConnectBoxesAction.cs @@ -33,6 +33,14 @@ namespace FlaxEditor.Surface.Undo CaptureConnections(iB, out _inputBefore); CaptureConnections(oB, out _outputBefore); + +#if BUILD_DEBUG + // Validate handles + if (_context.Get(_surface) != iB.ParentNode.Context) + throw new System.Exception("Invalid ContextHandle"); + if (_input.Get(iB.ParentNode.Context) != iB || _output.Get(oB.ParentNode.Context) != oB) + throw new System.Exception("Invalid BoxHandle"); +#endif } public void End() diff --git a/Source/Editor/Surface/Undo/ContextHandle.cs b/Source/Editor/Surface/Undo/ContextHandle.cs index 5206c7f16..c816511cc 100644 --- a/Source/Editor/Surface/Undo/ContextHandle.cs +++ b/Source/Editor/Surface/Undo/ContextHandle.cs @@ -1,7 +1,9 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Text; using FlaxEngine; +using FlaxEngine.Json; namespace FlaxEditor.Surface.Undo { @@ -9,7 +11,7 @@ namespace FlaxEditor.Surface.Undo /// The helper structure for Surface context handle. /// [HideInEditor] - public struct ContextHandle + public struct ContextHandle : IEquatable { private readonly string[] _path; @@ -42,6 +44,31 @@ namespace FlaxEditor.Surface.Undo } } + /// + /// Initializes a new instance of the struct. + /// + /// The child context provider. + public ContextHandle(ISurfaceContext child) + { + if (child == null) + throw new ArgumentNullException(); + VisjectSurfaceContext parent = child.ParentContext; + VisjectSurfaceContext context = parent; + int count = 1; + while (parent != null) + { + count++; + parent = parent.Parent; + } + _path = new string[count]; + _path[0] = child.SurfaceName; + for (int i = 1; i < count; i++) + { + _path[i] = context.Context.SurfaceName; + context = context.Parent; + } + } + /// /// Gets the context. /// @@ -69,5 +96,46 @@ namespace FlaxEditor.Surface.Undo return context; } + + /// + public bool Equals(ContextHandle other) + { + return JsonSerializer.ValueEquals(_path, other._path); + } + + /// + public override bool Equals(object obj) + { + return obj is ContextHandle other && Equals(other); + } + + /// + public override int GetHashCode() + { + int hash = 17; + if (_path != null) + { + unchecked + { + for (var i = 0; i < _path.Length; i++) + { + var item = _path[i]; + hash = hash * 23 + (item != null ? item.GetHashCode() : 0); + } + } + } + return hash; + } + + /// + public override string ToString() + { + var sb = new StringBuilder(); + if (_path == null) + return string.Empty; + for (int i = _path.Length - 1; i >= 0; i--) + sb.Append(_path[i]).Append('/'); + return sb.ToString(); + } } } diff --git a/Source/Editor/Surface/VisjectSurface.Context.cs b/Source/Editor/Surface/VisjectSurface.Context.cs index e3547fa52..1d8b97729 100644 --- a/Source/Editor/Surface/VisjectSurface.Context.cs +++ b/Source/Editor/Surface/VisjectSurface.Context.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; +using FlaxEditor.Surface.Undo; +using FlaxEngine; namespace FlaxEditor.Surface { @@ -9,7 +11,7 @@ namespace FlaxEditor.Surface { private VisjectSurfaceContext _root; private VisjectSurfaceContext _context; - private readonly Dictionary _contextCache = new Dictionary(); + private readonly Dictionary _contextCache = new Dictionary(); /// /// The surface context stack. @@ -54,11 +56,12 @@ namespace FlaxEditor.Surface return; // Get or create context - if (!_contextCache.TryGetValue(context, out VisjectSurfaceContext surfaceContext)) + var contextHandle = new ContextHandle(context); + if (!_contextCache.TryGetValue(contextHandle, out VisjectSurfaceContext surfaceContext)) { surfaceContext = CreateContext(_context, context); _context?.Children.Add(surfaceContext); - _contextCache.Add(context, surfaceContext); + _contextCache.Add(contextHandle, surfaceContext); context.OnContextCreated(surfaceContext); @@ -118,7 +121,8 @@ namespace FlaxEditor.Surface } // Check if has context in cache - if (_contextCache.TryGetValue(context, out VisjectSurfaceContext surfaceContext)) + var contextHandle = new ContextHandle(context); + if (_contextCache.TryGetValue(contextHandle, out VisjectSurfaceContext surfaceContext)) { // Remove from navigation path while (ContextStack.Contains(surfaceContext)) @@ -126,7 +130,7 @@ namespace FlaxEditor.Surface // Dispose surfaceContext.Clear(); - _contextCache.Remove(context); + _contextCache.Remove(contextHandle); } } @@ -147,7 +151,8 @@ namespace FlaxEditor.Surface return; // Check if already in a path - if (_contextCache.TryGetValue(context, out VisjectSurfaceContext surfaceContext) && ContextStack.Contains(surfaceContext)) + var contextHandle = new ContextHandle(context); + if (_contextCache.TryGetValue(contextHandle, out VisjectSurfaceContext surfaceContext) && ContextStack.Contains(surfaceContext)) { // Change stack do diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index 93b7a685d..0b3bc8130 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -899,6 +899,9 @@ namespace FlaxEditor.Surface /// public abstract byte[] SurfaceData { get; set; } + /// + public VisjectSurfaceContext ParentContext => null; + /// public void OnContextCreated(VisjectSurfaceContext context) { diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs index 1cfd13743..632ec199e 100644 --- a/Source/Editor/Viewport/Previews/MaterialPreview.cs +++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs @@ -366,6 +366,9 @@ namespace FlaxEditor.Viewport.Previews } } + /// + public VisjectSurfaceContext ParentContext => null; + /// void ISurfaceContext.OnContextCreated(VisjectSurfaceContext context) { diff --git a/Source/Editor/Windows/AssetReferencesGraphWindow.cs b/Source/Editor/Windows/AssetReferencesGraphWindow.cs index 6e7d3d0ef..de5b86f96 100644 --- a/Source/Editor/Windows/AssetReferencesGraphWindow.cs +++ b/Source/Editor/Windows/AssetReferencesGraphWindow.cs @@ -408,6 +408,9 @@ namespace FlaxEditor.Windows /// public byte[] SurfaceData { get; set; } + /// + public VisjectSurfaceContext ParentContext => null; + /// public void OnContextCreated(VisjectSurfaceContext context) { diff --git a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs index 2a75a76bd..7ae6ae8be 100644 --- a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs +++ b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs @@ -179,6 +179,9 @@ namespace FlaxEditor.Windows.Assets /// public abstract byte[] SurfaceData { get; set; } + /// + public VisjectSurfaceContext ParentContext => null; + /// public void OnContextCreated(VisjectSurfaceContext context) { diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 72a043eaa..6c2ec72b0 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -1147,6 +1147,9 @@ namespace FlaxEditor.Windows.Assets } } + /// + public VisjectSurfaceContext ParentContext => null; + /// public void OnContextCreated(VisjectSurfaceContext context) { diff --git a/Source/Editor/Windows/Search/ContentSearchWindow.cs b/Source/Editor/Windows/Search/ContentSearchWindow.cs index 1545fd80b..5ee3ff11c 100644 --- a/Source/Editor/Windows/Search/ContentSearchWindow.cs +++ b/Source/Editor/Windows/Search/ContentSearchWindow.cs @@ -105,6 +105,9 @@ namespace FlaxEngine.Windows.Search /// public byte[] SurfaceData { get; set; } + /// + public VisjectSurfaceContext ParentContext => null; + /// public void OnContextCreated(VisjectSurfaceContext context) { From dadb9207b9b4763422c7a5db4311be00f812e13e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 13 Mar 2023 16:00:27 +0100 Subject: [PATCH 11/17] Fix Json asset cooking to properly serialize whole asset data even if modified at runtime --- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 4 +-- Source/Engine/Content/JsonAsset.cpp | 36 +++++++++++++------ Source/Engine/Content/JsonAsset.h | 11 ++++-- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index 92ebf39f6..85f4a4126 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -327,8 +327,8 @@ bool CookAssetsStep::ProcessDefaultAsset(AssetCookData& options) { // Use compact json rapidjson_flax::StringBuffer buffer; - rapidjson_flax::Writer writer(buffer); - asJsonAsset->Document.Accept(writer); + CompactJsonWriter writerObj(buffer); + asJsonAsset->Save(writerObj); // Store json data in the first chunk auto chunk = New(); diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 1f0e914f2..86880e56b 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -146,7 +146,7 @@ void JsonAssetBase::GetReferences(const StringAnsiView& json, Array& outpu FindIds(document, output); } -bool JsonAssetBase::Save(const StringView& path) +bool JsonAssetBase::Save(const StringView& path) const { // Validate state if (WaitForLoaded()) @@ -160,11 +160,32 @@ bool JsonAssetBase::Save(const StringView& path) return true; } ScopeLock lock(Locker); - - // Serialize to json file + + // Serialize to json to the buffer rapidjson_flax::StringBuffer buffer; PrettyJsonWriter writerObj(buffer); - JsonWriter& writer = writerObj; + Save(writerObj); + + // Save json to file + if (File::WriteAllBytes(path.HasChars() ? path : StringView(GetPath()), (byte*)buffer.GetString(), (int32)buffer.GetSize())) + { + LOG(Error, "Cannot save \'{0}\'", ToString()); + return true; + } + + return false; +} + +bool JsonAssetBase::Save(JsonWriter& writer) const +{ + // Validate state + if (WaitForLoaded()) + { + LOG(Error, "Asset loading failed. Cannot save it."); + return true; + } + ScopeLock lock(Locker); + writer.StartObject(); { // Json resource header @@ -183,13 +204,6 @@ bool JsonAssetBase::Save(const StringView& path) } writer.EndObject(); - // Save json to file - if (File::WriteAllBytes(path.HasChars() ? path : StringView(GetPath()), (byte*)buffer.GetString(), (int32)buffer.GetSize())) - { - LOG(Error, "Cannot save \'{0}\'", ToString()); - return true; - } - return false; } diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h index ca52ac86b..c53649d33 100644 --- a/Source/Engine/Content/JsonAsset.h +++ b/Source/Engine/Content/JsonAsset.h @@ -32,7 +32,7 @@ public: ISerializable::SerializeDocument Document; /// - /// The data node (reference from Document). + /// The data node (reference from Document or Document itself). /// ISerializable::DeserializeStream* Data; @@ -78,7 +78,14 @@ public: /// /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. /// True if cannot save data, otherwise false. - API_FUNCTION() bool Save(const StringView& path = StringView::Empty); + API_FUNCTION() bool Save(const StringView& path = StringView::Empty) const; + + /// + /// Saves this asset to the Json Writer buffer (both ID, Typename header and Data contents). Supported only in Editor. + /// + /// The output Json Writer to write asset. + /// True if cannot save data, otherwise false. + bool Save(JsonWriter& writer) const; #endif protected: From a41ad511f00b5a42d7e75f0d19a2a28d3705a40d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 13 Mar 2023 16:10:21 +0100 Subject: [PATCH 12/17] Fix Visual Studio project names collision when using both C++ and C# scripting --- Source/Tools/Flax.Build/Build/Builder.Projects.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index 0d25aa3d1..ade17fd59 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -369,7 +369,7 @@ namespace Flax.Build var project = dotNetProjectGenerator.CreateProject(); project.Type = TargetType.DotNet; project.Name = project.BaseName = binaryModuleName; - if (mainSolutionProject != null && projectInfo == rootProject) + if (projects.Any(p => p.Type != TargetType.DotNet && p.BaseName == binaryModuleName)) project.Name += ".CSharp"; // Prevent overlapping name with native code project project.OutputType = TargetOutputType.Library; project.Targets = targets; From d2a0438b713b901fd1b5c1b56ca9c33c72ad33ab Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 13 Mar 2023 20:06:45 +0100 Subject: [PATCH 13/17] Fix error on control reorder while it got remove before --- Source/Engine/UI/GUI/ContainerControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index 365889cbf..d5a7e89d3 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -264,7 +264,7 @@ namespace FlaxEngine.GUI internal void ChangeChildIndex(Control child, int newIndex) { int oldIndex = _children.IndexOf(child); - if (oldIndex == newIndex) + if (oldIndex == newIndex || oldIndex == -1) return; _children.RemoveAt(oldIndex); From 6f304040b02497fba0268ac29c6c6e426d42a256 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 13 Mar 2023 20:20:10 +0100 Subject: [PATCH 14/17] Format code --- .../Build/Plugins/NetworkingPlugin.cs | 86 +++++++++++++------ 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index fcc1ca83b..5dccfccc4 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -53,6 +53,7 @@ namespace Flax.Build.Plugins internal const string NetworkRpc = "NetworkRpc"; private const string Thunk1 = "INetworkSerializable_Serialize"; private const string Thunk2 = "INetworkSerializable_Deserialize"; + private static readonly Dictionary _inBuildSerializers = new Dictionary() { { "System.Boolean", new InBuildSerializer("WriteBoolean", "ReadBoolean") }, @@ -127,6 +128,7 @@ namespace Flax.Build.Plugins { fields = structInfo.Fields; } + bool useReplication = false, useRpc = false; if (fields != null) { @@ -139,6 +141,7 @@ namespace Flax.Build.Plugins } } } + if (properties != null) { foreach (var propertyInfo in properties) @@ -150,6 +153,7 @@ namespace Flax.Build.Plugins } } } + if (functions != null) { foreach (var functionInfo in functions) @@ -161,20 +165,22 @@ namespace Flax.Build.Plugins } } } + if (useReplication) { typeInfo.SetTag(NetworkReplicated, string.Empty); - + // Generate C++ wrapper functions to serialize/deserialize type BindingsGenerator.CppIncludeFiles.Add("Engine/Networking/NetworkReplicator.h"); BindingsGenerator.CppIncludeFiles.Add("Engine/Networking/NetworkStream.h"); OnGenerateCppTypeSerialize(buildData, typeInfo, contents, fields, properties, true); OnGenerateCppTypeSerialize(buildData, typeInfo, contents, fields, properties, false); } + if (useRpc) { typeInfo.SetTag(NetworkRpc, string.Empty); - + // Generate C++ wrapper functions to invoke/execute RPC BindingsGenerator.CppIncludeFiles.Add("Engine/Networking/NetworkStream.h"); BindingsGenerator.CppIncludeFiles.Add("Engine/Networking/NetworkReplicator.h"); @@ -282,7 +288,7 @@ namespace Flax.Build.Plugins // Replicate base type OnGenerateCppWriteSerializer(contents, classStructInfo.BaseType.NativeName, "obj", serialize); } - + // Replicate all marked fields and properties if (fields != null) { @@ -293,6 +299,7 @@ namespace Flax.Build.Plugins OnGenerateCppTypeSerializeData(buildData, typeInfo, contents, fieldInfo.Type, $"obj.{fieldInfo.Name}", serialize); } } + if (properties != null) { foreach (var propertyInfo in properties) @@ -308,6 +315,7 @@ namespace Flax.Build.Plugins } } } + contents.AppendLine(" }"); contents.AppendLine(); } @@ -326,9 +334,10 @@ namespace Flax.Build.Plugins if (apiType != null) return IsRawPOD(buildData, apiType); } + return false; } - + private void OnGenerateCppTypeSerializeData(Builder.BuildData buildData, ApiTypeInfo caller, StringBuilder contents, TypeInfo type, string name, bool serialize) { var apiType = BindingsGenerator.FindApiTypeInfo(buildData, type, caller); @@ -365,7 +374,7 @@ namespace Flax.Build.Plugins throw new Exception($"Invalid pointer type '{type}' that cannot be serialized for replication of {caller.Name}."); if (type.IsRef) throw new Exception($"Invalid reference type '{type}' that cannot be serialized for replication of {caller.Name}."); - + // Structure serializer OnGenerateCppWriteSerializer(contents, apiType.NativeName, name, serialize); } @@ -375,13 +384,13 @@ namespace Flax.Build.Plugins OnGenerateCppWriteRaw(contents, name, serialize); } } - + private void OnGenerateCppWriteRaw(StringBuilder contents, string data, bool serialize) { var method = serialize ? "Write" : "Read"; contents.AppendLine($" stream->{method}({data});"); } - + private void OnGenerateCppWriteSerializer(StringBuilder contents, string type, string data, bool serialize) { if (type == "ScriptingObject" || type == "Script" || type == "Actor") @@ -405,6 +414,7 @@ namespace Flax.Build.Plugins // Register generated serializer functions contents.AppendLine($" NetworkReplicator::AddSerializer(ScriptingTypeHandle({typeNameNative}::TypeInitializer), {typeNameInternal}Internal::INetworkSerializable_Serialize, {typeNameInternal}Internal::INetworkSerializable_Deserialize);"); } + if (rpcTag != null) { // Register generated RPCs @@ -413,6 +423,7 @@ namespace Flax.Build.Plugins { functions = classInfo.Functions; } + if (functions != null) { foreach (var functionInfo in functions) @@ -430,16 +441,15 @@ namespace Flax.Build.Plugins // Skip types that don't use networking if (typeInfo.GetTag(NetworkReplicated) == null) return; - + if (typeInfo is ClassInfo) return; // Generate C# wrapper functions to serialize/deserialize type directly from managed code OnGenerateCSharpTypeSerialize(buildData, typeInfo, contents, indent, true); OnGenerateCSharpTypeSerialize(buildData, typeInfo, contents, indent, false); - } - + private void OnGenerateCSharpTypeSerialize(Builder.BuildData buildData, ApiTypeInfo typeInfo, StringBuilder contents, string indent, bool serialize) { var mode = serialize ? "true" : "false"; @@ -463,6 +473,7 @@ namespace Flax.Build.Plugins contents.Append(indent).AppendLine($" throw new NotImplementedException(\"Not supported native structure with references used in managed code for replication.\");"); } } + contents.Append(indent).AppendLine("}"); } @@ -493,7 +504,7 @@ namespace Flax.Build.Plugins private void OnPatchDotNetAssembly(Builder.BuildData buildData, NativeCpp.BuildOptions buildOptions, Task buildTask, string assemblyPath) { using (DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver()) - using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters{ ReadWrite = true, ReadSymbols = true, AssemblyResolver = assemblyResolver })) + using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters { ReadWrite = true, ReadSymbols = true, AssemblyResolver = assemblyResolver })) { // Setup module search locations var searchDirectories = new HashSet(); @@ -503,6 +514,7 @@ namespace Flax.Build.Plugins if (file.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) searchDirectories.Add(Path.GetDirectoryName(file)); } + foreach (var e in searchDirectories) assemblyResolver.AddSearchDirectory(e); @@ -554,6 +566,7 @@ namespace Flax.Build.Plugins } } } + var isNetworkReplicated = false; foreach (FieldDefinition f in type.Fields) { @@ -562,6 +575,7 @@ namespace Flax.Build.Plugins isNetworkReplicated = true; break; } + foreach (PropertyDefinition p in type.Properties) { if (!p.HasAttribute(NetworkReplicatedAttribute)) @@ -569,6 +583,7 @@ namespace Flax.Build.Plugins isNetworkReplicated = true; break; } + if (type.IsValueType) { if (isINetworkSerializable) @@ -597,6 +612,7 @@ namespace Flax.Build.Plugins modified = true; } } + if (failed) throw new Exception($"Failed to generate network replication for assembly {assemblyPath}"); if (!modified) @@ -650,6 +666,7 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Newobj, module.ImportReference(serializeFuncCtor)); il.Emit(OpCodes.Call, module.ImportReference(addSerializer)); } + foreach (var e in methodRPCs) { // NetworkReplicator.AddRPC(typeof(), "", _Execute, , , ); @@ -664,13 +681,14 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Ldc_I4, e.Channel); il.Emit(OpCodes.Call, module.ImportReference(addRPC)); } + il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ret); c.Methods.Add(m); } // Serialize assembly back to the file - assembly.Write(new WriterParameters { WriteSymbols = true } ); + assembly.Write(new WriterParameters { WriteSymbols = true }); } } @@ -696,7 +714,7 @@ namespace Flax.Build.Plugins TypeDefinition networkStream = networkStreamType.Resolve(); ILProcessor il = m.Body.GetILProcessor(); il.Emit(OpCodes.Nop); - + // Serialize base type if (type.BaseType != null && type.BaseType.FullName != "System.ValueType" && type.BaseType.FullName != "FlaxEngine.Object" && type.BaseType.CanBeResolved()) { @@ -747,14 +765,14 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Call, module.ImportReference(fromUnmanagedPtr)); il.Emit(OpCodes.Castclass, type); il.Emit(OpCodes.Stloc_0); - + // NetworkStream stream = (NetworkStream)FlaxEngine.Object.FromUnmanagedPtr(streamPtr) il.Body.Variables.Add(new VariableDefinition(networkStream)); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Call, module.ImportReference(fromUnmanagedPtr)); il.Emit(OpCodes.Castclass, module.ImportReference(networkStream)); il.Emit(OpCodes.Stloc_1); - + // Generate normal serializer var serializer = GenerateSerializer(type, serialize, ref failed, name, voidType, networkStreamType); @@ -810,17 +828,20 @@ namespace Flax.Build.Plugins 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); failed = true; return; } + if (property.GetMethod.IsVirtual) propertyGetOpCode = OpCodes.Callvirt; if (property.SetMethod.IsVirtual) propertySetOpCode = OpCodes.Callvirt; } + ModuleDefinition module = type.Module; TypeDefinition valueTypeDef = valueType.Resolve(); if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) @@ -843,6 +864,7 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Ldarg_1); m = networkStreamType.GetMethod(serializer.ReadMethod); } + il.Emit(OpCodes.Callvirt, module.ImportReference(m)); if (!serialize) { @@ -966,7 +988,7 @@ namespace Flax.Build.Plugins else il.Emit(propertyGetOpCode, property.GetMethod); - // int num2 = ((array2 != null) ? array2.Length : 0); + // int num2 = ((array2 != null) ? array2.Length : 0); il.Emit(OpCodes.Dup); Instruction jmp1 = il.Create(OpCodes.Nop); il.Emit(OpCodes.Brtrue_S, jmp1); @@ -979,8 +1001,8 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Conv_I4); il.Append(jmp2); il.Emit(OpCodes.Stloc, varStart + 0); - - // stream.WriteInt32(num2); + + // stream.WriteInt32(num2); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldloc, varStart + 0); var m = networkStreamType.GetMethod("WriteInt32"); @@ -1039,16 +1061,16 @@ namespace Flax.Build.Plugins var m = networkStreamType.GetMethod("ReadInt32"); il.Emit(OpCodes.Callvirt, module.ImportReference(m)); il.Emit(OpCodes.Stloc, varStart + 0); - - // System.Array.Resize(ref Array1, num); + + // 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) + + // fixed (int* buffer = Array1) il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, field); @@ -1067,8 +1089,8 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Stloc, varStart + 1); Instruction jmp3 = il.Create(OpCodes.Nop); il.Emit(OpCodes.Br_S, jmp3); - - // stream.ReadBytes((byte*)buffer, num * sizeof()); + + // stream.ReadBytes((byte*)buffer, num * sizeof()); il.Append(jmp2); il.Emit(OpCodes.Ldloc, varStart + 2); il.Emit(OpCodes.Ldc_I4_0); @@ -1127,7 +1149,7 @@ namespace Flax.Build.Plugins module.GetType("System.Guid", out var guidType); module.GetType("FlaxEngine.Object", out var scriptingObjectType); if (serialize) - { + { il.InsertBefore(ilStart, il.Create(OpCodes.Nop)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, streamLocalIndex)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldarg, localIndex)); @@ -1222,12 +1244,14 @@ namespace Flax.Build.Plugins failed = true; return; } + if (method.IsVirtual) { MonoCecil.CompilationError($"Not supported virtual RPC method '{method.FullName}'.", method); failed = true; return; } + ModuleDefinition module = type.Module; var voidType = module.TypeSystem.Void; if (method.ReturnType != voidType) @@ -1236,12 +1260,14 @@ namespace Flax.Build.Plugins failed = true; return; } + if (method.IsStatic) { MonoCecil.CompilationError($"Not supported static RPC method '{method.FullName}'.", method); failed = true; return; } + var methodRPC = new MethodRPC(); methodRPC.Type = type; methodRPC.Method = method; @@ -1252,6 +1278,7 @@ namespace Flax.Build.Plugins methodRPC.IsClient = (bool)attribute.ConstructorArguments[1].Value; methodRPC.Channel = (int)attribute.ConstructorArguments[2].Value; } + methodRPC.IsServer = (bool)attribute.GetFieldValue("Server", methodRPC.IsServer); methodRPC.IsClient = (bool)attribute.GetFieldValue("Client", methodRPC.IsClient); methodRPC.Channel = (int)attribute.GetFieldValue("Channel", methodRPC.Channel); @@ -1261,12 +1288,14 @@ namespace Flax.Build.Plugins failed = true; return; } + if (!methodRPC.IsServer && !methodRPC.IsClient) { MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} needs to have Server or Client specifier.", method); failed = true; return; } + module.GetType("System.IntPtr", out var intPtrType); module.GetType("FlaxEngine.Object", out var scriptingObjectType); var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr"); @@ -1294,7 +1323,7 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Call, module.ImportReference(fromUnmanagedPtr)); il.Emit(OpCodes.Castclass, networkStream); il.Emit(OpCodes.Stloc_1); - + // Add locals for each RPC parameter var argsStart = il.Body.Variables.Count; for (int i = 0; i < method.Parameters.Count; i++) @@ -1306,6 +1335,7 @@ namespace Flax.Build.Plugins failed = true; return; } + var parameterType = parameter.ParameterType; il.Body.Variables.Add(new VariableDefinition(parameterType)); } @@ -1324,8 +1354,9 @@ namespace Flax.Build.Plugins { il.Emit(OpCodes.Ldloc, argsStart + i); } + il.Emit(OpCodes.Callvirt, method); - + il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ret); type.Methods.Add(m); @@ -1418,7 +1449,6 @@ namespace Flax.Build.Plugins il.InsertBefore(ilStart, tmp); //il.InsertBefore(ilStart, il.Create(OpCodes.Ret)); il.InsertBefore(ilStart, il.Create(OpCodes.Br, jumpBodyEnd)); - } // if (client && networkMode == NetworkManagerMode.Server) return; From 7cdd35cc8d76964c487590b1121fda81a6bcba8a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 13 Mar 2023 20:36:10 +0100 Subject: [PATCH 15/17] Fix network codegen for 6 or more param RPC in C# --- .../Flax.Build/Build/Plugins/NetworkingPlugin.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 5dccfccc4..99ded7da1 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -531,9 +531,6 @@ namespace Flax.Build.Plugins { if (type.IsInterface || type.IsEnum) continue; - var isNative = type.HasAttribute("FlaxEngine.UnmanagedAttribute"); - if (isNative) - continue; // Generate RPCs var methods = type.Methods; @@ -545,9 +542,14 @@ namespace Flax.Build.Plugins if (attribute != null) { GenerateDotNetRPCBody(type, method, attribute, ref failed, networkStreamType, methodRPCs); + modified = true; } } + var isNative = type.HasAttribute("FlaxEngine.UnmanagedAttribute"); + if (isNative) + continue; + // Generate serializers if (type.HasMethod(Thunk1) || type.HasMethod(Thunk2)) continue; @@ -1402,10 +1404,10 @@ namespace Flax.Build.Plugins // || il.InsertBefore(ilStart, jumpIf2Start); il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, varsStart + 1)); - il.InsertBefore(ilStart, il.Create(OpCodes.Brfalse_S, jumpBodyStart)); + il.InsertBefore(ilStart, il.Create(OpCodes.Brfalse, jumpBodyStart)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, varsStart + 2)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldc_I4_2)); - il.InsertBefore(ilStart, il.Create(OpCodes.Beq_S, jumpBodyStart)); + il.InsertBefore(ilStart, il.Create(OpCodes.Beq, jumpBodyStart)); // { il.InsertBefore(ilStart, jumpIfBodyStart); From 7950c0d9d76dac13c99e2fa929063bd6290240cb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 14 Mar 2023 00:02:24 +0100 Subject: [PATCH 16/17] Fix codegen for C# networking when using custom structures for replication and RPCs --- .../Build/Plugins/NetworkingPlugin.cs | 310 +++++++++++------- 1 file changed, 183 insertions(+), 127 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 99ded7da1..40f86ef66 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -47,6 +47,18 @@ namespace Flax.Build.Plugins public MethodDefinition Execute; } + private struct DotnetContext + { + public bool Modified; + public bool Failed; + public AssemblyDefinition Assembly; + public List AddSerializers; + public List MethodRPCs; + public HashSet GeneratedSerializers; + public TypeReference VoidType; + public TypeReference NetworkStreamType; + } + internal const string Network = "Network"; internal const string NetworkReplicated = "NetworkReplicated"; internal const string NetworkReplicatedAttribute = "FlaxEngine.NetworkReplicatedAttribute"; @@ -494,7 +506,7 @@ namespace Flax.Build.Plugins task.WorkingDirectory = buildTask.WorkingDirectory; task.Command = () => OnPatchDotNetAssembly(buildData, buildOptions, buildTask, assemblyPath); task.CommandPath = null; - task.InfoMessage = $"Generating netowrking code for {Path.GetFileName(assemblyPath)}..."; + task.InfoMessage = $"Generating networking code for {Path.GetFileName(assemblyPath)}..."; task.Cost = 50; task.DisableCache = true; task.DependentTasks = new HashSet(); @@ -519,109 +531,31 @@ namespace Flax.Build.Plugins assemblyResolver.AddSearchDirectory(e); ModuleDefinition module = assembly.MainModule; - TypeReference voidType = module.ImportReference(typeof(void)); - module.GetType("FlaxEngine.Networking.NetworkStream", out var networkStreamType); // Process all types within a module - bool modified = false; - bool failed = false; - var addSerializers = new List(); - var methodRPCs = new List(); + var context = new DotnetContext + { + Modified = false, + Failed = false, + Assembly = assembly, + AddSerializers = new List(), + MethodRPCs = new List(), + GeneratedSerializers = new HashSet(), + VoidType = module.ImportReference(typeof(void)), + }; + module.GetType("FlaxEngine.Networking.NetworkStream", out context.NetworkStreamType); foreach (TypeDefinition type in module.Types) { - if (type.IsInterface || type.IsEnum) - continue; - - // Generate RPCs - var methods = type.Methods; - var methodsCount = methods.Count; // methods list can be modified during RPCs generation - for (int i = 0; i < methodsCount; i++) - { - MethodDefinition method = methods[i]; - var attribute = method.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == "FlaxEngine.NetworkRpcAttribute"); - if (attribute != null) - { - GenerateDotNetRPCBody(type, method, attribute, ref failed, networkStreamType, methodRPCs); - modified = true; - } - } - - var isNative = type.HasAttribute("FlaxEngine.UnmanagedAttribute"); - if (isNative) - continue; - - // Generate serializers - if (type.HasMethod(Thunk1) || type.HasMethod(Thunk2)) - continue; - var isINetworkSerializable = type.HasInterface("FlaxEngine.Networking.INetworkSerializable"); - MethodDefinition serializeINetworkSerializable = null, deserializeINetworkSerializable = null; - if (isINetworkSerializable) - { - foreach (MethodDefinition m in type.Methods) - { - if (m.HasBody && m.Parameters.Count == 1 && m.Parameters[0].ParameterType.FullName == "FlaxEngine.Networking.NetworkStream") - { - if (m.Name == "Serialize") - serializeINetworkSerializable = m; - else if (m.Name == "Deserialize") - deserializeINetworkSerializable = m; - } - } - } - - var isNetworkReplicated = false; - foreach (FieldDefinition f in type.Fields) - { - if (!f.HasAttribute(NetworkReplicatedAttribute)) - continue; - isNetworkReplicated = true; - break; - } - - foreach (PropertyDefinition p in type.Properties) - { - if (!p.HasAttribute(NetworkReplicatedAttribute)) - continue; - isNetworkReplicated = true; - break; - } - - if (type.IsValueType) - { - if (isINetworkSerializable) - { - // Generate INetworkSerializable interface method calls - GenerateCallINetworkSerializable(type, Thunk1, voidType, networkStreamType, serializeINetworkSerializable); - GenerateCallINetworkSerializable(type, Thunk2, voidType, networkStreamType, deserializeINetworkSerializable); - modified = true; - } - else if (isNetworkReplicated) - { - // Generate serializization methods - GenerateSerializer(type, true, ref failed, Thunk1, voidType, networkStreamType); - GenerateSerializer(type, false, ref failed, Thunk2, voidType, networkStreamType); - modified = true; - } - } - else if (!isINetworkSerializable && isNetworkReplicated) - { - // Generate serializization methods - var addSerializer = new TypeSerializer(); - addSerializer.Type = type; - addSerializer.Serialize = GenerateNativeSerializer(type, true, ref failed, Thunk1, voidType, networkStreamType); - addSerializer.Deserialize = GenerateNativeSerializer(type, false, ref failed, Thunk2, voidType, networkStreamType); - addSerializers.Add(addSerializer); - modified = true; - } + GenerateTypeNetworking(ref context, type); } - if (failed) + if (context.Failed) throw new Exception($"Failed to generate network replication for assembly {assemblyPath}"); - if (!modified) + if (!context.Modified) return; // Generate serializers initializer (invoked on module load) - if (addSerializers.Count != 0 || methodRPCs.Count != 0) + if (context.AddSerializers.Count != 0 || context.MethodRPCs.Count != 0) { // Create class var name = "Initializer"; @@ -641,7 +575,7 @@ namespace Flax.Build.Plugins c.CustomAttributes.Add(attribute); // Add Init method - var m = new MethodDefinition("Init", MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, voidType); + var m = new MethodDefinition("Init", MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, context.VoidType); ILProcessor il = m.Body.GetILProcessor(); il.Emit(OpCodes.Nop); module.GetType("System.Type", out var typeType); @@ -655,7 +589,7 @@ namespace Flax.Build.Plugins module.ImportReference(addRPC); var executeRPCFuncType = addRPC.Parameters[2].ParameterType; var executeRPCFuncCtor = executeRPCFuncType.Resolve().GetMethod(".ctor"); - foreach (var e in addSerializers) + foreach (var e in context.AddSerializers) { // NetworkReplicator.AddSerializer(typeof(), .INetworkSerializable_SerializeNative, .INetworkSerializable_DeserializeNative); il.Emit(OpCodes.Ldtoken, e.Type); @@ -669,7 +603,7 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Call, module.ImportReference(addSerializer)); } - foreach (var e in methodRPCs) + foreach (var e in context.MethodRPCs) { // NetworkReplicator.AddRPC(typeof(), "", _Execute, , , ); il.Emit(OpCodes.Ldtoken, e.Type); @@ -694,10 +628,124 @@ namespace Flax.Build.Plugins } } - private static void GenerateCallINetworkSerializable(TypeDefinition type, string name, TypeReference voidType, TypeReference networkStreamType, MethodDefinition method) + private static void GenerateTypeNetworking(ref DotnetContext context, TypeDefinition type) { - var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, voidType); - m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, networkStreamType)); + if (type.IsInterface || type.IsEnum) + return; + + // Process nested types + foreach (var nestedType in type.NestedTypes) + { + GenerateTypeNetworking(ref context, nestedType); + } + + if (type.IsClass) + { + // Generate RPCs + var methods = type.Methods; + var methodsCount = methods.Count; // methods list can be modified during RPCs generation + for (int i = 0; i < methodsCount; i++) + { + MethodDefinition method = methods[i]; + var attribute = method.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == "FlaxEngine.NetworkRpcAttribute"); + if (attribute != null) + { + GenerateDotNetRPCBody(ref context, type, method, attribute, context.NetworkStreamType); + context.Modified = true; + } + } + } + + GenerateTypeSerialization(ref context, type); + } + + private static void GenerateTypeSerialization(ref DotnetContext context, TypeDefinition type) + { + // Skip types outside from current assembly + if (context.Assembly.MainModule != type.Module) + return; + + // Skip if already generated serialization for this type (eg. via referenced RPC in other type) + if (context.GeneratedSerializers.Contains(type)) + return; + context.GeneratedSerializers.Add(type); + + // Skip native types + var isNative = type.HasAttribute("FlaxEngine.UnmanagedAttribute"); + if (isNative) + return; + + // Skip if manually implemented serializers + if (type.HasMethod(Thunk1) || type.HasMethod(Thunk2)) + return; + + // Generate serializers + var isINetworkSerializable = type.HasInterface("FlaxEngine.Networking.INetworkSerializable"); + MethodDefinition serializeINetworkSerializable = null, deserializeINetworkSerializable = null; + if (isINetworkSerializable) + { + foreach (MethodDefinition m in type.Methods) + { + if (m.HasBody && m.Parameters.Count == 1 && m.Parameters[0].ParameterType.FullName == "FlaxEngine.Networking.NetworkStream") + { + if (m.Name == "Serialize") + serializeINetworkSerializable = m; + else if (m.Name == "Deserialize") + deserializeINetworkSerializable = m; + } + } + } + + var isNetworkReplicated = false; + foreach (FieldDefinition f in type.Fields) + { + if (!f.HasAttribute(NetworkReplicatedAttribute)) + continue; + isNetworkReplicated = true; + break; + } + + foreach (PropertyDefinition p in type.Properties) + { + if (!p.HasAttribute(NetworkReplicatedAttribute)) + continue; + isNetworkReplicated = true; + break; + } + + if (type.IsValueType) + { + if (isINetworkSerializable) + { + // Generate INetworkSerializable interface method calls + GenerateCallINetworkSerializable(ref context, type, Thunk1, serializeINetworkSerializable); + GenerateCallINetworkSerializable(ref context, type, Thunk2, deserializeINetworkSerializable); + context.Modified = true; + } + else if (isNetworkReplicated) + { + // Generate serializization methods + GenerateSerializer(ref context, type, true, Thunk1); + GenerateSerializer(ref context, type, false, Thunk2); + context.Modified = true; + } + } + else if (!isINetworkSerializable && isNetworkReplicated) + { + // Generate serializization methods + var addSerializer = new TypeSerializer(); + addSerializer.Type = type; + addSerializer.Serialize = GenerateNativeSerializer(ref context, type, true, Thunk1); + addSerializer.Deserialize = GenerateNativeSerializer(ref context, type, false, Thunk2); + context.AddSerializers.Add(addSerializer); + context.Modified = true; + } + } + + private static void GenerateCallINetworkSerializable(ref DotnetContext context, TypeDefinition type, string name, MethodDefinition method) + { + var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, context.VoidType); + m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, context.NetworkStreamType)); ILProcessor il = m.Body.GetILProcessor(); il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ldarg_0); @@ -708,12 +756,11 @@ namespace Flax.Build.Plugins type.Methods.Add(m); } - private static MethodDefinition GenerateSerializer(TypeDefinition type, bool serialize, ref bool failed, string name, TypeReference voidType, TypeReference networkStreamType) + private static MethodDefinition GenerateSerializer(ref DotnetContext context, TypeDefinition type, bool serialize, string name) { ModuleDefinition module = type.Module; - var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, voidType); - m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, module.ImportReference(networkStreamType))); - TypeDefinition networkStream = networkStreamType.Resolve(); + var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, context.VoidType); + m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, module.ImportReference(context.NetworkStreamType))); ILProcessor il = m.Body.GetILProcessor(); il.Emit(OpCodes.Nop); @@ -728,7 +775,7 @@ namespace Flax.Build.Plugins { if (!f.HasAttribute(NetworkReplicatedAttribute)) continue; - GenerateSerializerType(type, serialize, ref failed, f, null, f.FieldType, il, networkStream); + GenerateSerializerType(ref context, type, serialize, f, null, f.FieldType, il); } // Serialize all type properties marked with NetworkReplicated attribute @@ -736,7 +783,7 @@ namespace Flax.Build.Plugins { if (!p.HasAttribute(NetworkReplicatedAttribute)) continue; - GenerateSerializerType(type, serialize, ref failed, null, p, p.PropertyType, il, networkStream); + GenerateSerializerType(ref context, type, serialize, null, p, p.PropertyType, il); } if (serialize) @@ -746,17 +793,17 @@ namespace Flax.Build.Plugins return m; } - private static MethodDefinition GenerateNativeSerializer(TypeDefinition type, bool serialize, ref bool failed, string name, TypeReference voidType, TypeReference networkStreamType) + private static MethodDefinition GenerateNativeSerializer(ref DotnetContext context, TypeDefinition type, bool serialize, string name) { ModuleDefinition module = type.Module; module.GetType("System.IntPtr", out var intPtrType); module.GetType("FlaxEngine.Object", out var scriptingObjectType); var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr"); - var m = new MethodDefinition(name + "Native", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, voidType); + var m = new MethodDefinition(name + "Native", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, context.VoidType); m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtrType)); m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtrType)); - TypeReference networkStream = module.ImportReference(networkStreamType); + TypeReference networkStream = module.ImportReference(context.NetworkStreamType); ILProcessor il = m.Body.GetILProcessor(); il.Emit(OpCodes.Nop); il.Body.InitLocals = true; @@ -776,7 +823,7 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Stloc_1); // Generate normal serializer - var serializer = GenerateSerializer(type, serialize, ref failed, name, voidType, networkStreamType); + var serializer = GenerateSerializer(ref context, type, serialize, name); // Call serializer il.Emit(OpCodes.Ldloc_0); @@ -816,10 +863,11 @@ namespace Flax.Build.Plugins } } - private static void GenerateSerializerType(TypeDefinition type, bool serialize, ref bool failed, FieldReference field, PropertyDefinition property, TypeReference valueType, ILProcessor il, TypeDefinition networkStreamType) + private static void GenerateSerializerType(ref DotnetContext context, TypeDefinition type, bool serialize, FieldReference field, PropertyDefinition property, TypeReference valueType, ILProcessor il) { if (field == null && property == null) throw new ArgumentException(); + TypeDefinition networkStreamType = context.NetworkStreamType.Resolve(); var propertyGetOpCode = OpCodes.Call; var propertySetOpCode = OpCodes.Call; if (property != null) @@ -827,14 +875,14 @@ namespace Flax.Build.Plugins if (property.GetMethod == null) { MonoCecil.CompilationError($"Missing getter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", property); - failed = true; + 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); - failed = true; + context.Failed = true; return; } @@ -846,6 +894,10 @@ namespace Flax.Build.Plugins ModuleDefinition module = type.Module; TypeDefinition valueTypeDef = valueType.Resolve(); + + // Ensure to have valid serialization already generated for that value type (eg. when using custom structure field serialization) + GenerateTypeSerialization(ref context, valueTypeDef); + if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) { // Call NetworkStream method to write/read data @@ -1121,14 +1173,18 @@ namespace Flax.Build.Plugins MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {field.Name} in {type.FullName} for automatic replication.", field.Resolve()); else MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' for automatic replication."); - failed = true; + context.Failed = true; } } - private static void GenerateDotNetRPCSerializerType(TypeDefinition type, bool serialize, ref bool failed, int localIndex, TypeReference valueType, ILProcessor il, TypeDefinition networkStreamType, int streamLocalIndex, Instruction ilStart) + private static void GenerateDotNetRPCSerializerType(ref DotnetContext context, TypeDefinition type, bool serialize, int localIndex, TypeReference valueType, ILProcessor il, TypeDefinition networkStreamType, int streamLocalIndex, Instruction ilStart) { ModuleDefinition module = type.Module; TypeDefinition valueTypeDef = valueType.Resolve(); + + // Ensure to have valid serialization already generated for that value type + GenerateTypeSerialization(ref context, valueTypeDef); + if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) { // Call NetworkStream method to write/read data @@ -1233,24 +1289,24 @@ namespace Flax.Build.Plugins { // Unknown type Log.Error($"Not supported type '{valueType.FullName}' for RPC parameter in {type.FullName}."); - failed = true; + context.Failed = true; } } - private static void GenerateDotNetRPCBody(TypeDefinition type, MethodDefinition method, CustomAttribute attribute, ref bool failed, TypeReference networkStreamType, List methodRPCs) + private static void GenerateDotNetRPCBody(ref DotnetContext context, TypeDefinition type, MethodDefinition method, CustomAttribute attribute, TypeReference networkStreamType) { // Validate RPC usage if (method.IsAbstract) { MonoCecil.CompilationError($"Not supported abstract RPC method '{method.FullName}'.", method); - failed = true; + context.Failed = true; return; } if (method.IsVirtual) { MonoCecil.CompilationError($"Not supported virtual RPC method '{method.FullName}'.", method); - failed = true; + context.Failed = true; return; } @@ -1259,14 +1315,14 @@ namespace Flax.Build.Plugins if (method.ReturnType != voidType) { MonoCecil.CompilationError($"Not supported non-void RPC method '{method.FullName}'.", method); - failed = true; + context.Failed = true; return; } if (method.IsStatic) { MonoCecil.CompilationError($"Not supported static RPC method '{method.FullName}'.", method); - failed = true; + context.Failed = true; return; } @@ -1287,14 +1343,14 @@ namespace Flax.Build.Plugins if (methodRPC.IsServer && methodRPC.IsClient) { MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} cannot be both Server and Client.", method); - failed = true; + context.Failed = true; return; } if (!methodRPC.IsServer && !methodRPC.IsClient) { MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} needs to have Server or Client specifier.", method); - failed = true; + context.Failed = true; return; } @@ -1334,7 +1390,7 @@ namespace Flax.Build.Plugins if (parameter.IsOut) { MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} parameter {parameter.Name} cannot be 'out'.", method); - failed = true; + context.Failed = true; return; } @@ -1347,7 +1403,7 @@ namespace Flax.Build.Plugins { var parameter = method.Parameters[i]; var parameterType = parameter.ParameterType; - GenerateDotNetRPCSerializerType(type, false, ref failed, argsStart + i, parameterType, il, networkStream.Resolve(), 1, null); + GenerateDotNetRPCSerializerType(ref context, type, false, argsStart + i, parameterType, il, networkStream.Resolve(), 1, null); } // Call RPC method body @@ -1424,7 +1480,7 @@ namespace Flax.Build.Plugins { var parameter = method.Parameters[i]; var parameterType = parameter.ParameterType; - GenerateDotNetRPCSerializerType(type, true, ref failed, 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(), "", stream); @@ -1471,7 +1527,7 @@ namespace Flax.Build.Plugins il.InsertBefore(ilStart, jumpBodyStart); } - methodRPCs.Add(methodRPC); + context.MethodRPCs.Add(methodRPC); } } } From eece05c1186a8df00f15e0d6d5e2393f62fdc50a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 14 Mar 2023 00:02:47 +0100 Subject: [PATCH 17/17] Add support for using `INetworkSerializable` on custom structure in C++ for networking --- Source/Engine/Networking/NetworkStream.cpp | 21 +++++++++++++++++++++ Source/Engine/Networking/NetworkStream.h | 10 ++++++++++ 2 files changed, 31 insertions(+) diff --git a/Source/Engine/Networking/NetworkStream.cpp b/Source/Engine/Networking/NetworkStream.cpp index f7ce87156..6de667878 100644 --- a/Source/Engine/Networking/NetworkStream.cpp +++ b/Source/Engine/Networking/NetworkStream.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "NetworkStream.h" +#include "INetworkSerializable.h" NetworkStream::NetworkStream(const SpawnParams& params) : ScriptingObject(params) @@ -47,6 +48,26 @@ void NetworkStream::Initialize(byte* buffer, uint32 length) _allocated = false; } +void NetworkStream::Read(INetworkSerializable& obj) +{ + obj.Deserialize(this); +} + +void NetworkStream::Read(INetworkSerializable* obj) +{ + obj->Deserialize(this); +} + +void NetworkStream::Write(INetworkSerializable& obj) +{ + obj.Serialize(this); +} + +void NetworkStream::Write(INetworkSerializable* obj) +{ + obj->Serialize(this); +} + void NetworkStream::Flush() { // Nothing to do diff --git a/Source/Engine/Networking/NetworkStream.h b/Source/Engine/Networking/NetworkStream.h index af87c2363..190bd0dcb 100644 --- a/Source/Engine/Networking/NetworkStream.h +++ b/Source/Engine/Networking/NetworkStream.h @@ -6,6 +6,8 @@ #include "Engine/Serialization/ReadStream.h" #include "Engine/Serialization/WriteStream.h" +class INetworkSerializable; + /// /// Objects and values serialization stream for sending data over network. Uses memory buffer for both read and write operations. /// @@ -62,6 +64,14 @@ public: ReadBytes(data, bytes); } + using ReadStream::Read; + void Read(INetworkSerializable& obj); + void Read(INetworkSerializable* obj); + + using WriteStream::Write; + void Write(INetworkSerializable& obj); + void Write(INetworkSerializable* obj); + public: // [Stream] void Flush() override;