diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index b95f9e722..e9bcd97a9 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -156,88 +156,126 @@ namespace FlaxEditor.CustomEditors.Editors pickerData.IsEditing = false; } - - private static void OnAddButtonClicked(Tree tree, TreeNode parentNode, PickerData pickerData) + private static void OnAddTagButtonClicked(string input, Tree tree, TextBox textBox, DropPanel dropPanel, PickerData pickerData) { - // Create new node - var nodeIndent = 16.0f; - var indentation = 0; - var parentTag = string.Empty; - if (parentNode.CustomArrowRect.HasValue) - { - indentation = (int)((parentNode.CustomArrowRect.Value.Location.X - 18) / nodeIndent) + 1; - var parentTagValue = (Tag)parentNode.Tag; - parentTag = parentTagValue.ToString(); - } - var node = new TreeNodeWithAddons - { - ChildrenIndent = nodeIndent, - CullChildren = false, - ClipChildren = false, - TextMargin = new Margin(22.0f, 2.0f, 2.0f, 2.0f), - CustomArrowRect = new Rectangle(18 + indentation * nodeIndent, 2, 12, 12), - BackgroundColorSelected = Color.Transparent, - BackgroundColorHighlighted = parentNode.BackgroundColorHighlighted, - }; - var checkbox = new CheckBox(32.0f + indentation * nodeIndent, 0) - { - Height = 16.0f, - IsScrollable = false, - Parent = node, - }; - node.Addons.Add(checkbox); - checkbox.StateChanged += c => OnCheckboxEdited(node, c, pickerData); - var addButton = new Button(tree.Width - 16, 0, 14, 14) - { - Text = "+", - TooltipText = "Add subtag within this tag namespace", - IsScrollable = false, - BorderColor = Color.Transparent, - BackgroundColor = Color.Transparent, - Parent = node, - AnchorPreset = AnchorPresets.TopRight, - }; - node.Addons.Add(addButton); - addButton.ButtonClicked += button => OnAddButtonClicked(tree, node, pickerData); + if (string.IsNullOrEmpty(input)) + return; - // Link - node.Parent = parentNode; - node.IndexInParent = 0; - parentNode.Expand(true); - ((Panel)tree.Parent.Parent).ScrollViewTo(node); + // Ensure that name is unique + if (Tags.List.Contains(input)) + return; - // Start renaming the tag - var prefix = parentTag.Length != 0 ? parentTag + '.' : string.Empty; - var renameArea = node.HeaderRect; - if (renameArea.Location.X < nodeIndent) - { - // Fix root node's child renaming - renameArea.Location.X += nodeIndent; - renameArea.Size.X -= nodeIndent; - } - var dialog = RenamePopup.Show(node, renameArea, prefix, false); - var cursor = dialog.InputField.TextLength; - dialog.InputField.SelectionRange = new TextRange(cursor, cursor); - dialog.Validate = (popup, value) => - { - // Ensure that name is unique - if (Tags.List.Contains(value)) - return false; + var subInputs = input.Split('.'); + string tagString = string.Empty; + string lastTagString = string.Empty; - // Ensure user entered direct subtag of the parent node - if (value.StartsWith(popup.InitialValue)) + TreeNode parentNode = tree.GetChild(); // Start at root + int parentCount = 0; + for (int i = 0; i < subInputs.Length; i++) + { + if (string.IsNullOrEmpty(subInputs[i])) { - var name = value.Substring(popup.InitialValue.Length); - if (name.Length > 0 && name.IndexOf('.') == -1) - return true; + continue; } - return false; - }; - dialog.Renamed += popup => - { + + // Check all entered subtags and create any that dont exist + for (int j = 0; j <= i; j++) + { + tagString += j == 0 ? subInputs[j] : "." + subInputs[j]; + } + + if (string.IsNullOrEmpty(tagString)) + { + tagString = string.Empty; + continue; + } + + if (Tags.List.Contains(tagString)) + { + // Find next parent node + foreach (var child in parentNode.Children) + { + if (!(child is TreeNode childNode)) + continue; + + var tagValue = (Tag)childNode.Tag; + if (!string.Equals(tagValue.ToString(), tagString)) + continue; + + parentNode = childNode; + parentCount += 1; + } + lastTagString = tagString; + tagString = string.Empty; + continue; + } + else if (subInputs.Length > 1) + { + // Find next parent node + foreach (var child in parentNode.Children) + { + if (!(child is TreeNode childNode)) + continue; + + var tagValue = (Tag)childNode.Tag; + if (!string.Equals(tagValue.ToString(), lastTagString)) + continue; + + parentNode = childNode; + parentCount += 1; + } + } + + // Create new node + var nodeIndent = 16.0f; + var indentation = 0; + var parentTag = string.Empty; + if (parentNode.CustomArrowRect.HasValue) + { + indentation = (int)((parentNode.CustomArrowRect.Value.Location.X - 18) / nodeIndent) + 1; + var parentTagValue = (Tag)parentNode.Tag; + parentTag = parentTagValue.ToString(); + } + var node = new TreeNodeWithAddons + { + ChildrenIndent = nodeIndent, + CullChildren = false, + ClipChildren = false, + TextMargin = new Margin(22.0f, 2.0f, 2.0f, 2.0f), + CustomArrowRect = new Rectangle(18 + indentation * nodeIndent, 2, 12, 12), + BackgroundColorSelected = Color.Transparent, + BackgroundColorHighlighted = parentNode.BackgroundColorHighlighted, + }; + var checkbox = new CheckBox(32.0f + indentation * nodeIndent, 0) + { + Height = 16.0f, + IsScrollable = false, + Parent = node, + }; + node.Addons.Add(checkbox); + checkbox.StateChanged += c => OnCheckboxEdited(node, c, pickerData); + var addButton = new Button(tree.Width - 16, 0, 14, 14) + { + Text = "+", + TooltipText = "Add subtag within this tag namespace", + IsScrollable = false, + BorderColor = Color.Transparent, + BackgroundColor = Color.Transparent, + Parent = node, + AnchorPreset = AnchorPresets.TopRight, + }; + node.Addons.Add(addButton); + addButton.ButtonClicked += button => OnAddSubTagButtonClicked(((Tag)node.Tag).ToString(), textBox, dropPanel); + + // Link + node.Parent = parentNode; + node.IndexInParent = 0; + parentNode.Expand(true); + ((Panel)tree.Parent.Parent).ScrollViewTo(node); + // Get tag name - var tagName = popup.Text; - var tagShortName = tagName.Substring(popup.InitialValue.Length); + var tagName = tagString; + var tagShortName = tagName.Substring(parentCount == 0 ? lastTagString.Length : lastTagString.Length + 1); if (tagShortName.Length == 0) return; @@ -256,24 +294,134 @@ namespace FlaxEditor.CustomEditors.Editors // Update asset var settingsObj = (LayersAndTagsSettings)settingsAsset.Instance; settingsObj.Tags.Add(tagName); + settingsObj.Tags.Sort(); settingsAsset.SetInstance(settingsObj); // Reload editor window to reflect new tag assetWindow?.RefreshAsset(); } - }; - dialog.Closed += popup => + + lastTagString = tagString; + tagString = string.Empty; + } + textBox.Text = string.Empty; + } + + private static void OnAddSubTagButtonClicked(string parentTag, TextBox textBox, DropPanel dropPanel) + { + if (textBox == null || dropPanel == null || string.IsNullOrEmpty(parentTag)) { - // Remove temporary node if renaming was canceled - if (popup.InitialValue == popup.Text || popup.Text.Length == 0) - node.Dispose(); - }; + return; + } + dropPanel.Open(); + textBox.Text = parentTag + "."; + textBox.Focus(); + textBox.SelectionRange = new TextRange(textBox.Text.Length, textBox.Text.Length); } internal static ContextMenuBase CreatePicker(Tag value, Tag[] values, PickerData pickerData) { // Initialize search popup - var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree, 20.0f); + var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree, 40.0f); + + // Add tag drop panel + var addTagDropPanel = new DropPanel + { + HeaderText = "Add Tag", + EnableDropDownIcon = true, + ArrowImageOpened = new SpriteBrush(FlaxEngine.GUI.Style.Current.ArrowDown), + ArrowImageClosed = new SpriteBrush(FlaxEngine.GUI.Style.Current.ArrowRight), + Parent = menu, + HeaderTextMargin = new Margin(2.0f), + AnchorPreset = AnchorPresets.TopLeft, + Bounds = new Rectangle(2, 2, menu.Width - 4, 30), + IsClosed = true, + ItemsMargin = new Margin(2.0f), + CloseAnimationTime = 0, + }; + var tagNamePanel = new HorizontalPanel + { + Parent = addTagDropPanel, + AutoSize = false, + Bounds = new Rectangle(0, 0, addTagDropPanel.Width, 20.0f), + }; + var nameLabel = new Label + { + Size = new Float2(addTagDropPanel.Width / 4, 0), + Parent = tagNamePanel, + Text = "Tag Name:", + }; + var nameTextBox = new TextBox + { + IsMultiline = false, + WatermarkText = "X.Y.Z", + Size = new Float2(addTagDropPanel.Width * 0.7f, 0), + Parent = tagNamePanel, + EndEditOnClick = false, + }; + + bool uniqueText = true; + nameTextBox.TextChanged += () => + { + // Ensure that name is unique + if (Tags.List.Contains(nameTextBox.Text)) + uniqueText = false; + else + uniqueText = true; + + var currentStyle = FlaxEngine.GUI.Style.Current; + if (uniqueText) + { + nameTextBox.BorderColor = Color.Transparent; + nameTextBox.BorderSelectedColor = currentStyle.BackgroundSelected; + } + else + { + var color = new Color(1.0f, 0.0f, 0.02745f, 1.0f); + nameTextBox.BorderColor = Color.Lerp(color, currentStyle.TextBoxBackground, 0.6f); + nameTextBox.BorderSelectedColor = color; + } + }; + + nameTextBox.EditEnd += () => + { + if (!uniqueText) + return; + + if (addTagDropPanel.IsClosed) + { + Debug.Log("Hit"); + nameTextBox.BorderColor = Color.Transparent; + nameTextBox.BorderSelectedColor = FlaxEngine.GUI.Style.Current.BackgroundSelected; + return; + } + + OnAddTagButtonClicked(nameTextBox.Text, tree, nameTextBox, addTagDropPanel, pickerData); + }; + + var addButtonPanel = new HorizontalPanel + { + Parent = addTagDropPanel, + AutoSize = false, + Bounds = new Rectangle(0, 0, addTagDropPanel.Width, 30.0f), + }; + var buttonAddTag = new Button + { + Parent = addButtonPanel, + Size = new Float2(100, 20), + Text = "Add Tag", + AnchorPreset = AnchorPresets.MiddleCenter, + }; + buttonAddTag.Clicked += () => + { + if (!uniqueText) + return; + + OnAddTagButtonClicked(nameTextBox.Text, tree, nameTextBox, addTagDropPanel, pickerData); + }; + + // Used for how far everything should drop when the drop down panel is opened + var dropPanelOpenHeight = tagNamePanel.Height + addButtonPanel.Height + 4; // Create tree with tags hierarchy tree.Margin = new Margin(-16.0f, 0.0f, -16.0f, -0.0f); // Hide root node @@ -306,7 +454,7 @@ namespace FlaxEditor.CustomEditors.Editors tagShortName = tagName.Substring(lastDotIndex + 1); tagParentName = tagName.Substring(0, lastDotIndex); } - + // Create node var node = new TreeNodeWithAddons { @@ -339,7 +487,7 @@ namespace FlaxEditor.CustomEditors.Editors AnchorPreset = AnchorPresets.TopRight, }; node.Addons.Add(addButton); - addButton.ButtonClicked += button => OnAddButtonClicked(tree, node, pickerData); + addButton.ButtonClicked += button => OnAddSubTagButtonClicked(((Tag)node.Tag).ToString(), nameTextBox, addTagDropPanel); // Link to parent { @@ -360,10 +508,13 @@ namespace FlaxEditor.CustomEditors.Editors { Margin = new Margin(1.0f), AutoSize = false, - Bounds = new Rectangle(0, 0, menu.Width, 20.0f), + Bounds = new Rectangle(0, 0, menu.Width - 4, 20.0f), Parent = menu, }; - var buttonsSize = new Float2((menu.Width - buttonsPanel.Margin.Width) / 4.0f - buttonsPanel.Spacing, 18.0f); + buttonsPanel.Y += addTagDropPanel.HeaderHeight + 4; + buttonsPanel.X += 2; + + var buttonsSize = new Float2((menu.Width - 2 - buttonsPanel.Margin.Width) / 3.0f - buttonsPanel.Spacing, 18.0f); var buttonExpandAll = new Button { Size = buttonsSize, @@ -382,20 +533,13 @@ namespace FlaxEditor.CustomEditors.Editors root.CollapseAll(true); root.Expand(true); }; - var buttonAddTag = new Button + var buttonClearAll = new Button { Size = buttonsSize, Parent = buttonsPanel, - Text = "Add Tag", + Text = "Clear all", }; - buttonAddTag.Clicked += () => OnAddButtonClicked(tree, root, pickerData); - var buttonReset = new Button - { - Size = buttonsSize, - Parent = buttonsPanel, - Text = "Reset", - }; - buttonReset.Clicked += () => + buttonClearAll.Clicked += () => { pickerData.IsEditing = true; UncheckAll(root); @@ -404,6 +548,48 @@ namespace FlaxEditor.CustomEditors.Editors pickerData.SetValues?.Invoke(null); }; + // Move menu children to location when drop panel is opened and closed + addTagDropPanel.IsClosedChanged += panel => + { + if (panel.IsClosed) + { + // Resize/ Move UI to fit space with drop down + foreach (var child in menu.Children) + { + if (child == panel) + continue; + + // Expand panels so scrollbars work + if (child is Panel) + { + child.Bounds = new Rectangle(child.Bounds.X, child.Bounds.Y - dropPanelOpenHeight, child.Width, child.Height + dropPanelOpenHeight); + continue; + } + child.Y -= dropPanelOpenHeight; + nameTextBox.Text = string.Empty; + nameTextBox.Defocus(); + } + } + else + { + // Resize/ Move UI to fit space with drop down + foreach (var child in menu.Children) + { + if (child == panel) + continue; + + // Shrink panels so scrollbars work + if (child is Panel) + { + child.Bounds = new Rectangle(child.Bounds.X, child.Bounds.Y + dropPanelOpenHeight, child.Width, child.Height - dropPanelOpenHeight); + continue; + } + child.Y += dropPanelOpenHeight; + nameTextBox.Text = string.Empty; + } + } + }; + // Setup search filter searchBox.TextChanged += delegate { diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 866c88128..6ef152e28 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -94,6 +94,7 @@ namespace FlaxEditor.Windows "Lukáš Jech", "Jean-Baptiste Perrier", "Chandler Cox", + "Ari Vuollet", }); authors.Sort(); var authorsLabel = new Label(4, topParentControl.Bottom + 20, Width - 8, 70) diff --git a/Source/Editor/Windows/Assets/AssetEditorWindow.cs b/Source/Editor/Windows/Assets/AssetEditorWindow.cs index 05a9151c2..0d244479c 100644 --- a/Source/Editor/Windows/Assets/AssetEditorWindow.cs +++ b/Source/Editor/Windows/Assets/AssetEditorWindow.cs @@ -313,7 +313,12 @@ namespace FlaxEditor.Windows.Assets /// public void RefreshAsset() { - _isWaitingForLoaded = true; + if (_asset == null || _asset.WaitForLoaded()) + return; + + OnAssetLoaded(); + MarkAsEdited(); + Save(); } /// diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index f17deb4bd..aaed484a1 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -816,15 +816,23 @@ namespace FlaxEditor.Windows } // Selected UI controls outline + bool drawAnySelectedControl = false; for (var i = 0; i < Editor.Instance.SceneEditing.Selection.Count; i++) { if (Editor.Instance.SceneEditing.Selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null) { + if (!drawAnySelectedControl) + { + drawAnySelectedControl = true; + Render2D.PushTransform(ref _viewport._cachedTransform); + } var control = controlActor.Control; var bounds = Rectangle.FromPoints(control.PointToParent(_viewport, Float2.Zero), control.PointToParent(_viewport, control.Size)); Render2D.DrawRectangle(bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize); } } + if (drawAnySelectedControl) + Render2D.PopTransform(); // Play mode hints and overlay if (Editor.StateMachine.IsPlayMode) diff --git a/Source/Engine/Core/Types/DataContainer.h b/Source/Engine/Core/Types/DataContainer.h index 5c29a5f3c..be63dd034 100644 --- a/Source/Engine/Core/Types/DataContainer.h +++ b/Source/Engine/Core/Types/DataContainer.h @@ -231,7 +231,7 @@ public: /// Data to copy. void Copy(const DataContainer& data) { - if (data.IsValid()) + if (data.Length() != 0) Copy(data.Get(), data.Length()); else Release(); @@ -243,7 +243,7 @@ public: /// Data to copy. void Copy(const Span& data) { - if (data.IsValid()) + if (data.Length() != 0) Copy(data.Get(), data.Length()); else Release(); diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 9162102dd..7a271ef0a 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -897,6 +897,13 @@ void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa rpc.Info = *info; const Span argsData(argsStream->GetBuffer(), argsStream->GetPosition()); rpc.ArgsData.Copy(argsData); +#if USE_EDITOR || !BUILD_RELEASE + auto it = Objects.Find(obj->GetID()); + if (it == Objects.End()) + { + LOG(Error, "Cannot invoke RPC method '{0}.{1}' on object '{2}' that is not registered in networking (use 'NetworkReplicator.AddObject').", type.ToString(), String(name), obj->GetID()); + } +#endif ObjectsLock.Unlock(); } @@ -980,6 +987,7 @@ void NetworkInternal::NetworkReplicatorUpdate() CachedWriteStream = New(); const bool isClient = NetworkManager::IsClient(); const bool isServer = NetworkManager::IsServer(); + const bool isHost = NetworkManager::IsHost(); NetworkStream* stream = CachedWriteStream; NetworkPeer* peer = NetworkManager::Peer; @@ -1279,10 +1287,10 @@ void NetworkInternal::NetworkReplicatorUpdate() // Client -> Server peer->EndSendMessage(channel, msg); } - else if (e.Info.Client && isServer) + else if (e.Info.Client && (isServer || isHost)) { // Server -> Client(s) - BuildCachedTargets(item); + BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, NetworkManager::LocalClientId); peer->EndSendMessage(channel, msg, CachedTargets); } } diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index fc7d5a623..23355d76f 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -379,17 +379,11 @@ namespace Flax.Build // Reference module output binary fileReferences.Add(Path.Combine(outputPath, dependencyModule.BinaryModuleName + ".CSharp.dll")); } - foreach (var e in buildData.ReferenceBuilds) + var referencedBuild = buildData.FinReferenceBuildModule(dependencyModule.BinaryModuleName); + if (referencedBuild != null && !string.IsNullOrEmpty(referencedBuild.ManagedPath)) { - foreach (var q in e.Value.BuildInfo.BinaryModules) - { - if (q.Name == dependencyModule.BinaryModuleName && !string.IsNullOrEmpty(q.ManagedPath)) - { - // Reference binary module build build for referenced target - fileReferences.Add(q.ManagedPath); - break; - } - } + // Reference binary module build build for referenced target + fileReferences.Add(referencedBuild.ManagedPath); } } } diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 47be906d9..63c9bacfa 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -805,13 +805,13 @@ namespace Flax.Build.Plugins { if (property.GetMethod == null) { - Log.Error($"Missing getter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication."); + MonoCecil.CompilationError($"Missing getter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", property); failed = true; return; } if (property.SetMethod == null) { - Log.Error($"Missing setter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication."); + MonoCecil.CompilationError($"Missing setter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", property); failed = true; return; } @@ -1090,7 +1090,12 @@ namespace Flax.Build.Plugins else { // Unknown type - Log.Error($"Not supported type '{valueType.FullName}' on {(field?.Name ?? property.Name)} in {type.FullName} for automatic replication."); + if (property != null) + MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {property.Name} in {type.FullName} for automatic replication.", property); + else if (field != null) + 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; } } @@ -1212,13 +1217,13 @@ namespace Flax.Build.Plugins // Validate RPC usage if (method.IsAbstract) { - Log.Error($"Not supported abstract RPC method '{method.FullName}'."); + MonoCecil.CompilationError($"Not supported abstract RPC method '{method.FullName}'.", method); failed = true; return; } if (method.IsVirtual) { - Log.Error($"Not supported virtual RPC method '{method.FullName}'."); + MonoCecil.CompilationError($"Not supported virtual RPC method '{method.FullName}'.", method); failed = true; return; } @@ -1226,13 +1231,13 @@ namespace Flax.Build.Plugins var voidType = module.TypeSystem.Void; if (method.ReturnType != voidType) { - Log.Error($"Not supported non-void RPC method '{method.FullName}'."); + MonoCecil.CompilationError($"Not supported non-void RPC method '{method.FullName}'.", method); failed = true; return; } if (method.IsStatic) { - Log.Error($"Not supported static RPC method '{method.FullName}'."); + MonoCecil.CompilationError($"Not supported static RPC method '{method.FullName}'.", method); failed = true; return; } @@ -1241,19 +1246,25 @@ namespace Flax.Build.Plugins methodRPC.Method = method; methodRPC.IsServer = (bool)attribute.GetFieldValue("Server", false); methodRPC.IsClient = (bool)attribute.GetFieldValue("Client", false); + methodRPC.Channel = (int)attribute.GetFieldValue("Channel", 4); // int as NetworkChannelType (default is ReliableOrdered=4) + if (attribute.HasConstructorArguments && attribute.ConstructorArguments.Count >= 3) + { + methodRPC.IsServer = (bool)attribute.ConstructorArguments[0].Value; + methodRPC.IsClient = (bool)attribute.ConstructorArguments[1].Value; + methodRPC.Channel = (int)attribute.ConstructorArguments[2].Value; + } if (methodRPC.IsServer && methodRPC.IsClient) { - Log.Error($"Network RPC {method.Name} in {type.FullName} cannot be both Server and Client."); + MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} cannot be both Server and Client.", method); failed = true; return; } if (!methodRPC.IsServer && !methodRPC.IsClient) { - Log.Error($"Network RPC {method.Name} in {type.FullName} needs to have Server or Client specifier."); + MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} needs to have Server or Client specifier.", method); failed = true; return; } - methodRPC.Channel = (int)attribute.GetFieldValue("Channel", 4); // int as NetworkChannelType (default is ReliableOrdered=4) module.GetType("System.IntPtr", out var intPtrType); module.GetType("FlaxEngine.Object", out var scriptingObjectType); var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr"); @@ -1289,7 +1300,7 @@ namespace Flax.Build.Plugins var parameter = method.Parameters[i]; if (parameter.IsOut) { - Log.Error($"Network RPC {method.Name} in {type.FullName} parameter {parameter.Name} cannot be 'out'."); + MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} parameter {parameter.Name} cannot be 'out'.", method); failed = true; return; } diff --git a/Source/Tools/Flax.Build/Utilities/MonoCecil.cs b/Source/Tools/Flax.Build/Utilities/MonoCecil.cs index 444cbf6ec..43c1de4ba 100644 --- a/Source/Tools/Flax.Build/Utilities/MonoCecil.cs +++ b/Source/Tools/Flax.Build/Utilities/MonoCecil.cs @@ -11,6 +11,58 @@ namespace Flax.Build /// internal static class MonoCecil { + public static void CompilationError(string message) + { + Log.Error(message); + } + + public static void CompilationError(string message, MethodDefinition method) + { + if (method != null && method.DebugInformation.HasSequencePoints) + { + var sp = method.DebugInformation.SequencePoints[0]; + message = $"{sp.Document.Url}({sp.StartLine},{sp.StartColumn},{sp.EndLine},{sp.EndColumn}): error: {message}"; + } + Log.Error(message); + } + + public static void CompilationError(string message, PropertyDefinition property) + { + if (property != null) + { + if (property.GetMethod != null) + { + CompilationError(message, property.GetMethod); + return; + } + else if (property.SetMethod != null) + { + CompilationError(message, property.SetMethod); + return; + } + } + Log.Error(message); + } + + public static void CompilationError(string message, FieldDefinition field) + { + if (field != null && field.DeclaringType != null) + { + // Just include potential filename + var methods = field.DeclaringType.Methods; + if (methods != null && methods.Count != 0) + { + var method = methods[0]; + if (method != null && method.DebugInformation.HasSequencePoints) + { + var sp = method.DebugInformation.SequencePoints[0]; + message = $"{sp.Document.Url}({0},{0},{0},{0}): error: {message}"; + } + } + } + Log.Error(message); + } + public static bool HasAttribute(this ICustomAttributeProvider type, string fullName) { return type.CustomAttributes.Any(x => x.AttributeType.FullName == fullName);