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

This commit is contained in:
Wojtek Figat
2023-07-03 22:01:50 +02:00
87 changed files with 1026 additions and 429 deletions

2
.gitattributes vendored
View File

@@ -1,5 +1,5 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
* text=auto eol=lf
# Explicitly declare text files you want to always be normalized and converted to native line endings on checkout.
*.c text diff=cpp

View File

@@ -3,7 +3,7 @@
"Version": {
"Major": 1,
"Minor": 6,
"Build": 6342
"Build": 6343
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",

View File

@@ -113,7 +113,15 @@ namespace FlaxEditor.Content
public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Other;
/// <inheritdoc />
public override bool CanRename => ParentFolder != null; // Deny rename action for root folders
public override bool CanRename
{
get
{
var hasParentFolder = ParentFolder != null;
var isContentFolder = Node is MainContentTreeNode;
return hasParentFolder && !isContentFolder;
}
}
/// <inheritdoc />
public override bool CanDrag => ParentFolder != null; // Deny rename action for root folders

View File

@@ -3,7 +3,6 @@
using FlaxEditor.CustomEditors;
using FlaxEditor.Windows;
using FlaxEngine.GUI;
using FlaxEngine;
using DockState = FlaxEditor.GUI.Docking.DockState;
namespace FlaxEditor
@@ -86,8 +85,12 @@ namespace FlaxEditor
if (!FlaxEngine.Scripting.IsTypeFromGameScripts(type))
return;
Editor.Instance.Windows.AddToRestore(this);
if (!Window.IsHidden)
{
Editor.Instance.Windows.AddToRestore(this);
}
Window.Close();
Window.Dispose();
}
/// <summary>

View File

@@ -167,7 +167,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
Presenter.Undo?.AddAction(new MultiUndoAction(actions));
// Build ragdoll
SceneGraph.Actors.AnimatedModelNode.BuildRagdoll(animatedModel, options, ragdoll);
AnimatedModelNode.BuildRagdoll(animatedModel, options, ragdoll);
}
private void OnRebuildBone(Button button)
@@ -191,7 +191,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
// Build ragdoll
SceneGraph.Actors.AnimatedModelNode.BuildRagdoll(animatedModel, new AnimatedModelNode.RebuildOptions(), ragdoll, name);
AnimatedModelNode.BuildRagdoll(animatedModel, new AnimatedModelNode.RebuildOptions(), ragdoll, name);
}
private void OnRemoveBone(Button button)

View File

@@ -37,7 +37,8 @@ public class Editor : EditorModule
{
base.Setup(options);
options.ScriptingAPI.SystemReferences.Add("System.Private.Xml");
options.ScriptingAPI.SystemReferences.Add("System.Xml");
options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter");
options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter");

View File

@@ -184,7 +184,7 @@ bool Editor::CheckProjectUpgrade()
" BuildNativeCode = false;\n"
" }}\n"
"}}\n"
), codeName), Encoding::Unicode);
), codeName), Encoding::UTF8);
if (useEditorModule)
{
File::WriteAllText(gameEditorModuleFolder / String::Format(TEXT("{0}Editor.Build.cs"), codeName), String::Format(TEXT(
@@ -211,7 +211,7 @@ bool Editor::CheckProjectUpgrade()
" BuildNativeCode = false;\n"
" }}\n"
"}}\n"
), codeName), Encoding::Unicode);
), codeName), Encoding::UTF8);
}
// Generate target files
@@ -229,7 +229,7 @@ bool Editor::CheckProjectUpgrade()
" Modules.Add(\"{0}\");\n"
" }}\n"
"}}\n"
), codeName), Encoding::Unicode);
), codeName), Encoding::UTF8);
const String editorTargetGameEditorModule = useEditorModule ? String::Format(TEXT(" Modules.Add(\"{0}Editor\");\n"), codeName) : String::Empty;
File::WriteAllText(sourceFolder / String::Format(TEXT("{0}EditorTarget.Build.cs"), codeName), String::Format(TEXT(
"using Flax.Build;\n"
@@ -246,7 +246,7 @@ bool Editor::CheckProjectUpgrade()
"{1}"
" }}\n"
"}}\n"
), codeName, editorTargetGameEditorModule), Encoding::Unicode);
), codeName, editorTargetGameEditorModule), Encoding::UTF8);
// Generate new project file
Project->ProjectPath = root / String::Format(TEXT("{0}.flaxproj"), codeName);
@@ -454,7 +454,7 @@ int32 Editor::LoadProduct()
" // Reference the modules for game\n"
" Modules.Add(\"Game\");\n"
" }\n"
"}\n"), Encoding::Unicode);
"}\n"), Encoding::UTF8);
failed |= File::WriteAllText(projectPath / TEXT("Source/GameEditorTarget.Build.cs"),TEXT(
"using Flax.Build;\n"
"\n"
@@ -468,7 +468,7 @@ int32 Editor::LoadProduct()
" // Reference the modules for editor\n"
" Modules.Add(\"Game\");\n"
" }\n"
"}\n"), Encoding::Unicode);
"}\n"), Encoding::UTF8);
failed |= File::WriteAllText(projectPath / TEXT("Source/Game/Game.Build.cs"),TEXT(
"using Flax.Build;\n"
"using Flax.Build.NativeCpp;\n"
@@ -496,7 +496,7 @@ int32 Editor::LoadProduct()
" // To add C++ define use: options.PublicDefinitions.Add(\"COMPILE_WITH_FLAX\");\n"
" // To learn more see scripting documentation.\n"
" }\n"
"}\n"), Encoding::Unicode);
"}\n"), Encoding::UTF8);
if (failed)
return 12;
}

View File

@@ -27,6 +27,17 @@ namespace FlaxEditor.GUI
/// </summary>
public ScriptType Type => _type;
/// <summary>
/// Initializes a new instance of the <see cref="TypeItemView"/> class.
/// </summary>
public TypeItemView()
{
_type = ScriptType.Null;
Name = "<null>";
TooltipText = "Unset value.";
Tag = _type;
}
/// <summary>
/// Initializes a new instance of the <see cref="TypeItemView"/> class.
/// </summary>
@@ -83,6 +94,7 @@ namespace FlaxEditor.GUI
// TODO: use async thread to search types without UI stall
var allTypes = Editor.Instance.CodeEditing.All.Get();
AddItem(new TypeItemView());
for (int i = 0; i < allTypes.Count; i++)
{
var type = allTypes[i];

View File

@@ -12,19 +12,98 @@ namespace FlaxEditor.Gizmo
[HideInEditor]
public class GridGizmo : GizmoBase
{
private bool _enabled = true;
[HideInEditor]
private sealed class Renderer : PostProcessEffect
{
private IntPtr _debugDrawContext;
public Renderer()
{
Order = -100;
UseSingleTarget = true;
Location = PostProcessEffectLocation.BeforeForwardPass;
}
~Renderer()
{
if (_debugDrawContext != IntPtr.Zero)
{
DebugDraw.FreeContext(_debugDrawContext);
_debugDrawContext = IntPtr.Zero;
}
}
public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output)
{
Profiler.BeginEventGPU("Editor Grid");
if (_debugDrawContext == IntPtr.Zero)
_debugDrawContext = DebugDraw.AllocateContext();
DebugDraw.SetContext(_debugDrawContext);
DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Mathf.Max(Engine.FramesPerSecond, 1));
var viewPos = (Vector3)renderContext.View.Position;
var plane = new Plane(Vector3.Zero, Vector3.UnitY);
var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
float space, size;
if (dst <= 500.0f)
{
space = 50;
size = 8000;
}
else if (dst <= 2000.0f)
{
space = 100;
size = 8000;
}
else
{
space = 1000;
size = 100000;
}
Color color = Color.Gray * 0.7f;
int count = (int)(size / space);
Vector3 start = new Vector3(0, 0, size * -0.5f);
Vector3 end = new Vector3(0, 0, size * 0.5f);
for (int i = 0; i <= count; i++)
{
start.X = end.X = i * space + start.Z;
DebugDraw.DrawLine(start, end, color);
}
start = new Vector3(size * -0.5f, 0, 0);
end = new Vector3(size * 0.5f, 0, 0);
for (int i = 0; i <= count; i++)
{
start.Z = end.Z = i * space + start.X;
DebugDraw.DrawLine(start, end, color);
}
DebugDraw.Draw(ref renderContext, input.View(), null, true);
DebugDraw.SetContext(IntPtr.Zero);
Profiler.EndEventGPU();
}
}
private Renderer _renderer;
/// <summary>
/// Gets or sets a value indicating whether this <see cref="GridGizmo"/> is enabled.
/// </summary>
public bool Enabled
{
get => _enabled;
get => _renderer.Enabled;
set
{
if (_enabled != value)
if (_renderer.Enabled != value)
{
_enabled = value;
_renderer.Enabled = value;
EnabledChanged?.Invoke(this);
}
}
@@ -42,55 +121,16 @@ namespace FlaxEditor.Gizmo
public GridGizmo(IGizmoOwner owner)
: base(owner)
{
_renderer = new Renderer();
owner.RenderTask.AddCustomPostFx(_renderer);
}
/// <inheritdoc />
public override void Draw(ref RenderContext renderContext)
/// <summary>
/// Destructor.
/// </summary>
~GridGizmo()
{
if (!Enabled)
return;
var viewPos = Owner.ViewPosition;
var plane = new Plane(Vector3.Zero, Vector3.UnitY);
var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
float space, size;
if (dst <= 500.0f)
{
space = 50;
size = 8000;
}
else if (dst <= 2000.0f)
{
space = 100;
size = 8000;
}
else
{
space = 1000;
size = 100000;
}
Color color = Color.Gray * 0.7f;
int count = (int)(size / space);
Vector3 start = new Vector3(0, 0, size * -0.5f);
Vector3 end = new Vector3(0, 0, size * 0.5f);
for (int i = 0; i <= count; i++)
{
start.X = end.X = i * space + start.Z;
DebugDraw.DrawLine(start, end, color);
}
start = new Vector3(size * -0.5f, 0, 0);
end = new Vector3(size * 0.5f, 0, 0);
for (int i = 0; i <= count; i++)
{
start.Z = end.Z = i * space + start.X;
DebugDraw.DrawLine(start, end, color);
}
FlaxEngine.Object.Destroy(ref _renderer);
}
}
}

View File

@@ -20,7 +20,7 @@ namespace FlaxEditor
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
@@ -44,7 +44,7 @@ namespace FlaxEditor
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{

View File

@@ -112,6 +112,7 @@ public:
{
Version = ::Version(1, 0);
DefaultSceneSpawn = Ray(Vector3::Zero, Vector3::Forward);
DefaultScene = Guid::Empty;
}
/// <summary>

View File

@@ -832,7 +832,7 @@ namespace FlaxEditor.Scripting
get
{
if (_managed != null)
return _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null) != null;
return _managed.IsValueType || _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null) != null;
return _custom?.CanCreateInstance ?? false;
}
}
@@ -893,9 +893,16 @@ namespace FlaxEditor.Scripting
{
if (_managed != null)
{
var ctor = _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
object value = RuntimeHelpers.GetUninitializedObject(_managed);
ctor.Invoke(value, null);
if (!_managed.IsValueType)
{
var ctor = _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
#if !BUILD_RELEASE
if (ctor == null)
throw new Exception($"Missing empty constructor for type {_managed.FullName}.");
#endif
ctor.Invoke(value, null);
}
return value;
}
return _custom.CreateInstance();

View File

@@ -613,6 +613,28 @@ bool ScriptsBuilderService::Init()
const String targetOutput = Globals::ProjectFolder / TEXT("Binaries") / target / platform / architecture / configuration;
Array<String> files;
FileSystem::DirectoryGetFiles(files, targetOutput, TEXT("*.HotReload.*"), DirectorySearchOption::TopDirectoryOnly);
for (const auto& reference : Editor::Project->References)
{
if (reference.Project->Name == TEXT("Flax"))
continue;
String referenceTarget;
if (reference.Project->EditorTarget.HasChars())
{
referenceTarget = reference.Project->EditorTarget.Get();
}
else if (reference.Project->GameTarget.HasChars())
{
referenceTarget = reference.Project->GameTarget.Get();
}
if (referenceTarget.IsEmpty())
continue;
const String referenceTargetOutput = reference.Project->ProjectFolderPath / TEXT("Binaries") / referenceTarget / platform / architecture / configuration;
FileSystem::DirectoryGetFiles(files, referenceTargetOutput, TEXT("*.HotReload.*"), DirectorySearchOption::TopDirectoryOnly);
}
if (files.HasItems())
LOG(Info, "Removing {0} files from previous Editor run hot-reloads", files.Count());
for (auto& file : files)

View File

@@ -1125,7 +1125,9 @@ namespace FlaxEditor.Utilities
public static string GetAssetNamePathWithExt(string path)
{
var projectFolder = Globals.ProjectFolder;
if (path.StartsWith(projectFolder))
if (path == projectFolder)
path = string.Empty;
else if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
return path;
}

View File

@@ -1168,14 +1168,15 @@ namespace FlaxEditor.Viewport
{
offset = Float2.Zero;
}
offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X);
offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
_mouseDelta = offset / size;
_mouseDelta.Y *= size.Y / size.X;
var mouseDelta = Float2.Zero;
if (_useMouseFiltering)
{
offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X);
offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
_mouseDelta = offset / size;
_mouseDelta.Y *= size.Y / size.X;
// Update delta filtering buffer
_deltaFilteringBuffer[_deltaFilteringStep] = _mouseDelta;
_deltaFilteringStep++;
@@ -1192,6 +1193,8 @@ namespace FlaxEditor.Viewport
}
else
{
_mouseDelta = offset / size;
_mouseDelta.Y *= size.Y / size.X;
mouseDelta = _mouseDelta;
}

View File

@@ -3,8 +3,6 @@
using System;
using FlaxEditor.GUI.ContextMenu;
using FlaxEngine;
using FlaxEditor.GUI.Input;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews
@@ -15,11 +13,10 @@ namespace FlaxEditor.Viewport.Previews
/// <seealso cref="AssetPreview" />
public class AnimatedModelPreview : AssetPreview
{
private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
private bool _showNodes, _showBounds, _showFloor, _showCurrentLOD, _showNodesNames;
private AnimatedModel _previewModel;
private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
private bool _showNodes, _showBounds, _showFloor, _showNodesNames;
private StaticModel _floorModel;
private ContextMenuButton _showCurrentLODButton;
private bool _playAnimation, _playAnimationOnce;
private float _playSpeed = 1.0f;
@@ -207,26 +204,6 @@ namespace FlaxEditor.Viewport.Previews
// Show Floor
_showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor);
_showFloorButton.IndexInParent = 1;
// Show Current LOD
_showCurrentLODButton = ViewWidgetShowMenu.AddButton("Current LOD", button =>
{
_showCurrentLOD = !_showCurrentLOD;
_showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
});
_showCurrentLODButton.IndexInParent = 2;
// Preview LOD
{
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
previewLOD.CloseMenuOnClick = false;
var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f)
{
Parent = previewLOD
};
previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD;
}
}
// Enable shadows
@@ -339,44 +316,6 @@ namespace FlaxEditor.Viewport.Previews
_previewModel.ResetAnimation();
}
private int ComputeLODIndex(SkinnedModel model)
{
if (PreviewActor.ForcedLOD != -1)
return PreviewActor.ForcedLOD;
// Based on RenderTools::ComputeModelLOD
CreateProjectionMatrix(out var projectionMatrix);
float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22);
var sphere = PreviewActor.Sphere;
var viewOrigin = ViewPosition;
var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin);
var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr);
// Check if model is being culled
if (Mathf.Square(model.MinScreenSize * 0.5f) > screenRadiusSquared)
return -1;
// Skip if no need to calculate LOD
if (model.LoadedLODs == 0)
return -1;
var lods = model.LODs;
if (lods.Length == 0)
return -1;
if (lods.Length == 1)
return 0;
// Iterate backwards and return the first matching LOD
for (int lodIndex = lods.Length - 1; lodIndex >= 0; lodIndex--)
{
if (Mathf.Square(lods[lodIndex].ScreenSize * 0.5f) >= screenRadiusSquared)
{
return lodIndex + PreviewActor.LODBias;
}
}
return 0;
}
/// <inheritdoc />
protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext)
{
@@ -440,45 +379,6 @@ namespace FlaxEditor.Viewport.Previews
}
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
var skinnedModel = _previewModel.SkinnedModel;
if (skinnedModel == null || !skinnedModel.IsLoaded)
return;
var lods = skinnedModel.LODs;
if (lods.Length == 0)
{
// Force show skeleton for models without geometry
ShowNodes = true;
return;
}
if (_showCurrentLOD)
{
var lodIndex = ComputeLODIndex(skinnedModel);
string text = string.Format("Current LOD: {0}", lodIndex);
if (lodIndex != -1)
{
lodIndex = Mathf.Clamp(lodIndex + PreviewActor.LODBias, 0, lods.Length - 1);
var lod = lods[lodIndex];
int triangleCount = 0, vertexCount = 0;
for (int meshIndex = 0; meshIndex < lod.Meshes.Length; meshIndex++)
{
var mesh = lod.Meshes[meshIndex];
triangleCount += mesh.TriangleCount;
vertexCount += mesh.VertexCount;
}
text += string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}", triangleCount, vertexCount);
}
var font = Style.Current.FontMedium;
var pos = new Float2(10, 50);
Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black);
Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White);
}
}
/// <inheritdoc />
public override void Update(float deltaTime)
{
@@ -498,14 +398,21 @@ namespace FlaxEditor.Viewport.Previews
}
}
/// <summary>
/// Resets the camera to focus on a object.
/// </summary>
public void ResetCamera()
{
ViewportCamera.SetArcBallView(_previewModel.Box);
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.F:
// Pay respect..
ViewportCamera.SetArcBallView(_previewModel.Box);
ResetCamera();
return true;
case KeyboardKeys.Spacebar:
PlayAnimation = !PlayAnimation;
@@ -525,7 +432,6 @@ namespace FlaxEditor.Viewport.Previews
_showNodesButton = null;
_showBoundsButton = null;
_showFloorButton = null;
_showCurrentLODButton = null;
_showNodesNamesButton = null;
base.OnDestroy();

View File

@@ -4,6 +4,8 @@ using System;
using FlaxEditor.Surface;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.GUI.ContextMenu;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews
@@ -46,6 +48,7 @@ namespace FlaxEditor.Viewport.Previews
private int _selectedModelIndex;
private Image _guiMaterialControl;
private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1];
private ContextMenu _modelWidgetButtonMenu;
/// <summary>
/// Gets or sets the material asset to preview. It can be <see cref="FlaxEngine.Material"/> or <see cref="FlaxEngine.MaterialInstance"/>.
@@ -95,20 +98,33 @@ namespace FlaxEditor.Viewport.Previews
Task.AddCustomActor(_previewModel);
// Create context menu for primitive switching
if (useWidgets && ViewWidgetButtonMenu != null)
if (useWidgets)
{
ViewWidgetButtonMenu.AddSeparator();
var modelSelect = ViewWidgetButtonMenu.AddChildMenu("Model").ContextMenu;
// Fill out all models
for (int i = 0; i < Models.Length; i++)
// Model mode widget
var modelMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_modelWidgetButtonMenu = new ContextMenu();
_modelWidgetButtonMenu.VisibleChanged += control =>
{
var button = modelSelect.AddButton(Models[i]);
button.Tag = i;
}
if (!control.Visible)
return;
_modelWidgetButtonMenu.ItemsContainer.DisposeChildren();
// Link the action
modelSelect.ButtonClicked += (button) => SelectedModelIndex = (int)button.Tag;
// Fill out all models
for (int i = 0; i < Models.Length; i++)
{
var index = i;
var button = _modelWidgetButtonMenu.AddButton(Models[index]);
button.ButtonClicked += _ => SelectedModelIndex = index;
button.Checked = SelectedModelIndex == index;
button.Tag = index;
}
};
new ViewportWidgetButton("Model", SpriteHandle.Invalid, _modelWidgetButtonMenu)
{
TooltipText = "Change material model",
Parent = modelMode,
};
modelMode.Parent = this;
}
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.Input;
using FlaxEngine;
using Object = FlaxEngine.Object;
@@ -56,25 +55,14 @@ namespace FlaxEditor.Viewport.Previews
// Link actors for rendering
Task.AddCustomActor(StaticModel);
Task.AddCustomActor(AnimatedModel);
}
if (useWidgets)
{
// Preview LOD
{
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
previewLOD.CloseMenuOnClick = false;
var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f)
{
Parent = previewLOD
};
previewLODValue.ValueChanged += () =>
{
StaticModel.ForcedLOD = previewLODValue.Value;
AnimatedModel.ForcedLOD = previewLODValue.Value;
};
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = StaticModel.ForcedLOD;
}
}
/// <summary>
/// Resets the camera to focus on a object.
/// </summary>
public void ResetCamera()
{
ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box);
}
private void OnBegin(RenderTask task, GPUContext context)
@@ -103,8 +91,7 @@ namespace FlaxEditor.Viewport.Previews
switch (key)
{
case KeyboardKeys.F:
// Pay respect..
ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box);
ResetCamera();
break;
}
return base.OnKeyDown(key);

View File

@@ -1,10 +1,10 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
using FlaxEditor.Viewport.Widgets;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews
@@ -16,10 +16,27 @@ namespace FlaxEditor.Viewport.Previews
public class ModelPreview : AssetPreview
{
private ContextMenuButton _showBoundsButton, _showCurrentLODButton, _showNormalsButton, _showTangentsButton, _showBitangentsButton, _showFloorButton;
private ContextMenu _previewLODsWidgetButtonMenu;
private StaticModel _previewModel, _floorModel;
private bool _showBounds, _showCurrentLOD, _showNormals, _showTangents, _showBitangents, _showFloor;
private MeshDataCache _meshDatas;
/// <summary>
/// Gets or sets a value that shows LOD statistics
/// </summary>
public bool ShowCurrentLOD
{
get => _showCurrentLOD;
set
{
if (_showCurrentLOD == value)
return;
_showCurrentLOD = value;
if (_showCurrentLODButton != null)
_showCurrentLODButton.Checked = value;
}
}
/// <summary>
/// Gets or sets the model asset to preview.
/// </summary>
@@ -198,17 +215,36 @@ namespace FlaxEditor.Viewport.Previews
});
_showCurrentLODButton.IndexInParent = 2;
// Preview LOD
// Preview LODs mode widget
var PreviewLODsMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_previewLODsWidgetButtonMenu = new ContextMenu();
_previewLODsWidgetButtonMenu.VisibleChanged += control =>
{
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
previewLOD.CloseMenuOnClick = false;
var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f)
if (!control.Visible)
return;
var model = _previewModel.Model;
if (model && !model.WaitForLoaded())
{
Parent = previewLOD
};
previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD;
}
_previewLODsWidgetButtonMenu.ItemsContainer.DisposeChildren();
var lods = model.LODs.Length;
for (int i = -1; i < lods; i++)
{
var index = i;
var button = _previewLODsWidgetButtonMenu.AddButton("LOD " + (index == -1 ? "Auto" : index));
button.ButtonClicked += _ => _previewModel.ForcedLOD = index;
button.Checked = _previewModel.ForcedLOD == index;
button.Tag = index;
if (lods <= 1)
break;
}
}
};
new ViewportWidgetButton("Preview LOD", SpriteHandle.Invalid, _previewLODsWidgetButtonMenu)
{
TooltipText = "Preview LOD properties",
Parent = PreviewLODsMode,
};
PreviewLODsMode.Parent = this;
}
}
@@ -346,8 +382,11 @@ namespace FlaxEditor.Viewport.Previews
if (_showCurrentLOD)
{
var asset = Model;
var lodIndex = ComputeLODIndex(asset, out var screenSize);
string text = string.Format("Current LOD: {0}\nScreen Size: {1:F2}", lodIndex, screenSize);
var lodIndex = ComputeLODIndex(asset, out var screenSize);
var auto = _previewModel.ForcedLOD == -1;
string text = auto ? "LOD Automatic" : "";
text += auto ? string.Format("\nScreen Size: {0:F2}", screenSize) : "";
text += string.Format("\nCurrent LOD: {0}", lodIndex);
if (lodIndex != -1)
{
var lods = asset.LODs;
@@ -369,14 +408,21 @@ namespace FlaxEditor.Viewport.Previews
}
}
/// <summary>
/// Resets the camera to focus on a object.
/// </summary>
public void ResetCamera()
{
ViewportCamera.SetArcBallView(_previewModel.Box);
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.F:
// Pay respect..
ViewportCamera.SetArcBallView(_previewModel.Box);
ResetCamera();
break;
}
return base.OnKeyDown(key);
@@ -389,6 +435,7 @@ namespace FlaxEditor.Viewport.Previews
Object.Destroy(ref _previewModel);
_showBoundsButton = null;
_showCurrentLODButton = null;
_previewLODsWidgetButtonMenu = null;
_showNormalsButton = null;
_showTangentsButton = null;
_showBitangentsButton = null;

View File

@@ -0,0 +1,177 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.ContextMenu;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEditor.Viewport.Widgets;
namespace FlaxEditor.Viewport.Previews
{
/// <summary>
/// Animation asset preview editor viewport.
/// </summary>
/// <seealso cref="AnimatedModelPreview" />
public class SkinnedModelPreview : AnimatedModelPreview
{
private bool _showCurrentLOD;
private ContextMenuButton _showCurrentLODButton;
private ContextMenu _previewLODsWidgetButtonMenu;
/// <summary>
/// Gets or sets a value that shows LOD statistics
/// </summary>
public bool ShowCurrentLOD
{
get => _showCurrentLOD;
set
{
if (_showCurrentLOD == value)
return;
_showCurrentLOD = value;
if (_showCurrentLODButton != null)
_showCurrentLODButton.Checked = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SkinnedModelPreview"/> class.
/// </summary>
/// <param name="useWidgets">if set to <c>true</c> use widgets.</param>
public SkinnedModelPreview(bool useWidgets)
: base(useWidgets)
{
if (useWidgets)
{
// Show Current LOD
_showCurrentLODButton = ViewWidgetShowMenu.AddButton("Current LOD", button =>
{
_showCurrentLOD = !_showCurrentLOD;
_showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
});
_showCurrentLODButton.IndexInParent = 2;
// PreviewLODS mode widget
var PreviewLODSMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_previewLODsWidgetButtonMenu = new ContextMenu();
_previewLODsWidgetButtonMenu.VisibleChanged += control =>
{
if (!control.Visible)
return;
var skinned = PreviewActor.SkinnedModel;
if (skinned && !skinned.WaitForLoaded())
{
_previewLODsWidgetButtonMenu.ItemsContainer.DisposeChildren();
var lods = skinned.LODs.Length;
for (int i = -1; i < lods; i++)
{
var index = i;
var button = _previewLODsWidgetButtonMenu.AddButton("LOD " + (index == -1 ? "Auto" : index));
button.ButtonClicked += (button) => PreviewActor.ForcedLOD = index;
button.Checked = PreviewActor.ForcedLOD == index;
button.Tag = index;
if (lods <= 1)
break;
}
}
};
new ViewportWidgetButton("Preview LOD", SpriteHandle.Invalid, _previewLODsWidgetButtonMenu)
{
TooltipText = "Preview LOD properties",
Parent = PreviewLODSMode,
};
PreviewLODSMode.Parent = this;
}
}
private int ComputeLODIndex(SkinnedModel model, out float screenSize)
{
screenSize = 1.0f;
if (PreviewActor.ForcedLOD != -1)
return PreviewActor.ForcedLOD;
// Based on RenderTools::ComputeModelLOD
CreateProjectionMatrix(out var projectionMatrix);
float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22);
var sphere = PreviewActor.Sphere;
var viewOrigin = ViewPosition;
var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin);
var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr);
screenSize = Mathf.Sqrt((float)screenRadiusSquared) * 2.0f;
// Check if model is being culled
if (Mathf.Square(model.MinScreenSize * 0.5f) > screenRadiusSquared)
return -1;
// Skip if no need to calculate LOD
if (model.LoadedLODs == 0)
return -1;
var lods = model.LODs;
if (lods.Length == 0)
return -1;
if (lods.Length == 1)
return 0;
// Iterate backwards and return the first matching LOD
for (int lodIndex = lods.Length - 1; lodIndex >= 0; lodIndex--)
{
if (Mathf.Square(lods[lodIndex].ScreenSize * 0.5f) >= screenRadiusSquared)
{
return lodIndex + PreviewActor.LODBias;
}
}
return 0;
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
var skinnedModel = PreviewActor.SkinnedModel;
if (skinnedModel == null || !skinnedModel.IsLoaded)
return;
var lods = skinnedModel.LODs;
if (lods.Length == 0)
{
// Force show skeleton for models without geometry
ShowNodes = true;
return;
}
if (_showCurrentLOD)
{
var lodIndex = ComputeLODIndex(skinnedModel, out var screenSize);
var auto = PreviewActor.ForcedLOD == -1;
string text = auto ? "LOD Automatic" : "";
text += auto ? string.Format("\nScreen Size: {0:F2}", screenSize) : "";
text += string.Format("\nCurrent LOD: {0}", lodIndex);
if (lodIndex != -1)
{
lodIndex = Mathf.Clamp(lodIndex + PreviewActor.LODBias, 0, lods.Length - 1);
var lod = lods[lodIndex];
int triangleCount = 0, vertexCount = 0;
for (int meshIndex = 0; meshIndex < lod.Meshes.Length; meshIndex++)
{
var mesh = lod.Meshes[meshIndex];
triangleCount += mesh.TriangleCount;
vertexCount += mesh.VertexCount;
}
text += string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}", triangleCount, vertexCount);
}
var font = Style.Current.FontMedium;
var pos = new Float2(10, 50);
Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black);
Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White);
}
}
/// <inheritdoc />
public override void OnDestroy()
{
_showCurrentLODButton = null;
_previewLODsWidgetButtonMenu = null;
base.OnDestroy();
}
}
}

View File

@@ -182,6 +182,7 @@ namespace FlaxEditor.Windows.Assets
{
// Toolstrip
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole collision");
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more");
// Split Panel

View File

@@ -779,6 +779,7 @@ namespace FlaxEditor.Windows.Assets
private MeshDataCache _meshData;
private ModelImportSettings _importSettings = new ModelImportSettings();
private float _backfacesThreshold = 0.6f;
private ToolStripButton _showCurrentLODButton;
/// <inheritdoc />
public ModelWindow(Editor editor, AssetItem item)
@@ -786,6 +787,9 @@ namespace FlaxEditor.Windows.Assets
{
// Toolstrip
_toolstrip.AddSeparator();
_showCurrentLODButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64, () => _preview.ShowCurrentLOD = !_preview.ShowCurrentLOD).LinkTooltip("Show LOD statistics");
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole model");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/models/index.html")).LinkTooltip("See documentation to learn more");
// Model preview
@@ -869,6 +873,8 @@ namespace FlaxEditor.Windows.Assets
}
}
_showCurrentLODButton.Checked = _preview.ShowCurrentLOD;
base.Update(deltaTime);
}
@@ -946,6 +952,7 @@ namespace FlaxEditor.Windows.Assets
base.OnDestroy();
Object.Destroy(ref _highlightActor);
_showCurrentLODButton = null;
}
}
}

View File

@@ -31,7 +31,7 @@ namespace FlaxEditor.Windows.Assets
/// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" />
public sealed class SkinnedModelWindow : ModelBaseWindow<SkinnedModel, SkinnedModelWindow>
{
private sealed class Preview : AnimatedModelPreview
private sealed class Preview : SkinnedModelPreview
{
private readonly SkinnedModelWindow _window;
@@ -1105,6 +1105,7 @@ namespace FlaxEditor.Windows.Assets
private Preview _preview;
private AnimatedModel _highlightActor;
private ToolStripButton _showNodesButton;
private ToolStripButton _showCurrentLODButton;
private MeshData[][] _meshDatas;
private bool _meshDatasInProgress;
@@ -1116,7 +1117,9 @@ namespace FlaxEditor.Windows.Assets
{
// Toolstrip
_toolstrip.AddSeparator();
_showCurrentLODButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64, () => _preview.ShowCurrentLOD = !_preview.ShowCurrentLOD).LinkTooltip("Show LOD statistics");
_showNodesButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Bone64, () => _preview.ShowNodes = !_preview.ShowNodes).LinkTooltip("Show animated model nodes debug view");
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole model");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/skinned-model/index.html")).LinkTooltip("See documentation to learn more");
@@ -1265,6 +1268,7 @@ namespace FlaxEditor.Windows.Assets
}
}
_showCurrentLODButton.Checked = _preview.ShowCurrentLOD;
_showNodesButton.Checked = _preview.ShowNodes;
base.Update(deltaTime);
@@ -1349,6 +1353,7 @@ namespace FlaxEditor.Windows.Assets
Object.Destroy(ref _highlightActor);
_preview = null;
_showNodesButton = null;
_showCurrentLODButton = null;
}
}
}

View File

@@ -304,6 +304,6 @@ void SplashScreen::OnFontLoaded(Asset* asset)
// Create fonts
const float s = _dpiScale;
_titleFont = font->CreateFont((uint32)(35 * s));
_subtitleFont = font->CreateFont((uint32)(9 * s));
_titleFont = font->CreateFont(35 * s);
_subtitleFont = font->CreateFont(9 * s);
}

View File

@@ -10,7 +10,7 @@
/// <summary>
/// Assets Importing service allows to import or create new assets
/// </summary>
class AssetsExportingManager
class FLAXENGINE_API AssetsExportingManager
{
public:
/// <summary>

View File

@@ -26,7 +26,7 @@ typedef Function<ExportAssetResult(ExportAssetContext&)> ExportAssetFunction;
/// <summary>
/// Exporting asset context structure
/// </summary>
class ExportAssetContext : public NonCopyable
class FLAXENGINE_API ExportAssetContext : public NonCopyable
{
public:
/// <summary>

View File

@@ -9,7 +9,7 @@
/// <summary>
/// Assets Importing service allows to import or create new assets
/// </summary>
class AssetsImportingManager
class FLAXENGINE_API AssetsImportingManager
{
public:
/// <summary>

View File

@@ -28,7 +28,7 @@ typedef Function<CreateAssetResult(CreateAssetContext&)> CreateAssetFunction;
/// <summary>
/// Importing/creating asset context structure
/// </summary>
class CreateAssetContext : public NonCopyable
class FLAXENGINE_API CreateAssetContext : public NonCopyable
{
private:
CreateAssetResult _applyChangesResult;

View File

@@ -357,8 +357,8 @@ public:
{
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
if (i->Value)
::Delete(i->Value);
if (i->Item)
::Delete(i->Item);
}
Clear();
}

View File

@@ -4,4 +4,4 @@
#include "Enums.h"
DECLARE_ENUM_3(Encoding, ANSI, Unicode, UnicodeBigEndian);
DECLARE_ENUM_4(Encoding, ANSI, Unicode, UnicodeBigEndian, UTF8);

View File

@@ -12,6 +12,7 @@
/// </summary>
API_STRUCT(InBuild) struct FLAXENGINE_API BoundingFrustum
{
friend CollisionsHelper;
private:
Matrix _matrix;

View File

@@ -939,7 +939,6 @@ bool CollisionsHelper::RayIntersectsSphere(const Ray& ray, const BoundingSphere&
normal = Vector3::Up;
return false;
}
const Vector3 point = ray.Position + ray.Direction * distance;
normal = Vector3::Normalize(point - sphere.Center);
return true;
@@ -953,22 +952,17 @@ bool CollisionsHelper::RayIntersectsSphere(const Ray& ray, const BoundingSphere&
point = Vector3::Zero;
return false;
}
point = ray.Position + ray.Direction * distance;
return true;
}
PlaneIntersectionType CollisionsHelper::PlaneIntersectsPoint(const Plane& plane, const Vector3& point)
{
Real distance = Vector3::Dot(plane.Normal, point);
distance += plane.D;
const Real distance = Vector3::Dot(plane.Normal, point) + plane.D;
if (distance > Plane::DistanceEpsilon)
return PlaneIntersectionType::Front;
if (distance < Plane::DistanceEpsilon)
return PlaneIntersectionType::Back;
return PlaneIntersectionType::Intersecting;
}
@@ -1169,7 +1163,6 @@ ContainmentType CollisionsHelper::SphereContainsPoint(const BoundingSphere& sphe
{
if (Vector3::DistanceSquared(point, sphere.Center) <= sphere.Radius * sphere.Radius)
return ContainmentType::Contains;
return ContainmentType::Disjoint;
}
@@ -1254,13 +1247,10 @@ ContainmentType CollisionsHelper::SphereContainsBox(const BoundingSphere& sphere
ContainmentType CollisionsHelper::SphereContainsSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2)
{
const Real distance = Vector3::Distance(sphere1.Center, sphere2.Center);
if (sphere1.Radius + sphere2.Radius < distance)
return ContainmentType::Disjoint;
if (sphere1.Radius - sphere2.Radius < distance)
return ContainmentType::Intersects;
return ContainmentType::Contains;
}
@@ -1274,7 +1264,8 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
auto result = ContainmentType::Contains;
for (int32 i = 0; i < 6; i++)
{
Plane plane = frustum.GetPlane(i);
Plane plane = frustum._planes[i];
Vector3 p = box.Minimum;
if (plane.Normal.X >= 0)
p.X = box.Maximum.X;
@@ -1282,7 +1273,7 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
p.Y = box.Maximum.Y;
if (plane.Normal.Z >= 0)
p.Z = box.Maximum.Z;
if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back)
if (Vector3::Dot(plane.Normal, p) + plane.D < Plane::DistanceEpsilon)
return ContainmentType::Disjoint;
p = box.Maximum;
@@ -1292,7 +1283,7 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
p.Y = box.Minimum.Y;
if (plane.Normal.Z >= 0)
p.Z = box.Minimum.Z;
if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back)
if (Vector3::Dot(plane.Normal, p) + plane.D < Plane::DistanceEpsilon)
result = ContainmentType::Intersects;
}
return result;

View File

@@ -36,6 +36,32 @@ namespace AllocatorExt
return result;
}
/// <summary>
/// Reallocates block of the memory.
/// </summary>
/// </summary>
/// <param name="ptr">A pointer to the memory block to reallocate.</param>
/// <param name="newSize">The size of the new allocation (in bytes).</param>
/// <param name="alignment">The memory alignment (in bytes). Must be an integer power of 2.</param>
/// <returns>The pointer to the allocated chunk of the memory. The pointer is a multiple of alignment.</returns>
inline void* ReallocAligned(void* ptr, uint64 newSize, uint64 alignment)
{
if (newSize == 0)
{
Allocator::Free(ptr);
return nullptr;
}
if (!ptr)
return Allocator::Allocate(newSize, alignment);
void* result = Allocator::Allocate(newSize, alignment);
if (result)
{
Platform::MemoryCopy(result, ptr, newSize);
Allocator::Free(ptr);
}
return result;
}
/// <summary>
/// Reallocates block of the memory.
/// </summary>

View File

@@ -114,3 +114,14 @@ inline Span<T> ToSpan(const T* ptr, int32 length)
{
return Span<T>(ptr, length);
}
template<typename T>
inline bool SpanContains(const Span<T> span, const T& value)
{
for (int32 i = 0; i < span.Length(); i++)
{
if (span.Get()[i] == value)
return true;
}
return false;
}

View File

@@ -73,7 +73,7 @@ void String::Set(const char* chars, int32 length)
_length = length;
}
if (chars)
StringUtils::ConvertANSI2UTF16(chars, _data, length);
StringUtils::ConvertANSI2UTF16(chars, _data, length, _length);
}
void String::SetUTF8(const char* chars, int32 length)
@@ -112,7 +112,7 @@ void String::Append(const char* chars, int32 count)
_data = (Char*)Platform::Allocate((_length + 1) * sizeof(Char), 16);
Platform::MemoryCopy(_data, oldData, oldLength * sizeof(Char));
StringUtils::ConvertANSI2UTF16(chars, _data + oldLength, count * sizeof(Char));
StringUtils::ConvertANSI2UTF16(chars, _data + oldLength, count, _length);
_data[_length] = 0;
Platform::Free(oldData);

View File

@@ -125,7 +125,8 @@ public:
const int32 length = str && *str ? StringUtils::Length(str) : 0;
const int32 prevCnt = _data.Count();
_data.AddDefault(length);
StringUtils::ConvertANSI2UTF16(str, _data.Get() + prevCnt, length);
int32 tmp;
StringUtils::ConvertANSI2UTF16(str, _data.Get() + prevCnt, length, tmp);
return *this;
}

View File

@@ -660,7 +660,8 @@ Variant::Variant(const StringAnsiView& v)
const int32 length = v.Length() * sizeof(Char) + 2;
AsBlob.Data = Allocator::Allocate(length);
AsBlob.Length = length;
StringUtils::ConvertANSI2UTF16(v.Get(), (Char*)AsBlob.Data, v.Length());
int32 tmp;
StringUtils::ConvertANSI2UTF16(v.Get(), (Char*)AsBlob.Data, v.Length(), tmp);
((Char*)AsBlob.Data)[v.Length()] = 0;
}
else
@@ -2578,7 +2579,8 @@ void Variant::SetString(const StringAnsiView& str)
AsBlob.Data = Allocator::Allocate(length);
AsBlob.Length = length;
}
StringUtils::ConvertANSI2UTF16(str.Get(), (Char*)AsBlob.Data, str.Length());
int32 tmp;
StringUtils::ConvertANSI2UTF16(str.Get(), (Char*)AsBlob.Data, str.Length(), tmp);
((Char*)AsBlob.Data)[str.Length()] = 0;
}

View File

@@ -466,7 +466,7 @@ inline void DrawText3D(const DebugText3D& t, const RenderContext& renderContext,
Matrix::Multiply(fw, vp, m);
Render2D::Begin(context, target, depthBuffer, viewport, m);
const StringView text(t.Text.Get(), t.Text.Count() - 1);
Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, Vector2::Zero);
Render2D::DrawText(DebugDrawFont->CreateFont((float)t.Size), text, t.Color, Vector2::Zero);
Render2D::End();
}
@@ -777,7 +777,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
context->BindSR(0, renderContext.Buffers->DepthBuffer);
const bool enableDepthWrite = data.EnableDepthTest;
context->SetRenderTarget(depthBuffer ? depthBuffer : *renderContext.Buffers->DepthBuffer, target);
context->SetRenderTarget(depthBuffer ? depthBuffer : (data.EnableDepthTest ? nullptr : renderContext.Buffers->DepthBuffer->View()), target);
// Lines
if (depthTestLines.VertexCount)
@@ -859,12 +859,12 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
for (auto& t : Context->DebugDrawDefault.DefaultText2D)
{
const StringView text(t.Text.Get(), t.Text.Count() - 1);
Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, t.Position);
Render2D::DrawText(DebugDrawFont->CreateFont((float)t.Size), text, t.Color, t.Position);
}
for (auto& t : Context->DebugDrawDefault.OneFrameText2D)
{
const StringView text(t.Text.Get(), t.Text.Count() - 1);
Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, t.Position);
Render2D::DrawText(DebugDrawFont->CreateFont((float)t.Size), text, t.Color, t.Position);
}
Render2D::End();
}

View File

@@ -50,13 +50,13 @@ namespace FlaxEngine.Interop
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
public static ManagedArray WrapPooledArray(Array arr, Type arrayType)
{
ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * Marshal.SizeOf(arr.GetType().GetElementType()));
ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * NativeInterop.GetTypeSize(arr.GetType().GetElementType()));
managedArray.WrapArray(arr, arrayType);
return managedArray;
}
internal static ManagedArray AllocateNewArray(int length, Type arrayType, Type elementType)
=> new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, Marshal.SizeOf(elementType)), length, arrayType, elementType);
=> new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, NativeInterop.GetTypeSize(elementType)), length, arrayType, elementType);
internal static ManagedArray AllocateNewArray(IntPtr ptr, int length, Type arrayType, Type elementType)
=> new ManagedArray(ptr, length, arrayType, elementType);
@@ -86,7 +86,7 @@ namespace FlaxEngine.Interop
_length = arr.Length;
_arrayType = arrayType;
_elementType = arr.GetType().GetElementType();
_elementSize = Marshal.SizeOf(_elementType);
_elementSize = NativeInterop.GetTypeSize(_elementType);
}
internal void Allocate<T>(int length) where T : unmanaged
@@ -117,7 +117,7 @@ namespace FlaxEngine.Interop
_length = length;
_arrayType = arrayType;
_elementType = elementType;
_elementSize = Marshal.SizeOf(elementType);
_elementSize = NativeInterop.GetTypeSize(_elementType);
}
~ManagedArray()

View File

@@ -350,14 +350,14 @@ namespace FlaxEngine.Interop
#endif
public static class NativeToManaged
{
public static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements)
public static T[] AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements)
{
if (unmanaged is null)
return null;
return new T[numElements];
}
public static Span<T> GetManagedValuesDestination(T[]? managed) => managed;
public static Span<T> GetManagedValuesDestination(T[] managed) => managed;
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements)
{
@@ -390,7 +390,7 @@ namespace FlaxEngine.Interop
#endif
public static class ManagedToNative
{
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements)
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements)
{
if (managed is null)
{
@@ -402,7 +402,7 @@ namespace FlaxEngine.Interop
return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak);
}
public static ReadOnlySpan<T> GetManagedValuesSource(T[]? managed) => managed;
public static ReadOnlySpan<T> GetManagedValuesSource(T[] managed) => managed;
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
{
@@ -431,7 +431,7 @@ namespace FlaxEngine.Interop
ManagedArray unmanagedArray;
ManagedHandle handle;
public void FromManaged(T[]? managed)
public void FromManaged(T[] managed)
{
if (managed == null)
return;
@@ -476,7 +476,7 @@ namespace FlaxEngine.Interop
}
}
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements)
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements)
{
if (managed is null)
{
@@ -489,7 +489,7 @@ namespace FlaxEngine.Interop
return (TUnmanagedElement*)handle;
}
public static ReadOnlySpan<T> GetManagedValuesSource(T[]? managed) => managed;
public static ReadOnlySpan<T> GetManagedValuesSource(T[] managed) => managed;
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
{
@@ -499,9 +499,9 @@ namespace FlaxEngine.Interop
return unmanagedArray.ToSpan<TUnmanagedElement>();
}
public static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) => unmanaged is null ? null : new T[numElements];
public static T[] AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) => unmanaged is null ? null : new T[numElements];
public static Span<T> GetManagedValuesDestination(T[]? managed) => managed;
public static Span<T> GetManagedValuesDestination(T[] managed) => managed;
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements)
{

View File

@@ -885,6 +885,7 @@ namespace FlaxEngine.Interop
handle.Free();
fieldHandleCacheCollectible.Clear();
#endif
_typeSizeCache.Clear();
foreach (var pair in classAttributesCacheCollectible)
pair.Value.Free();

View File

@@ -46,6 +46,7 @@ namespace FlaxEngine.Interop
#endif
private static Dictionary<object, ManagedHandle> classAttributesCacheCollectible = new();
private static Dictionary<Assembly, ManagedHandle> assemblyHandles = new();
private static Dictionary<Type, int> _typeSizeCache = new();
private static Dictionary<string, IntPtr> loadedNativeLibraries = new();
internal static Dictionary<string, string> nativeLibraryPaths = new();
@@ -58,19 +59,8 @@ namespace FlaxEngine.Interop
if (!loadedNativeLibraries.TryGetValue(libraryName, out IntPtr nativeLibrary))
{
if (!nativeLibraryPaths.TryGetValue(libraryName, out var nativeLibraryPath))
{
nativeLibraryPath = libraryName;
// Check if any of the loaded assemblies has matching native module filename
foreach (var e in nativeLibraryPaths)
{
if (string.Equals(Path.GetFileNameWithoutExtension(e.Value), libraryName, StringComparison.Ordinal))
{
nativeLibraryPath = e.Value;
break;
}
}
}
nativeLibrary = NativeLibrary.Load(nativeLibraryPath, assembly, dllImportSearchPath);
loadedNativeLibraries.Add(libraryName, nativeLibrary);
assemblyOwnedNativeLibraries.Add(assembly, libraryName);
@@ -107,14 +97,17 @@ namespace FlaxEngine.Interop
}
#if FLAX_EDITOR
private static Assembly? OnScriptingAssemblyLoadContextResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName)
private static Assembly OnScriptingAssemblyLoadContextResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName)
{
// FIXME: There should be a better way to resolve the path to EditorTargetPath where the dependencies are stored
string editorTargetPath = Path.GetDirectoryName(nativeLibraryPaths.Keys.First(x => x != "FlaxEngine"));
foreach (string nativeLibraryPath in nativeLibraryPaths.Values)
{
string editorTargetPath = Path.GetDirectoryName(nativeLibraryPath);
var assemblyPath = Path.Combine(editorTargetPath, assemblyName.Name + ".dll");
if (File.Exists(assemblyPath))
return assemblyLoadContext.LoadFromAssemblyPath(assemblyPath);
var assemblyPath = Path.Combine(editorTargetPath, assemblyName.Name + ".dll");
if (File.Exists(assemblyPath))
return assemblyLoadContext.LoadFromAssemblyPath(assemblyPath);
}
return null;
}
#endif
@@ -592,7 +585,7 @@ namespace FlaxEngine.Interop
else if (fieldType.IsClass || fieldType.IsPointer)
fieldAlignment = IntPtr.Size;
else
fieldAlignment = Marshal.SizeOf(fieldType);
fieldAlignment = GetTypeSize(fieldType);
}
internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset)
@@ -1096,6 +1089,26 @@ namespace FlaxEngine.Interop
return handle;
}
internal static int GetTypeSize(Type type)
{
if (!_typeSizeCache.TryGetValue(type, out var size))
{
try
{
size = Marshal.SizeOf(type);
}
catch
{
// Workaround the issue where structure defined within generic type instance (eg. MyType<int>.MyStruct) fails to get size
// https://github.com/dotnet/runtime/issues/46426
var obj = Activator.CreateInstance(type);
size = Marshal.SizeOf(obj);
}
_typeSizeCache.Add(type, size);
}
return size;
}
private static class DelegateHelpers
{
#if USE_AOT

View File

@@ -145,7 +145,7 @@ bool GPUShader::Create(MemoryReadStream& stream)
// Create CB
#if GPU_ENABLE_RESOURCE_NAMING
String name = ToString() + TEXT(".CB") + i;
String name = String::Format(TEXT("{}.CB{}"), ToString(), i);
#else
String name;
#endif

View File

@@ -146,7 +146,7 @@ void AnimatedModel::SetCurrentPose(const Array<Matrix>& nodesTransformation, boo
Matrix invWorld;
Matrix::Invert(world, invWorld);
for (auto& m : GraphInstance.NodesPose)
m = invWorld * m;
m = m * invWorld;
}
OnAnimationUpdated();
}
@@ -774,7 +774,13 @@ void AnimatedModel::OnAnimationUpdated_Sync()
// Update synchronous stuff
UpdateSockets();
ApplyRootMotion(GraphInstance.RootMotion);
AnimationUpdated();
if (!_isDuringUpdateEvent)
{
// Prevent stack-overflow when gameplay modifies the pose within the event
_isDuringUpdateEvent = true;
AnimationUpdated();
_isDuringUpdateEvent = false;
}
}
void AnimatedModel::OnAnimationUpdated()

View File

@@ -67,6 +67,7 @@ private:
AnimationUpdateMode _actualMode;
uint32 _counter;
Real _lastMinDstSqr;
bool _isDuringUpdateEvent = false;
uint64 _lastUpdateFrame;
mutable MeshDeformation* _deformation = nullptr;
ScriptingObjectReference<AnimatedModel> _masterPose;

View File

@@ -740,16 +740,20 @@ Actor* FindActorRecursive(Actor* node, const Tag& tag)
return result;
}
void FindActorsRecursive(Actor* node, const Tag& tag, Array<Actor*>& result)
void FindActorsRecursive(Actor* node, const Tag& tag, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
if (node->HasTag(tag))
result.Add(node);
for (Actor* child : node->Children)
FindActorsRecursive(child, tag, result);
FindActorsRecursive(child, tag, activeOnly, result);
}
void FindActorsRecursiveByParentTags(Actor* node, const Array<Tag>& tags, Array<Actor*>& result)
void FindActorsRecursiveByParentTags(Actor* node, const Array<Tag>& tags, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
for (Tag tag : tags)
{
if (node->HasTag(tag))
@@ -759,7 +763,7 @@ void FindActorsRecursiveByParentTags(Actor* node, const Array<Tag>& tags, Array<
}
}
for (Actor* child : node->Children)
FindActorsRecursiveByParentTags(child, tags, result);
FindActorsRecursiveByParentTags(child, tags, activeOnly, result);
}
Actor* Level::FindActor(const Tag& tag, Actor* root)
@@ -785,24 +789,24 @@ void FindActorRecursive(Actor* node, const Tag& tag, Array<Actor*>& result)
FindActorRecursive(child, tag, result);
}
Array<Actor*> Level::FindActors(const Tag& tag, Actor* root)
Array<Actor*> Level::FindActors(const Tag& tag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
if (root)
{
FindActorsRecursive(root, tag, result);
FindActorsRecursive(root, tag, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursive(scene, tag, result);
FindActorsRecursive(scene, tag, activeOnly, result);
}
return result;
}
Array<Actor*> Level::FindActorsByParentTag(const Tag& parentTag, Actor* root)
Array<Actor*> Level::FindActorsByParentTag(const Tag& parentTag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
@@ -814,19 +818,19 @@ Array<Actor*> Level::FindActorsByParentTag(const Tag& parentTag, Actor* root)
}
if (subTags.Count() == 1)
{
result = FindActors(subTags[0], root);
result = FindActors(subTags[0], activeOnly, root);
return result;
}
if (root)
{
FindActorsRecursiveByParentTags(root, subTags, result);
FindActorsRecursiveByParentTags(root, subTags, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursiveByParentTags(scene, subTags, result);
FindActorsRecursiveByParentTags(scene, subTags, activeOnly, result);
}
return result;

View File

@@ -494,17 +494,19 @@ public:
/// Tries to find the actors with the given tag (returns all found).
/// </summary>
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="activeOnly">Find only active actors.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Found actors or empty if none.</returns>
API_FUNCTION() static Array<Actor*> FindActors(const Tag& tag, Actor* root = nullptr);
API_FUNCTION() static Array<Actor*> FindActors(const Tag& tag, const bool activeOnly = false, Actor* root = nullptr);
/// <summary>
/// Search actors using a parent parentTag.
/// </summary>
/// <param name="parentTag">The tag to search actors with subtags belonging to this tag</param>
/// <param name="activeOnly">Find only active actors.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Returns all actors that have subtags belonging to the given parent parentTag</returns>
API_FUNCTION() static Array<Actor*> FindActorsByParentTag(const Tag& parentTag, Actor* root = nullptr);
API_FUNCTION() static Array<Actor*> FindActorsByParentTag(const Tag& parentTag, const bool activeOnly = false, Actor* root = nullptr);
private:
// Actor API

View File

@@ -23,8 +23,8 @@ namespace FlaxEngine
if (_keyframes == null || _keyframes.Length != count)
_keyframes = new BezierCurve<Transform>.Keyframe[count];
#if !BUILD_RELEASE
if (Marshal.SizeOf(typeof(BezierCurve<Transform>.Keyframe)) != Transform.SizeInBytes * 3 + sizeof(float))
throw new Exception("Invalid size of BezierCurve keyframe " + Marshal.SizeOf(typeof(BezierCurve<Transform>.Keyframe)) + " bytes.");
if (System.Runtime.CompilerServices.Unsafe.SizeOf<BezierCurve<Transform>.Keyframe>() != Transform.SizeInBytes * 3 + sizeof(float))
throw new Exception("Invalid size of BezierCurve keyframe " + System.Runtime.CompilerServices.Unsafe.SizeOf<BezierCurve<Transform>.Keyframe>() + " bytes.");
#endif
Internal_GetKeyframes(__unmanagedPtr, _keyframes);
return _keyframes;

View File

@@ -23,7 +23,7 @@ void NetworkReplicationHierarchyUpdateResult::Init()
{
_clientsHaveLocation = false;
_clients.Resize(NetworkManager::Clients.Count());
_clientsMask = NetworkClientsMask();
_clientsMask = NetworkManager::Mode == NetworkManagerMode::Client ? NetworkClientsMask::All : NetworkClientsMask();
for (int32 i = 0; i < _clients.Count(); i++)
_clientsMask.SetBit(i);
_entries.Clear();
@@ -49,7 +49,7 @@ bool NetworkReplicationHierarchyUpdateResult::GetClientLocation(int32 clientInde
void NetworkReplicationNode::AddObject(NetworkReplicationHierarchyObject obj)
{
if (obj.ReplicationFPS > 0.0f)
if (obj.ReplicationFPS > ZeroTolerance) // > 0
{
// Randomize initial replication update to spread rep rates more evenly for large scenes that register all objects within the same frame
obj.ReplicationUpdatesLeft = NetworkReplicationNodeObjectCounter++ % Math::Clamp(Math::RoundToInt(NetworkManager::NetworkFPS / obj.ReplicationFPS), 1, 60);
@@ -63,6 +63,17 @@ bool NetworkReplicationNode::RemoveObject(ScriptingObject* obj)
return !Objects.Remove(obj);
}
bool NetworkReplicationNode::GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result)
{
const int32 index = Objects.Find(obj);
if (index != -1)
{
result = Objects[index];
return true;
}
return false;
}
bool NetworkReplicationNode::DirtyObject(ScriptingObject* obj)
{
const int32 index = Objects.Find(obj);
@@ -80,7 +91,11 @@ void NetworkReplicationNode::Update(NetworkReplicationHierarchyUpdateResult* res
const float networkFPS = NetworkManager::NetworkFPS / result->ReplicationScale;
for (NetworkReplicationHierarchyObject& obj : Objects)
{
if (obj.ReplicationFPS <= 0.0f)
if (obj.ReplicationFPS < -ZeroTolerance) // < 0
{
continue;
}
else if (obj.ReplicationFPS < ZeroTolerance) // == 0
{
// Always relevant
result->AddObject(obj.Object);
@@ -152,6 +167,7 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj
cell->MinCullDistance = obj.CullDistance;
}
cell->Node->AddObject(obj);
_objectToCell[obj.Object] = coord;
// Cache minimum culling distance for a whole cell to skip it at once
cell->MinCullDistance = Math::Min(cell->MinCullDistance, obj.CullDistance);
@@ -159,14 +175,35 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj
bool NetworkReplicationGridNode::RemoveObject(ScriptingObject* obj)
{
for (const auto& e : _children)
Int3 coord;
if (!_objectToCell.TryGet(obj, coord))
{
if (e.Value.Node->RemoveObject(obj))
{
// TODO: remove empty cells?
// TODO: update MinCullDistance for cell?
return true;
}
return false;
}
if (_children[coord].Node->RemoveObject(obj))
{
_objectToCell.Remove(obj);
// TODO: remove empty cells?
// TODO: update MinCullDistance for cell?
return true;
}
return false;
}
bool NetworkReplicationGridNode::GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result)
{
Int3 coord;
if (!_objectToCell.TryGet(obj, coord))
{
return false;
}
if (_children[coord].Node->GetObject(obj, result))
{
return true;
}
return false;
}

View File

@@ -20,7 +20,7 @@ API_STRUCT(NoDefault, Namespace = "FlaxEngine.Networking") struct FLAXENGINE_API
// The object to replicate.
API_FIELD() ScriptingObjectReference<ScriptingObject> Object;
// The target amount of the replication updates per second (frequency of the replication). Constrained by NetworkManager::NetworkFPS. Use 0 for 'always relevant' object.
// The target amount of the replication updates per second (frequency of the replication). Constrained by NetworkManager::NetworkFPS. Use 0 for 'always relevant' object and less than 0 (eg. -1) for 'never relevant' objects that would only get synched on client join once.
API_FIELD() float ReplicationFPS = 60;
// The minimum distance from the player to the object at which it can process replication. For example, players further away won't receive object data. Use 0 if unused.
API_FIELD() float CullDistance = 15000;
@@ -200,6 +200,14 @@ API_CLASS(Abstract, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API Ne
/// <returns>True on successful removal, otherwise false.</returns>
API_FUNCTION() virtual bool RemoveObject(ScriptingObject* obj);
/// <summary>
/// Gets object from the hierarchy.
/// </summary>
/// <param name="obj">The object to get.</param>
/// <param name="result">The hierarchy object to retrieve.</param>
/// <returns>True on successful retrieval, otherwise false.</returns>
API_FUNCTION() virtual bool GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result);
/// <summary>
/// Force replicates the object during the next update. Resets any internal tracking state to force the synchronization.
/// </summary>
@@ -238,6 +246,7 @@ private:
};
Dictionary<Int3, Cell> _children;
Dictionary<ScriptingObject*, Int3> _objectToCell;
public:
/// <summary>
@@ -247,6 +256,7 @@ public:
void AddObject(NetworkReplicationHierarchyObject obj) override;
bool RemoveObject(ScriptingObject* obj) override;
bool GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result) override;
void Update(NetworkReplicationHierarchyUpdateResult* result) override;
};

View File

@@ -700,9 +700,9 @@ void NetworkReplicator::AddRPC(const ScriptingTypeHandle& typeHandle, const Stri
NetworkRpcInfo::RPCsTable[rpcName] = rpcInfo;
}
void NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds)
bool NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds)
{
EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan<uint32>(targetIds));
return EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan<uint32>(targetIds));
}
StringAnsiView NetworkReplicator::GetCSharpCachedName(const StringAnsiView& name)
@@ -1113,12 +1113,12 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC()
return CachedWriteStream;
}
void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds)
bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds)
{
Scripting::ObjectsLookupIdMapping.Set(nullptr);
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name));
if (!info || !obj || NetworkManager::IsOffline())
return;
return false;
ObjectsLock.Lock();
auto& rpc = RpcQueue.AddOne();
rpc.Object = obj;
@@ -1135,12 +1135,23 @@ void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa
}
#endif
ObjectsLock.Unlock();
// Check if skip local execution (eg. server rpc called from client or client rpc with specific targets)
const NetworkManagerMode networkMode = NetworkManager::Mode;
if (info->Server && networkMode == NetworkManagerMode::Client)
return true;
if (info->Client && networkMode == NetworkManagerMode::Server)
return true;
if (info->Client && networkMode == NetworkManagerMode::Host && targetIds.IsValid() && !SpanContains(targetIds, NetworkManager::LocalClientId))
return true;
return false;
}
void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client)
{
ScopeLock lock(ObjectsLock);
NewClients.Add(client);
ASSERT(sizeof(NetworkClientsMask) * 8 >= (uint32)NetworkManager::Clients.Count()); // Ensure that clients mask can hold all of clients
}
void NetworkInternal::NetworkReplicatorClientDisconnected(NetworkClient* client)
@@ -1193,6 +1204,7 @@ void NetworkInternal::NetworkReplicatorClear()
Objects.Remove(it);
}
}
Objects.Clear();
RpcQueue.Clear();
SpawnQueue.Clear();
DespawnQueue.Clear();

View File

@@ -120,10 +120,11 @@ namespace FlaxEngine.Networking
/// <param name="name">The RPC name.</param>
/// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param>
/// <param name="targetIds">Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.</param>
/// <returns>True if RPC cannot be executed locally, false if execute it locally too (checks RPC mode and target client ids).</returns>
[Unmanaged]
public static void EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream, uint[] targetIds = null)
public static bool EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream, uint[] targetIds = null)
{
Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream), targetIds);
return Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream), targetIds);
}
/// <summary>

View File

@@ -195,13 +195,14 @@ public:
/// <param name="name">The RPC name.</param>
/// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param>
/// <param name="targetIds">Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.</param>
static void EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds = Span<uint32>());
/// <returns>True if RPC cannot be executed locally, false if execute it locally too (checks RPC mode and target client ids).</returns>
static bool EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds = Span<uint32>());
private:
#if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& typeHandle, const Function<void(void*, void*)>& serialize, const Function<void(void*, void*)>& deserialize);
API_FUNCTION(NoProxy) static void AddRPC(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, const Function<void(void*, void*)>& execute, bool isServer, bool isClient, NetworkChannelType channel);
API_FUNCTION(NoProxy) static void CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds);
API_FUNCTION(NoProxy) static bool CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds);
static StringAnsiView GetCSharpCachedName(const StringAnsiView& name);
#endif
};

View File

@@ -41,7 +41,7 @@ struct FLAXENGINE_API NetworkRpcInfo
uint8 Client : 1;
uint8 Channel : 4;
void (*Execute)(ScriptingObject* obj, NetworkStream* stream, void* tag);
void (*Invoke)(ScriptingObject* obj, void** args);
bool (*Invoke)(ScriptingObject* obj, void** args);
void* Tag;
/// <summary>
@@ -83,10 +83,7 @@ FORCE_INLINE void NetworkRpcInitArg(Array<void*, FixedAllocation<16>>& args, con
{ \
Array<void*, FixedAllocation<16>> args; \
NetworkRpcInitArg(args, __VA_ARGS__); \
rpcInfo.Invoke(this, args.Get()); \
if (rpcInfo.Server && networkMode == NetworkManagerMode::Client) \
return; \
if (rpcInfo.Client && networkMode == NetworkManagerMode::Server) \
if (rpcInfo.Invoke(this, args.Get())) \
return; \
} \
}

View File

@@ -299,17 +299,19 @@ void ParticleEffect::Stop()
void ParticleEffect::UpdateBounds()
{
BoundingBox bounds = BoundingBox::Empty;
if (ParticleSystem && Instance.LastUpdateTime >= 0)
const auto particleSystem = ParticleSystem.Get();
if (particleSystem && Instance.LastUpdateTime >= 0)
{
for (int32 j = 0; j < ParticleSystem->Tracks.Count(); j++)
for (int32 j = 0; j < particleSystem->Tracks.Count(); j++)
{
const auto& track = ParticleSystem->Tracks[j];
const auto& track = particleSystem->Tracks[j];
if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled)
continue;
auto& emitter = ParticleSystem->Emitters[track.AsEmitter.Index];
auto& data = Instance.Emitters[track.AsEmitter.Index];
if (!emitter || emitter->Capacity == 0 || emitter->Graph.Layout.Size == 0)
const int32 emitterIndex = track.AsEmitter.Index;
ParticleEmitter* emitter = particleSystem->Emitters[emitterIndex].Get();
if (!emitter || emitter->Capacity == 0 || emitter->Graph.Layout.Size == 0 || Instance.Emitters.Count() <= emitterIndex)
continue;
auto& data = Instance.Emitters[emitterIndex];
BoundingBox emitterBounds;
if (emitter->GraphExecutorCPU.ComputeBounds(emitter, this, data, emitterBounds))

View File

@@ -230,10 +230,11 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view)
{
const Vector3 currentPos = wheel.Collider->GetPosition();
const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0);
const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, true);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, true);
DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, true);
DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true);
DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheelDebugOrientation, wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true);
if (!data.State.IsInAir)
{
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.TireContactPoint, 5.0f), Color::Green, 0, true);
@@ -260,6 +261,7 @@ void WheeledVehicle::OnDebugDrawSelected()
{
const Vector3 currentPos = wheel.Collider->GetPosition();
const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0);
const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90);
Transform actorPose = Transform::Identity, shapePose = Transform::Identity;
PhysicsBackend::GetRigidActorPose(_actor, actorPose.Translation, actorPose.Orientation);
PhysicsBackend::GetShapeLocalPose(wheel.Collider->GetPhysicsShape(), shapePose.Translation, shapePose.Orientation);
@@ -267,7 +269,7 @@ void WheeledVehicle::OnDebugDrawSelected()
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(actorPose.LocalToWorld(shapePose.Translation), wheel.Radius * 0.11f), Color::OrangeRed * 0.8f, 0, false);
DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, false);
DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false);
DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheelDebugOrientation, wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false);
if (!data.State.SuspensionTraceStart.IsZero())
{
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.SuspensionTraceStart, 5.0f), Color::AliceBlue, 0, false);

View File

@@ -279,6 +279,60 @@ class CharacterControllerFilterPhysX : public PxControllerFilterCallback
}
};
class CharacterControllerHitReportPhysX : public PxUserControllerHitReport
{
void onHit(const PxControllerHit& hit, Collision& c)
{
ASSERT_LOW_LAYER(c.ThisActor && c.OtherActor);
c.Impulse = Vector3::Zero;
c.ThisVelocity = P2C(hit.dir) * hit.length;
c.OtherVelocity = Vector3::Zero;
c.ContactsCount = 1;
ContactPoint& contact = c.Contacts[0];
contact.Point = P2C(hit.worldPos);
contact.Normal = P2C(hit.worldNormal);
contact.Separation = 0.0f;
//auto simulationEventCallback = static_cast<SimulationEventCallback*>(hit.controller->getScene()->getSimulationEventCallback());
//simulationEventCallback->Collisions[SimulationEventCallback::CollidersPair(c.ThisActor, c.OtherActor)] = c;
// TODO: build additional list for hit-only events to properly send enter/exit pairs instead of spamming every frame whether controller executes move
// Single-hit collision
c.ThisActor->OnCollisionEnter(c);
c.SwapObjects();
c.ThisActor->OnCollisionEnter(c);
c.SwapObjects();
c.ThisActor->OnCollisionExit(c);
c.SwapObjects();
c.ThisActor->OnCollisionExit(c);
}
void onShapeHit(const PxControllerShapeHit& hit) override
{
Collision c;
PxShape* controllerShape;
hit.controller->getActor()->getShapes(&controllerShape, 1);
c.ThisActor = static_cast<PhysicsColliderActor*>(controllerShape->userData);
c.OtherActor = static_cast<PhysicsColliderActor*>(hit.shape->userData);
onHit(hit, c);
}
void onControllerHit(const PxControllersHit& hit) override
{
Collision c;
PxShape* controllerShape;
hit.controller->getActor()->getShapes(&controllerShape, 1);
c.ThisActor = static_cast<PhysicsColliderActor*>(controllerShape->userData);
hit.other->getActor()->getShapes(&controllerShape, 1);
c.OtherActor = static_cast<PhysicsColliderActor*>(controllerShape->userData);
onHit(hit, c);
}
void onObstacleHit(const PxControllerObstacleHit& hit) override
{
}
};
#if WITH_VEHICLE
class WheelFilterPhysX : public PxQueryFilterCallback
@@ -463,6 +517,7 @@ namespace
QueryFilterPhysX QueryFilter;
CharacterQueryFilterPhysX CharacterQueryFilter;
CharacterControllerFilterPhysX CharacterControllerFilter;
CharacterControllerHitReportPhysX CharacterControllerHitReport;
Dictionary<PxScene*, Vector3, InlinedAllocation<32>> SceneOrigins;
CriticalSection FlushLocker;
@@ -2811,6 +2866,7 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic
const Vector3 sceneOrigin = SceneOrigins[scenePhysX->Scene];
PxCapsuleControllerDesc desc;
desc.userData = actor;
desc.reportCallback = &CharacterControllerHitReport;
desc.contactOffset = Math::Max(contactOffset, ZeroTolerance);
desc.position = PxExtendedVec3(position.X - sceneOrigin.X, position.Y - sceneOrigin.Y, position.Z - sceneOrigin.Z);
desc.slopeLimit = Math::Cos(slopeLimit * DegreesToRadians);

View File

@@ -309,6 +309,45 @@ bool FileBase::WriteAllText(const StringView& path, const Char* data, int32 leng
return WriteAllBytes(path, tmp.Get(), tmp.Count());
}
case Encoding::UTF8:
{
Array<byte> tmp;
tmp.SetCapacity(length);
for (int32 i = 0; i < length; i++)
{
Char c = data[i];
if (c < 0x0080)
{
tmp.Add((byte)c);
}
else if (c < 0x0800)
{
tmp.Add(0xC0 | (c >> 6));
tmp.Add(0x80 | (c & 0x3F));
}
else if (c < 0xD800 || c >= 0xE000)
{
tmp.Add(0xE0 | (c >> 12));
tmp.Add(0x80 | ((c >> 6) & 0x3F));
tmp.Add(0x80 | (c & 0x3F));
}
else // Surrogate pair
{
++i;
if (i >= length)
return true;
uint32 p = 0x10000 + (((c & 0x3FF) << 10) | (data[i] & 0x3FF));
tmp.Add(0xF0 | (p >> 18));
tmp.Add(0x80 | ((p >> 12) & 0x3F));
tmp.Add(0x80 | ((p >> 6) & 0x3F));
tmp.Add(0x80 | (p & 0x3F));
}
}
return WriteAllBytes(path, tmp.Get(), tmp.Count());
}
default:
return true;
}

View File

@@ -179,7 +179,7 @@ public:
public:
// Converts characters from ANSI to UTF-16
static void ConvertANSI2UTF16(const char* from, Char* to, int32 len);
static void ConvertANSI2UTF16(const char* from, Char* to, int32 fromLength, int32& toLength);
// Converts characters from UTF-16 to ANSI
static void ConvertUTF162ANSI(const Char* from, char* to, int32 len);

View File

@@ -311,14 +311,14 @@ static inline uint32 Utf8ToUtf32Codepoint(const char* src, int32 length)
}
}
void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 len)
void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 fromLength, int32& toLength)
{
const char* const u8end = from + len;
const char* const u8end = from + fromLength;
const char* u8cur = from;
char16_t* u16cur = to;
while (u8cur < u8end)
{
len = Utf8CodepointLength(*u8cur);
int32 len = Utf8CodepointLength(*u8cur);
uint32 codepoint = Utf8ToUtf32Codepoint(u8cur, len);
// Convert the UTF32 codepoint to one or more UTF16 codepoints
@@ -336,6 +336,7 @@ void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 len)
}
u8cur += len;
}
toLength = (int32)(u16cur - to);
}
static const char32_t kByteMask = 0x000000BF;

View File

@@ -179,10 +179,12 @@ const char* StringUtils::Find(const char* str, const char* toFind)
return strstr(str, toFind);
}
void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 len)
void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 fromLength, int32& toLength)
{
if (len)
mbstowcs(to, from, len);
if (fromLength)
toLength = mbstowcs(to, from, fromLength);
else
toLength = 0;
}
void StringUtils::ConvertUTF162ANSI(const Char* from, char* to, int32 len)

View File

@@ -992,7 +992,8 @@ void ReadPipe(HANDLE pipe, Array<char>& rawData, Array<Char>& logData, LogType l
// Log contents
logData.Clear();
logData.Resize(rawData.Count() + 1);
StringUtils::ConvertANSI2UTF16(rawData.Get(), logData.Get(), rawData.Count());
int32 tmp;
StringUtils::ConvertANSI2UTF16(rawData.Get(), logData.Get(), rawData.Count(), tmp);
logData.Last() = '\0';
if (settings.LogOutput)
Log::Logger::Write(logType, StringView(logData.Get(), rawData.Count()));

View File

@@ -7,7 +7,7 @@
#include "Engine/Threading/Threading.h"
#include "IncludeFreeType.h"
Font::Font(FontAsset* parentAsset, int32 size)
Font::Font(FontAsset* parentAsset, float size)
: ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer))
, _asset(parentAsset)
, _size(size)
@@ -436,7 +436,7 @@ void Font::FlushFaceSize() const
{
// Set the character size
const FT_Face face = _asset->GetFTFace();
const FT_Error error = FT_Set_Char_Size(face, 0, ConvertPixelTo26Dot6<FT_F26Dot6>((float)_size * FontManager::FontScale), DefaultDPI, DefaultDPI);
const FT_Error error = FT_Set_Char_Size(face, 0, ConvertPixelTo26Dot6<FT_F26Dot6>(_size * FontManager::FontScale), DefaultDPI, DefaultDPI);
if (error)
{
LOG_FT_ERROR(error);

View File

@@ -228,7 +228,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font);
private:
FontAsset* _asset;
int32 _size;
float _size;
int32 _height;
int32 _ascender;
int32 _descender;
@@ -244,7 +244,7 @@ public:
/// </summary>
/// <param name="parentAsset">The parent asset.</param>
/// <param name="size">The size.</param>
Font(FontAsset* parentAsset, int32 size);
Font(FontAsset* parentAsset, float size);
/// <summary>
/// Finalizes an instance of the <see cref="Font"/> class.
@@ -264,7 +264,7 @@ public:
/// <summary>
/// Gets font size.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 GetSize() const
API_PROPERTY() FORCE_INLINE float GetSize() const
{
return _size;
}

View File

@@ -92,7 +92,7 @@ void FontAsset::SetOptions(const FontOptions& value)
_options = value;
}
Font* FontAsset::CreateFont(int32 size)
Font* FontAsset::CreateFont(float size)
{
PROFILE_CPU();

View File

@@ -139,7 +139,7 @@ public:
/// </summary>
/// <param name="size">The font characters size.</param>
/// <returns>The created font object.</returns>
API_FUNCTION() Font* CreateFont(int32 size);
API_FUNCTION() Font* CreateFont(float size);
/// <summary>
/// Gets the font with bold style. Returns itself or creates a new virtual font asset using this font but with bold option enabled.

View File

@@ -13,7 +13,7 @@ namespace FlaxEngine
private FontAsset _font;
[NoSerialize]
private int _size;
private float _size;
[NoSerialize]
private Font _cachedFont;
@@ -33,7 +33,7 @@ namespace FlaxEngine
/// </summary>
/// <param name="font">The font.</param>
/// <param name="size">The font size.</param>
public FontReference(FontAsset font, int size)
public FontReference(FontAsset font, float size)
{
_font = font;
_size = size;
@@ -91,7 +91,7 @@ namespace FlaxEngine
/// The size of the font characters.
/// </summary>
[EditorOrder(10), Limit(1, 500, 0.1f), Tooltip("The size of the font characters.")]
public int Size
public float Size
{
get => _size;
set
@@ -187,7 +187,7 @@ namespace FlaxEngine
unchecked
{
int hashCode = _font ? _font.GetHashCode() : 0;
hashCode = (hashCode * 397) ^ _size;
hashCode = (hashCode * 397) ^ _size.GetHashCode();
return hashCode;
}
}

View File

@@ -712,7 +712,8 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa
// Provide new path of hot-reloaded native library path for managed DllImport
if (nativePath.HasChars())
{
RegisterNativeLibrary(assemblyPathAnsi.Get(), StringAnsi(nativePath).Get());
StringAnsi nativeName = _name.EndsWith(".CSharp") ? StringAnsi(_name.Get(), _name.Length() - 7) : StringAnsi(_name);
RegisterNativeLibrary(nativeName.Get(), StringAnsi(nativePath).Get());
}
_hasCachedClasses = false;
@@ -845,7 +846,7 @@ bool MClass::IsSubClassOf(const MClass* klass, bool checkInterfaces) const
bool MClass::HasInterface(const MClass* klass) const
{
static void* TypeIsAssignableFrom = GetStaticMethodPointer(TEXT("TypeIsAssignableFrom"));
return klass && CallStaticMethod<bool, void*, void*>(TypeIsAssignableFrom, _handle, klass->_handle);
return klass && CallStaticMethod<bool, void*, void*>(TypeIsAssignableFrom, klass->_handle, _handle);
}
bool MClass::IsInstanceOfType(MObject* object) const

View File

@@ -559,6 +559,16 @@ void Scripting::Release()
}
_objectsLocker.Unlock();
// Release assets sourced from game assemblies
const auto flaxModule = GetBinaryModuleFlaxEngine();
for (auto asset : Content::GetAssets())
{
if (asset->GetTypeHandle().Module == flaxModule)
continue;
asset->DeleteObjectNow();
}
// Unload assemblies (from back to front)
{
LOG(Info, "Unloading binary modules");
@@ -640,9 +650,9 @@ void Scripting::Reload(bool canTriggerSceneReload)
MCore::GC::WaitForPendingFinalizers();
// Destroy objects from game assemblies (eg. not released objects that might crash if persist in memory after reload)
const auto flaxModule = GetBinaryModuleFlaxEngine();
_objectsLocker.Lock();
{
const auto flaxModule = GetBinaryModuleFlaxEngine();
for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i)
{
auto obj = i->Value;
@@ -657,6 +667,15 @@ void Scripting::Reload(bool canTriggerSceneReload)
}
_objectsLocker.Unlock();
// Release assets sourced from game assemblies
for (auto asset : Content::GetAssets())
{
if (asset->GetTypeHandle().Module == flaxModule)
continue;
asset->DeleteObjectNow();
}
// Unload all game modules
LOG(Info, "Unloading game binary modules");
auto modules = BinaryModule::GetModules();

View File

@@ -6,6 +6,7 @@
#include "Engine/Core/Templates.h"
extern FLAXENGINE_API class ScriptingObject* FindObject(const Guid& id, class MClass* type);
extern FLAXENGINE_API class Asset* LoadAsset(const Guid& id, const struct ScriptingTypeHandle& type);
/// <summary>
/// Base class for all data read streams
@@ -159,6 +160,34 @@ public:
Read(ptr);
v = ptr;
}
template<typename T>
FORCE_INLINE void Read(SoftObjectReference<T>& v)
{
uint32 id[4];
ReadBytes(id, sizeof(id));
v.Set(*(Guid*)id);
}
template<typename T>
FORCE_INLINE void Read(AssetReference<T>& v)
{
uint32 id[4];
ReadBytes(id, sizeof(id));
v = (T*)::LoadAsset(*(Guid*)id, T::TypeInitializer);
}
template<typename T>
FORCE_INLINE void Read(WeakAssetReference<T>& v)
{
uint32 id[4];
ReadBytes(id, sizeof(id));
v = (T*)::LoadAsset(*(Guid*)id, T::TypeInitializer);
}
template<typename T>
FORCE_INLINE void Read(SoftAssetReference<T>& v)
{
uint32 id[4];
ReadBytes(id, sizeof(id));
v.Set(*(Guid*)id);
}
/// <summary>
/// Read data array

View File

@@ -17,6 +17,14 @@ class ISerializable;
class ScriptingObject;
template<typename T>
class ScriptingObjectReference;
template<typename T>
class SoftObjectReference;
template<typename T>
class AssetReference;
template<typename T>
class WeakAssetReference;
template<typename T>
class SoftAssetReference;
/// <summary>
/// Base class for all data streams (memory streams, file streams etc.)

View File

@@ -176,6 +176,26 @@ public:
{
Write(v.Get());
}
template<typename T>
FORCE_INLINE void Write(const SoftObjectReference<T>& v)
{
Write(v.Get());
}
template<typename T>
FORCE_INLINE void Write(const AssetReference<T>& v)
{
Write(v.Get());
}
template<typename T>
FORCE_INLINE void Write(const WeakAssetReference<T>& v)
{
Write(v.Get());
}
template<typename T>
FORCE_INLINE void Write(const SoftAssetReference<T>& v)
{
Write(v.Get());
}
template<typename T, typename AllocationType = HeapAllocation>
void Write(const Array<T, AllocationType>& data)

View File

@@ -73,14 +73,14 @@ void TextRender::SetColor(const Color& value)
}
}
int32 TextRender::GetFontSize() const
float TextRender::GetFontSize() const
{
return _size;
}
void TextRender::SetFontSize(int32 value)
void TextRender::SetFontSize(float value)
{
value = Math::Clamp(value, 1, 1024);
value = Math::Clamp(value, 1.0f, 1024.0f);
if (_size != value)
{
_size = value;

View File

@@ -38,7 +38,7 @@ private:
LocalizedString _text;
Color _color;
TextLayoutOptions _layoutOptions;
int32 _size;
float _size;
int32 _sceneRenderingKey = -1;
BoundingBox _localBox;
@@ -91,12 +91,12 @@ public:
/// Gets the font characters size.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(32), Limit(1, 1000), EditorDisplay(\"Text\")")
int32 GetFontSize() const;
float GetFontSize() const;
/// <summary>
/// Sets the font characters size.
/// </summary>
API_PROPERTY() void SetFontSize(int32 value);
API_PROPERTY() void SetFontSize(float value);
/// <summary>
/// The draw passes to use for rendering this object.

View File

@@ -49,7 +49,7 @@ public:
{
}
StringAsANSI(const Char* text, const int32 length)
StringAsANSI(const Char* text, int32 length)
{
if (length + 1 < InlinedSize)
{
@@ -83,7 +83,7 @@ public:
{
}
StringAsUTF8(const Char* text, const int32 length)
StringAsUTF8(const Char* text, int32 length)
{
int32 lengthUtf8;
if (length + 1 < InlinedSize)
@@ -112,17 +112,17 @@ public:
{
}
StringAsUTF16(const char* text, const int32 length)
StringAsUTF16(const char* text, int32 length)
{
if (length + 1 < InlinedSize)
{
StringUtils::ConvertANSI2UTF16(text, this->_inlined, length);
StringUtils::ConvertANSI2UTF16(text, this->_inlined, length, length);
this->_inlined[length] = 0;
}
else
{
this->_dynamic = (CharType*)Allocator::Allocate((length + 1) * sizeof(CharType));
StringUtils::ConvertANSI2UTF16(text, this->_dynamic, length);
StringUtils::ConvertANSI2UTF16(text, this->_dynamic, length, length);
this->_dynamic[length] = 0;
}
}

View File

@@ -255,7 +255,7 @@ namespace FlaxEngine
#else
private class ExtractArrayFromListContext<T>
{
public static FieldInfo? itemsField;
public static FieldInfo itemsField;
}
internal static T[] ExtractArrayFromList<T>(List<T> list)
{

View File

@@ -1204,7 +1204,7 @@ namespace Flax.Build.Bindings
if (apiType != null)
{
if (parameterInfo.IsOut)
contents.Append(indent).AppendFormat("{1} {0}Temp;", parameterInfo.Name, new TypeInfo(parameterInfo.Type) { IsRef = false }.GetFullNameNative(buildData, caller)).AppendLine();
contents.Append(indent).AppendFormat("{1} {0}Temp;", parameterInfo.Name, parameterInfo.Type.GetFullNameNative(buildData, caller, false)).AppendLine();
else
contents.Append(indent).AppendFormat("auto {0}Temp = {1};", parameterInfo.Name, param).AppendLine();
if (parameterInfo.Type.IsPtr && !parameterInfo.Type.IsRef)

View File

@@ -1417,6 +1417,9 @@ namespace Flax.Build.Bindings
context.Tokenizer.SkipUntil(TokenType.Comma, out desc.Lang);
desc.Code = context.Tokenizer.ExpectToken(TokenType.String).Value.Replace("\\\"", "\"");
desc.Code = desc.Code.Substring(1, desc.Code.Length - 2);
desc.Code = desc.Code.Replace("\\\n", "\n");
desc.Code = desc.Code.Replace("\\\r\n", "\n");
desc.Code = desc.Code.Replace("\t", " ");
context.Tokenizer.ExpectToken(TokenType.RightParent);
return desc;
}

View File

@@ -144,7 +144,7 @@ namespace Flax.Build.Bindings
GenericArgs = BindingsGenerator.Read(reader, GenericArgs);
}
public string GetFullNameNative(Builder.BuildData buildData, ApiTypeInfo caller)
public string GetFullNameNative(Builder.BuildData buildData, ApiTypeInfo caller, bool canRef = true, bool canConst = true)
{
var type = BindingsGenerator.FindApiTypeInfo(buildData, this, caller);
if (type == null)
@@ -155,7 +155,7 @@ namespace Flax.Build.Bindings
return type.FullNameNative;
var sb = new StringBuilder(64);
if (IsConst)
if (IsConst && canConst)
sb.Append("const ");
sb.Append(type.FullNameNative);
if (GenericArgs != null)
@@ -171,7 +171,7 @@ namespace Flax.Build.Bindings
}
if (IsPtr)
sb.Append('*');
if (IsRef)
if (IsRef && canRef)
sb.Append('&');
return sb.ToString();
}

View File

@@ -330,7 +330,7 @@ namespace Flax.Build
var referenceBuildOptions = GetBuildOptions(referenceTarget, configurationData.TargetBuildOptions.Platform, configurationData.TargetBuildOptions.Toolchain, configurationData.Architecture, configurationData.Configuration, reference.Project.ProjectFolderPath);
referenceBuildOptions.Flags |= BuildFlags.GenerateProject;
var referenceModules = CollectModules(rules, referenceBuildOptions.Platform, referenceTarget, referenceBuildOptions, referenceBuildOptions.Toolchain, referenceBuildOptions.Architecture, referenceBuildOptions.Configuration);
var referenceBinaryModules = GetBinaryModules(projectInfo, referenceTarget, referenceModules);
var referenceBinaryModules = referenceModules.Keys.GroupBy(x => x.BinaryModuleName).ToArray();
foreach (var binaryModule in referenceBinaryModules)
{
project.Defines.Add(binaryModule.Key.ToUpperInvariant() + "_API=");

View File

@@ -143,6 +143,16 @@ namespace Flax.Build
{
Log.Info("Removing: " + targetBuildOptions.IntermediateFolder);
CleanDirectory(intermediateFolder);
intermediateFolder.Create();
}
// Delete all output files
var outputFolder = new DirectoryInfo(targetBuildOptions.OutputFolder);
if (outputFolder.Exists)
{
Log.Info("Removing: " + targetBuildOptions.OutputFolder);
CleanDirectory(outputFolder);
outputFolder.Create();
}
}
}

View File

@@ -4,14 +4,16 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Flax.Build.Graph;
using Flax.Build.NativeCpp;
using Flax.Deploy;
namespace Flax.Build
{
static partial class Builder
{
public static event Action<TaskGraph, BuildData, NativeCpp.BuildOptions, Task, IGrouping<string, Module>> BuildDotNetAssembly;
public static event Action<TaskGraph, BuildData, BuildOptions, Task, IGrouping<string, Module>> BuildDotNetAssembly;
private static void BuildTargetDotNet(RulesAssembly rules, TaskGraph graph, Target target, Platform platform, TargetConfiguration configuration)
{
@@ -150,7 +152,7 @@ namespace Flax.Build
}
}
private static void BuildDotNet(TaskGraph graph, BuildData buildData, NativeCpp.BuildOptions buildOptions, string name, List<string> sourceFiles, HashSet<string> fileReferences = null, IGrouping<string, Module> binaryModule = null)
private static void BuildDotNet(TaskGraph graph, BuildData buildData, BuildOptions buildOptions, string name, List<string> sourceFiles, HashSet<string> fileReferences = null, IGrouping<string, Module> binaryModule = null)
{
// Setup build options
var buildPlatform = Platform.BuildTargetPlatform;
@@ -163,6 +165,8 @@ namespace Flax.Build
if (!dotnetSdk.IsValid)
throw new Exception("Cannot compile C# without .NET SDK");
string dotnetPath = "dotnet", referenceAnalyzers;
string[] runtimeVersionNameParts = dotnetSdk.RuntimeVersionName.Split('.');
string runtimeVersionShort = runtimeVersionNameParts[0] + '.' + runtimeVersionNameParts[1];
#else
string monoRoot, monoPath;
#endif
@@ -173,7 +177,7 @@ namespace Flax.Build
#if USE_NETCORE
dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet.exe");
cscPath = Path.Combine(dotnetSdk.RootPath, @$"sdk\{dotnetSdk.VersionName}\Roslyn\bincore\csc.dll");
referenceAssemblies = Path.Combine(dotnetSdk.RootPath, @$"shared\Microsoft.NETCore.App\{dotnetSdk.RuntimeVersionName}\");
referenceAssemblies = Path.Combine(dotnetSdk.RootPath, @$"packs\Microsoft.NETCore.App.Ref\{dotnetSdk.RuntimeVersionName}\ref\net{runtimeVersionShort}\");
referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, @$"packs\Microsoft.NETCore.App.Ref\{dotnetSdk.RuntimeVersionName}\analyzers\dotnet\cs\");
#else
monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Windows", "Mono");
@@ -189,7 +193,7 @@ namespace Flax.Build
{
#if USE_NETCORE
cscPath = Path.Combine(dotnetSdk.RootPath, $"sdk/{dotnetSdk.VersionName}/Roslyn/bincore/csc.dll");
referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"shared/Microsoft.NETCore.App/{dotnetSdk.RuntimeVersionName}/");
referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/ref/net{runtimeVersionShort}/");
referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/analyzers/dotnet/cs/");
#else
monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Linux", "Mono");
@@ -203,7 +207,7 @@ namespace Flax.Build
{
#if USE_NETCORE
cscPath = Path.Combine(dotnetSdk.RootPath, $"sdk/{dotnetSdk.VersionName}/Roslyn/bincore/csc.dll");
referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"shared/Microsoft.NETCore.App/{dotnetSdk.RuntimeVersionName}/");
referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/ref/net{runtimeVersionShort}/");
referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/analyzers/dotnet/cs/");
#else
monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Mac", "Mono");
@@ -242,7 +246,9 @@ namespace Flax.Build
args.Add("/filealign:512");
#if USE_NETCORE
args.Add("/langversion:11.0");
args.Add("-nowarn:8632"); // Nullable
args.Add(string.Format("/nullable:{0}", buildOptions.ScriptingAPI.CSharpNullableReferences.ToString().ToLowerInvariant()));
if (buildOptions.ScriptingAPI.CSharpNullableReferences == CSharpNullableReferences.Disable)
args.Add("-nowarn:8632"); // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#else
args.Add("/langversion:7.3");
#endif
@@ -253,6 +259,7 @@ namespace Flax.Build
args.Add(buildData.Configuration == TargetConfiguration.Release ? "/optimize+" : "/optimize-");
#else
args.Add(buildData.Configuration == TargetConfiguration.Debug ? "/optimize-" : "/optimize+");
args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies));
#endif
args.Add(string.Format("/out:\"{0}\"", outputFile));
args.Add(string.Format("/doc:\"{0}\"", outputDocFile));
@@ -260,7 +267,6 @@ namespace Flax.Build
args.Add("/define:" + string.Join(";", buildOptions.ScriptingAPI.Defines));
if (buildData.Configuration == TargetConfiguration.Debug)
args.Add("/define:DEBUG");
args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies));
foreach (var reference in buildOptions.ScriptingAPI.SystemReferences)
args.Add(string.Format("/reference:\"{0}{1}.dll\"", referenceAssemblies, reference));
foreach (var reference in fileReferences)
@@ -272,6 +278,13 @@ namespace Flax.Build
foreach (var sourceFile in sourceFiles)
args.Add("\"" + sourceFile + "\"");
#if USE_NETCORE
// Inject some assembly metadata (similar to msbuild in Visual Studio)
var assemblyAttributesPath = Path.Combine(buildOptions.IntermediateFolder, name + ".AssemblyAttributes.cs");
File.WriteAllText(assemblyAttributesPath, $"[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(\".NETCoreApp,Version=v{runtimeVersionShort}\", FrameworkDisplayName = \".NET {runtimeVersionShort}\")]\n", Encoding.UTF8);
args.Add("\"" + assemblyAttributesPath + "\"");
#endif
// Generate response file with source files paths and compilation arguments
string responseFile = Path.Combine(buildOptions.IntermediateFolder, name + ".response");
Utilities.WriteFileIfChanged(responseFile, string.Join(Environment.NewLine, args));

View File

@@ -23,6 +23,32 @@ namespace Flax.Build.NativeCpp
GenerateProject = 1,
}
/// <summary>
/// The nullable context type used with reference types (C#).
/// </summary>
public enum CSharpNullableReferences
{
/// <summary>
/// The code is nullable oblivious, nullable warnings and language analysis features are disabled.
/// </summary>
Disable,
/// <summary>
/// The compiler enables all null reference analysis and all language features.
/// </summary>
Enable,
/// <summary>
/// The compiler performs all null analysis and emits warnings when code might dereference null.
/// </summary>
Warnings,
/// <summary>
/// The compiler doesn't perform null analysis or emit warnings when code might dereference null.
/// </summary>
Annotations,
}
/// <summary>
/// The native C++ module build settings container.
/// </summary>
@@ -188,6 +214,15 @@ namespace Flax.Build.NativeCpp
/// </summary>
public bool IgnoreMissingDocumentationWarnings;
/// <summary>
/// The nullable context used in C# project.
/// </summary>
public CSharpNullableReferences CSharpNullableReferences = CSharpNullableReferences.Disable;
public ScriptingAPIOptions()
{
}
/// <summary>
/// Adds the other options into this.
/// </summary>
@@ -209,6 +244,8 @@ namespace Flax.Build.NativeCpp
Defines = new HashSet<string>(),
SystemReferences = new HashSet<string>
{
"mscorlib",
"netstandard",
"Microsoft.CSharp",
"System",
@@ -223,25 +260,32 @@ namespace Flax.Build.NativeCpp
//"System.ComponentModel.TypeConverter",
"System.Console",
"System.Core",
"System.Diagnostics.StackTrace",
"System.Globalization",
"System.IO",
"System.IO.Compression",
"System.IO.FileSystem.Watcher",
"System.Linq",
"System.Linq.Expressions",
"System.Memory",
"System.Net",
"System.Net.Http",
"System.Net.Primitives",
"System.ObjectModel",
"System.Private.CoreLib",
"System.Private.Uri",
//"System.Private.Xml",
"System.ValueTuple",
"System.Reflection",
"System.Runtime",
"System.Runtime.Extensions",
"System.Runtime.Handles",
"System.Runtime.Intrinsics",
"System.Runtime.Numerics",
"System.Runtime.Loader",
"System.Runtime.CompilerServices.Unsafe",
"System.Runtime.InteropServices",
"System.Runtime.InteropServices.RuntimeInformation",
"System.Runtime.Serialization.Formatters", // BinaryFormatter
"System.Runtime.Serialization",
"System.Runtime.Serialization.Formatters",
"System.Security.Cryptography",
"System.Security.Cryptography.Algorithms",
"System.Security.Cryptography.Primitives",
@@ -249,8 +293,11 @@ namespace Flax.Build.NativeCpp
"System.Threading.Tasks.Parallel",
//"System.Xml",
"System.Threading",
"System.Threading.Thread",
"System.Reflection",
//"System.Reflection.Metadata",
"netstandard",
},
SystemAnalyzers = new HashSet<string>
{

View File

@@ -241,7 +241,7 @@ namespace Flax.Build.Plugins
// Deserialize arguments
argNames += arg.Name;
contents.AppendLine($" {arg.Type.Type} {arg.Name};");
contents.AppendLine($" {arg.Type.GetFullNameNative(buildData, typeInfo, false, false)} {arg.Name};");
contents.AppendLine($" stream->Read({arg.Name});");
}
@@ -254,7 +254,7 @@ namespace Flax.Build.Plugins
// Generated method thunk to invoke RPC to network
{
contents.Append(" static void ").Append(functionInfo.Name).AppendLine("_Invoke(ScriptingObject* obj, void** args)");
contents.Append(" static bool ").Append(functionInfo.Name).AppendLine("_Invoke(ScriptingObject* obj, void** args)");
contents.AppendLine(" {");
contents.AppendLine(" NetworkStream* stream = NetworkReplicator::BeginInvokeRPC();");
contents.AppendLine(" Span<uint32> targetIds;");
@@ -270,11 +270,11 @@ namespace Flax.Build.Plugins
}
// Serialize arguments
contents.AppendLine($" stream->Write(*({arg.Type.Type}*)args[{i}]);");
contents.AppendLine($" stream->Write(*(const {arg.Type.GetFullNameNative(buildData, typeInfo, false, false)}*)args[{i}]);");
}
// Invoke RPC
contents.AppendLine($" NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream, targetIds);");
contents.AppendLine($" return NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream, targetIds);");
contents.AppendLine(" }");
}
contents.AppendLine();
@@ -780,7 +780,7 @@ namespace Flax.Build.Plugins
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));
m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, type.Module.ImportReference(context.NetworkStreamType)));
ILProcessor il = m.Body.GetILProcessor();
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
@@ -1304,7 +1304,10 @@ namespace Flax.Build.Plugins
il.Emit(jmp4);
valueContext.Load(ref il);
il.Emit(OpCodes.Ldloc, varStart + 1); // idx
il.Emit(OpCodes.Ldelem_Ref);
if (elementType.IsValueType)
il.Emit(OpCodes.Ldelem_Any, elementType);
else
il.Emit(OpCodes.Ldelem_Ref);
il.Emit(OpCodes.Stloc, varStart + 2); // <elementType>
// Serialize item value
@@ -1753,10 +1756,10 @@ namespace Flax.Build.Plugins
if (jumpBodyEnd == null)
throw new Exception("Missing IL Return op code in method " + method.Name);
il.Emit(OpCodes.Ldloc, varsStart + 0);
il.Emit(OpCodes.Brfalse_S, jumpIf2Start);
il.Emit(OpCodes.Brfalse, jumpIf2Start);
il.Emit(OpCodes.Ldloc, varsStart + 2);
il.Emit(OpCodes.Ldc_I4_2);
il.Emit(OpCodes.Beq_S, jumpIfBodyStart);
il.Emit(OpCodes.Beq, jumpIfBodyStart);
// ||
il.Emit(jumpIf2Start);
il.Emit(OpCodes.Ldloc, varsStart + 1);
@@ -1812,35 +1815,12 @@ namespace Flax.Build.Plugins
else
il.Emit(OpCodes.Ldnull);
var endInvokeRPC = networkReplicatorType.Resolve().GetMethod("EndInvokeRPC", 5);
if (endInvokeRPC.ReturnType.FullName != boolType.FullName)
throw new Exception("Invalid EndInvokeRPC return type. Remove any 'Binaries' folders to force project recompile.");
il.Emit(OpCodes.Call, module.ImportReference(endInvokeRPC));
// if (server && networkMode == NetworkManagerMode.Client) return;
if (methodRPC.IsServer)
{
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldloc, varsStart + 2);
il.Emit(OpCodes.Ldc_I4_2);
var tmp = ilp.Create(OpCodes.Nop);
il.Emit(OpCodes.Beq_S, tmp);
il.Emit(OpCodes.Br, jumpBodyStart);
il.Emit(tmp);
//il.Emit(OpCodes.Ret);
il.Emit(OpCodes.Br, jumpBodyEnd);
}
// if (client && networkMode == NetworkManagerMode.Server) return;
if (methodRPC.IsClient)
{
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldloc, varsStart + 2);
il.Emit(OpCodes.Ldc_I4_1);
var tmp = ilp.Create(OpCodes.Nop);
il.Emit(OpCodes.Beq_S, tmp);
il.Emit(OpCodes.Br, jumpBodyStart);
il.Emit(tmp);
//il.Emit(OpCodes.Ret);
il.Emit(OpCodes.Br, jumpBodyEnd);
}
// if (EndInvokeRPC) return
il.Emit(OpCodes.Brtrue, jumpBodyEnd);
// Continue to original method body
il.Emit(jumpBodyStart);

View File

@@ -88,7 +88,7 @@ namespace Flax.Build.Projects.VisualStudio
csProjectFileContent.AppendLine(" <TargetFramework>net7.0</TargetFramework>");
csProjectFileContent.AppendLine(" <ImplicitUsings>disable</ImplicitUsings>");
csProjectFileContent.AppendLine(" <Nullable>annotations</Nullable>");
csProjectFileContent.AppendLine(string.Format(" <Nullable>{0}</Nullable>", baseConfiguration.TargetBuildOptions.ScriptingAPI.CSharpNullableReferences.ToString().ToLowerInvariant()));
csProjectFileContent.AppendLine(" <IsPackable>false</IsPackable>");
csProjectFileContent.AppendLine(" <EnableDefaultItems>false</EnableDefaultItems>");
csProjectFileContent.AppendLine(" <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>");

View File

@@ -547,6 +547,7 @@ namespace Flax.Build.Projects.VisualStudio
if (project.Type == TargetType.DotNetCore)
{
var path = Path.Combine(Path.GetDirectoryName(project.Path), "Properties/launchSettings.json");
path = Utilities.NormalizePath(path);
if (profiles.ContainsKey(path))
profile.AppendLine(",");
profile.AppendLine($" \"{project.BaseName}\": {{");
@@ -568,6 +569,7 @@ namespace Flax.Build.Projects.VisualStudio
var folder = Path.GetDirectoryName(e.Key);
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
profile.Clear();
profile.AppendLine("{");
profile.AppendLine(" \"profiles\": {");
profile.AppendLine(e.Value);

View File

@@ -249,7 +249,7 @@ namespace Flax.Build
public static void GetType(this ModuleDefinition module, string fullName, out TypeReference type)
{
if (!module.TryGetTypeReference(fullName, out type))
//if (!module.TryGetTypeReference(fullName, out type)) // TODO: this seams to return 'FlaxEngine.Networking.NetworkManagerMode' as a Class instead of Enum
{
// Do manual search
foreach (var a in module.AssemblyReferences)