Merge branch 'master' of https://github.com/FlaxEngine/FlaxEngine into double-vectors
This commit is contained in:
@@ -4,7 +4,7 @@ https://github.com/flaxengine/FlaxEngine/issues?q=is%3Aissue
|
||||
|
||||
**Issue description:**
|
||||
<!-- What happened, and what was expected. -->
|
||||
|
||||
<!-- Log file, can be found in the project directory's `Logs` folder (optional) -->
|
||||
|
||||
**Steps to reproduce:**
|
||||
<!-- Enter minimal reproduction steps if available. -->
|
||||
|
||||
@@ -419,12 +419,18 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void Initialize(CustomEditorPresenter presenter, LayoutElementsContainer layout, ValueContainer values)
|
||||
{
|
||||
_isNull = values != null && values.IsNull;
|
||||
|
||||
base.Initialize(presenter, layout, values);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var values = Values;
|
||||
_visibleIfCaches = null;
|
||||
_isNull = values != null && values.IsNull;
|
||||
|
||||
// Collect items to edit
|
||||
List<ItemInfo> items;
|
||||
|
||||
@@ -388,6 +388,105 @@ int32 Editor::LoadProduct()
|
||||
projectPath.Clear();
|
||||
}
|
||||
|
||||
// Create new project option
|
||||
if (CommandLine::Options.NewProject)
|
||||
{
|
||||
if (projectPath.IsEmpty())
|
||||
projectPath = Platform::GetWorkingDirectory();
|
||||
else if (!FileSystem::DirectoryExists(projectPath))
|
||||
FileSystem::CreateDirectory(projectPath);
|
||||
FileSystem::NormalizePath(projectPath);
|
||||
|
||||
String folderName = StringUtils::GetFileName(projectPath);
|
||||
String tmpName;
|
||||
for (int32 i = 0; i < folderName.Length(); i++)
|
||||
{
|
||||
Char c = folderName[i];
|
||||
if (StringUtils::IsAlnum(c) && c != ' ' && c != '.')
|
||||
tmpName += c;
|
||||
}
|
||||
|
||||
// Create project file
|
||||
ProjectInfo newProject;
|
||||
newProject.Name = MoveTemp(tmpName);
|
||||
newProject.ProjectPath = projectPath / newProject.Name + TEXT(".flaxproj");
|
||||
newProject.ProjectFolderPath = projectPath;
|
||||
newProject.Version = Version(1, 0);
|
||||
newProject.Company = TEXT("My Company");
|
||||
newProject.MinEngineVersion = FLAXENGINE_VERSION;
|
||||
newProject.GameTarget = TEXT("GameTarget");
|
||||
newProject.EditorTarget = TEXT("GameEditorTarget");
|
||||
auto& flaxRef = newProject.References.AddOne();
|
||||
flaxRef.Name = TEXT("$(EnginePath)/Flax.flaxproj");
|
||||
flaxRef.Project = nullptr;
|
||||
if (newProject.SaveProject())
|
||||
return 10;
|
||||
|
||||
// Generate source files
|
||||
if (FileSystem::CreateDirectory(projectPath / TEXT("Content")))
|
||||
return 11;
|
||||
if (FileSystem::CreateDirectory(projectPath / TEXT("Source/Game")))
|
||||
return 11;
|
||||
bool failed = File::WriteAllText(projectPath / TEXT("Source/GameTarget.Build.cs"),TEXT(
|
||||
"using Flax.Build;\n"
|
||||
"\n"
|
||||
"public class GameTarget : GameProjectTarget\n"
|
||||
"{\n"
|
||||
" /// <inheritdoc />\n"
|
||||
" public override void Init()\n"
|
||||
" {\n"
|
||||
" base.Init();\n"
|
||||
"\n"
|
||||
" // Reference the modules for game\n"
|
||||
" Modules.Add(\"Game\");\n"
|
||||
" }\n"
|
||||
"}\n"), Encoding::Unicode);
|
||||
failed |= File::WriteAllText(projectPath / TEXT("Source/GameEditorTarget.Build.cs"),TEXT(
|
||||
"using Flax.Build;\n"
|
||||
"\n"
|
||||
"public class GameEditorTarget : GameProjectEditorTarget\n"
|
||||
"{\n"
|
||||
" /// <inheritdoc />\n"
|
||||
" public override void Init()\n"
|
||||
" {\n"
|
||||
" base.Init();\n"
|
||||
"\n"
|
||||
" // Reference the modules for editor\n"
|
||||
" Modules.Add(\"Game\");\n"
|
||||
" }\n"
|
||||
"}\n"), Encoding::Unicode);
|
||||
failed |= File::WriteAllText(projectPath / TEXT("Source/Game/Game.Build.cs"),TEXT(
|
||||
"using Flax.Build;\n"
|
||||
"using Flax.Build.NativeCpp;\n"
|
||||
"\n"
|
||||
"public class Game : GameModule\n"
|
||||
"{\n"
|
||||
" /// <inheritdoc />\n"
|
||||
" public override void Init()\n"
|
||||
" {\n"
|
||||
" base.Init();\n"
|
||||
"\n"
|
||||
" // C#-only scripting\n"
|
||||
" BuildNativeCode = false;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /// <inheritdoc />\n"
|
||||
" public override void Setup(BuildOptions options)\n"
|
||||
" {\n"
|
||||
" base.Setup(options);\n"
|
||||
"\n"
|
||||
" options.ScriptingAPI.IgnoreMissingDocumentationWarnings = true;\n"
|
||||
"\n"
|
||||
" // Here you can modify the build options for your game module\n"
|
||||
" // To reference another module use: options.PublicDependencies.Add(\"Audio\");\n"
|
||||
" // To add C++ define use: options.PublicDefinitions.Add(\"COMPILE_WITH_FLAX\");\n"
|
||||
" // To learn more see scripting documentation.\n"
|
||||
" }\n"
|
||||
"}\n"), Encoding::Unicode);
|
||||
if (failed)
|
||||
return 12;
|
||||
}
|
||||
|
||||
// Missing project case
|
||||
if (projectPath.IsEmpty())
|
||||
{
|
||||
|
||||
@@ -272,8 +272,10 @@ namespace FlaxEditor
|
||||
module.OnEndInit();
|
||||
}
|
||||
|
||||
internal void Init(bool isHeadless, bool skipCompile, Guid startupScene)
|
||||
internal void Init(bool isHeadless, bool skipCompile, bool newProject, Guid startupScene)
|
||||
{
|
||||
if (newProject)
|
||||
InitProject();
|
||||
EnsureState<LoadingState>();
|
||||
_isHeadlessMode = isHeadless;
|
||||
_startupSceneCmdLine = startupScene;
|
||||
@@ -473,6 +475,33 @@ namespace FlaxEditor
|
||||
}
|
||||
}
|
||||
|
||||
private void InitProject()
|
||||
{
|
||||
// Initialize empty project with default game configuration
|
||||
Log("Initialize new project");
|
||||
var project = GameProject;
|
||||
var gameSettings = new GameSettings
|
||||
{
|
||||
ProductName = project.Name,
|
||||
CompanyName = project.Company,
|
||||
};
|
||||
GameSettings.Save(gameSettings);
|
||||
GameSettings.Save(new TimeSettings());
|
||||
GameSettings.Save(new AudioSettings());
|
||||
GameSettings.Save(new PhysicsSettings());
|
||||
GameSettings.Save(new LayersAndTagsSettings());
|
||||
GameSettings.Save(new InputSettings());
|
||||
GameSettings.Save(new GraphicsSettings());
|
||||
GameSettings.Save(new NavigationSettings());
|
||||
GameSettings.Save(new LocalizationSettings());
|
||||
GameSettings.Save(new BuildSettings());
|
||||
GameSettings.Save(new StreamingSettings());
|
||||
GameSettings.Save(new WindowsPlatformSettings());
|
||||
GameSettings.Save(new LinuxPlatformSettings());
|
||||
GameSettings.Save(new AndroidPlatformSettings());
|
||||
GameSettings.Save(new UWPPlatformSettings());
|
||||
}
|
||||
|
||||
internal void OnPlayBeginning()
|
||||
{
|
||||
for (int i = 0; i < _modules.Count; i++)
|
||||
|
||||
@@ -456,18 +456,30 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
if (base.OnKeyDown(key))
|
||||
return true;
|
||||
|
||||
// Se;ect the first item
|
||||
if (key == KeyboardKeys.ArrowDown)
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.ArrowDown:
|
||||
for (int i = 0; i < _panel.Children.Count; i++)
|
||||
{
|
||||
if (_panel.Children[i] is ContextMenuButton item && item.Visible)
|
||||
if (_panel.Children[i] is ContextMenuButton item && item.Visible && item.Enabled)
|
||||
{
|
||||
item.Focus();
|
||||
_panel.ScrollViewTo(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.ArrowUp:
|
||||
for (int i = _panel.Children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_panel.Children[i] is ContextMenuButton item && item.Visible && item.Enabled)
|
||||
{
|
||||
item.Focus();
|
||||
_panel.ScrollViewTo(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -183,7 +183,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
case KeyboardKeys.ArrowUp:
|
||||
for (int i = IndexInParent - 1; i >= 0; i--)
|
||||
{
|
||||
if (ParentContextMenu.ItemsContainer.Children[i] is ContextMenuButton item && item.Visible)
|
||||
if (ParentContextMenu.ItemsContainer.Children[i] is ContextMenuButton item && item.Visible && item.Enabled)
|
||||
{
|
||||
item.Focus();
|
||||
ParentContextMenu.ItemsContainer.ScrollViewTo(item);
|
||||
@@ -194,7 +194,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
case KeyboardKeys.ArrowDown:
|
||||
for (int i = IndexInParent + 1; i < ParentContextMenu.ItemsContainer.Children.Count; i++)
|
||||
{
|
||||
if (ParentContextMenu.ItemsContainer.Children[i] is ContextMenuButton item && item.Visible)
|
||||
if (ParentContextMenu.ItemsContainer.Children[i] is ContextMenuButton item && item.Visible && item.Enabled)
|
||||
{
|
||||
item.Focus();
|
||||
ParentContextMenu.ItemsContainer.ScrollViewTo(item);
|
||||
|
||||
@@ -175,7 +175,7 @@ ManagedEditor::~ManagedEditor()
|
||||
void ManagedEditor::Init()
|
||||
{
|
||||
// Note: editor modules should perform quite fast init, any longer things should be done in async during 'editor splash screen time
|
||||
void* args[3];
|
||||
void* args[4];
|
||||
MClass* mclass = GetClass();
|
||||
if (mclass == nullptr)
|
||||
{
|
||||
@@ -194,14 +194,16 @@ void ManagedEditor::Init()
|
||||
MonoObject* exception = nullptr;
|
||||
bool isHeadless = CommandLine::Options.Headless.IsTrue();
|
||||
bool skipCompile = CommandLine::Options.SkipCompile.IsTrue();
|
||||
bool newProject = CommandLine::Options.NewProject.IsTrue();
|
||||
args[0] = &isHeadless;
|
||||
args[1] = &skipCompile;
|
||||
args[2] = &newProject;
|
||||
Guid sceneId;
|
||||
if (!CommandLine::Options.Play.HasValue() || (CommandLine::Options.Play.HasValue() && Guid::Parse(CommandLine::Options.Play.GetValue(), sceneId)))
|
||||
{
|
||||
sceneId = Guid::Empty;
|
||||
}
|
||||
args[2] = &sceneId;
|
||||
args[3] = &sceneId;
|
||||
initMethod->Invoke(instance, args, &exception);
|
||||
if (exception)
|
||||
{
|
||||
|
||||
@@ -239,7 +239,6 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args)
|
||||
cmdLine.Append(buildToolPath);
|
||||
cmdLine.Append(TEXT("\" "));
|
||||
cmdLine.Append(args.Get(), args.Length());
|
||||
cmdLine.Append(TEXT('\0'));
|
||||
// TODO: Set env var for the mono MONO_GC_PARAMS=nursery-size64m to boost build performance -> profile it
|
||||
|
||||
// Call build tool
|
||||
|
||||
@@ -816,6 +816,6 @@ bool EditorUtilities::ReplaceInFile(const StringView& file, const StringView& fi
|
||||
String text;
|
||||
if (File::ReadAllText(file, text))
|
||||
return true;
|
||||
text.Replace(findWhat.GetText(), replaceWith.GetText());
|
||||
text.Replace(findWhat.Get(), findWhat.Length(), replaceWith.Get(), replaceWith.Length());
|
||||
return File::WriteAllText(file, text, Encoding::ANSI);
|
||||
}
|
||||
|
||||
@@ -538,9 +538,9 @@ namespace FlaxEditor.Windows.Assets
|
||||
for (int i = 0; i < meshData.IndexBuffer.Length; i += 3)
|
||||
{
|
||||
// Cache triangle indices
|
||||
int i0 = meshData.IndexBuffer[i + 0];
|
||||
int i1 = meshData.IndexBuffer[i + 1];
|
||||
int i2 = meshData.IndexBuffer[i + 2];
|
||||
uint i0 = meshData.IndexBuffer[i + 0];
|
||||
uint i1 = meshData.IndexBuffer[i + 1];
|
||||
uint i2 = meshData.IndexBuffer[i + 2];
|
||||
|
||||
// Cache triangle uvs positions and transform positions to output target
|
||||
Vector2 uv0 = meshData.VertexBuffer[i0].TexCoord * uvScale;
|
||||
@@ -562,9 +562,9 @@ namespace FlaxEditor.Windows.Assets
|
||||
for (int i = 0; i < meshData.IndexBuffer.Length; i += 3)
|
||||
{
|
||||
// Cache triangle indices
|
||||
int i0 = meshData.IndexBuffer[i + 0];
|
||||
int i1 = meshData.IndexBuffer[i + 1];
|
||||
int i2 = meshData.IndexBuffer[i + 2];
|
||||
uint i0 = meshData.IndexBuffer[i + 0];
|
||||
uint i1 = meshData.IndexBuffer[i + 1];
|
||||
uint i2 = meshData.IndexBuffer[i + 2];
|
||||
|
||||
// Cache triangle uvs positions and transform positions to output target
|
||||
Vector2 uv0 = meshData.VertexBuffer[i0].LightmapUVs * uvScale;
|
||||
|
||||
@@ -645,9 +645,9 @@ namespace FlaxEditor.Windows.Assets
|
||||
for (int i = 0; i < meshData.IndexBuffer.Length; i += 3)
|
||||
{
|
||||
// Cache triangle indices
|
||||
int i0 = meshData.IndexBuffer[i + 0];
|
||||
int i1 = meshData.IndexBuffer[i + 1];
|
||||
int i2 = meshData.IndexBuffer[i + 2];
|
||||
uint i0 = meshData.IndexBuffer[i + 0];
|
||||
uint i1 = meshData.IndexBuffer[i + 1];
|
||||
uint i2 = meshData.IndexBuffer[i + 2];
|
||||
|
||||
// Cache triangle uvs positions and transform positions to output target
|
||||
Vector2 uv0 = meshData.VertexBuffer[i0].TexCoord * uvScale;
|
||||
@@ -820,7 +820,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
private struct MeshData
|
||||
{
|
||||
public int[] IndexBuffer;
|
||||
public uint[] IndexBuffer;
|
||||
public SkinnedMesh.Vertex[] VertexBuffer;
|
||||
}
|
||||
|
||||
|
||||
@@ -192,6 +192,7 @@ namespace FlaxEditor.Windows
|
||||
_contextMenu.AddButton("Clear log", Clear);
|
||||
_contextMenu.AddButton("Copy selection", _output.Copy);
|
||||
_contextMenu.AddButton("Select All", _output.SelectAll);
|
||||
_contextMenu.AddButton("Show in explorer", () => FileSystem.ShowFileExplorer(Path.Combine(Globals.ProjectFolder, "Logs")));
|
||||
_contextMenu.AddButton("Scroll to bottom", () => { _vScroll.TargetValue = _vScroll.Maximum; }).Icon = Editor.Icons.ArrowDown12;
|
||||
|
||||
// Setup editor options
|
||||
|
||||
@@ -1222,6 +1222,11 @@ Asset::LoadResult VisualScript::load()
|
||||
method.ProfilerName.Get()[assetName.Length()] = ':';
|
||||
method.ProfilerName.Get()[assetName.Length() + 1] = ':';
|
||||
Platform::MemoryCopy(method.ProfilerName.Get() + assetName.Length() + 2, method.Name.Get(), method.Name.Length());
|
||||
method.ProfilerData.name = method.ProfilerName.Get();
|
||||
method.ProfilerData.function = method.Name.Get();
|
||||
method.ProfilerData.file = nullptr;
|
||||
method.ProfilerData.line = 0;
|
||||
method.ProfilerData.color = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2139,7 +2144,7 @@ VisualScriptingBinaryModule* VisualScripting::GetBinaryModule()
|
||||
Variant VisualScripting::Invoke(VisualScript::Method* method, ScriptingObject* instance, Span<Variant> parameters)
|
||||
{
|
||||
CHECK_RETURN(method && method->Script->IsLoaded(), Variant::Zero);
|
||||
PROFILE_CPU_NAMED(*method->ProfilerName);
|
||||
PROFILE_CPU_SRC_LOC(method->ProfilerData);
|
||||
|
||||
// Add to the calling stack
|
||||
ScopeContext scope;
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#include "../BinaryAsset.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
#include "Engine/Visject/VisjectGraph.h"
|
||||
#if COMPILE_WITH_PROFILER
|
||||
#include "Engine/Profiler/ProfilerSrcLoc.h"
|
||||
#endif
|
||||
|
||||
#define VISUAL_SCRIPT_GRAPH_MAX_CALL_STACK 250
|
||||
#define VISUAL_SCRIPT_DEBUGGING USE_EDITOR
|
||||
@@ -118,6 +121,7 @@ public:
|
||||
Array<StringAnsi, InlinedAllocation<16>> ParamNames;
|
||||
#if COMPILE_WITH_PROFILER
|
||||
StringAnsi ProfilerName;
|
||||
SourceLocationData ProfilerData;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -384,12 +384,12 @@ Asset* Content::LoadAsyncInternal(const StringView& internalPath, MClass* type)
|
||||
CHECK_RETURN(type, nullptr);
|
||||
const auto scriptingType = Scripting::FindScriptingType(type->GetFullName());
|
||||
if (scriptingType)
|
||||
return LoadAsyncInternal(internalPath.GetText(), scriptingType);
|
||||
return LoadAsyncInternal(internalPath, scriptingType);
|
||||
LOG(Error, "Failed to find asset type '{0}'.", String(type->GetFullName()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Asset* Content::LoadAsyncInternal(const Char* internalPath, const ScriptingTypeHandle& type)
|
||||
Asset* Content::LoadAsyncInternal(const StringView& internalPath, const ScriptingTypeHandle& type)
|
||||
{
|
||||
#if USE_EDITOR
|
||||
const String path = Globals::EngineContentFolder / internalPath + ASSET_FILES_EXTENSION_WITH_DOT;
|
||||
@@ -411,6 +411,11 @@ Asset* Content::LoadAsyncInternal(const Char* internalPath, const ScriptingTypeH
|
||||
return asset;
|
||||
}
|
||||
|
||||
Asset* Content::LoadAsyncInternal(const Char* internalPath, const ScriptingTypeHandle& type)
|
||||
{
|
||||
return LoadAsyncInternal(StringView(internalPath), type);
|
||||
}
|
||||
|
||||
FLAXENGINE_API Asset* LoadAsset(const Guid& id, const ScriptingTypeHandle& type)
|
||||
{
|
||||
return Content::LoadAsync(id, type);
|
||||
|
||||
@@ -187,6 +187,14 @@ public:
|
||||
/// <returns>The loaded asset or null if failed.</returns>
|
||||
API_FUNCTION(Attributes="HideInEditor") static Asset* LoadAsyncInternal(const StringView& internalPath, MClass* type);
|
||||
|
||||
/// <summary>
|
||||
/// Loads internal engine asset and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async.
|
||||
/// </summary>
|
||||
/// <param name="internalPath">The path of the asset relative to the engine internal content (excluding the extension).</param>
|
||||
/// <param name="type">The asset type. If loaded object has different type (excluding types derived from the given) the loading fails.</param>
|
||||
/// <returns>The loaded asset or null if failed.</returns>
|
||||
static Asset* LoadAsyncInternal(const StringView& internalPath, const ScriptingTypeHandle& type);
|
||||
|
||||
/// <summary>
|
||||
/// Loads internal engine asset and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async.
|
||||
/// </summary>
|
||||
|
||||
@@ -296,7 +296,7 @@ namespace FlaxEditor.Content.Settings
|
||||
}
|
||||
|
||||
// Create new settings asset and link it to the game settings
|
||||
var path = StringUtils.CombinePaths(Globals.ProjectContentFolder, CustomEditors.CustomEditorsUtil.GetPropertyNameUI(typeof(T).Name) + ".json");
|
||||
var path = StringUtils.CombinePaths(Globals.ProjectContentFolder, "Settings", CustomEditors.CustomEditorsUtil.GetPropertyNameUI(typeof(T).Name) + ".json");
|
||||
if (Editor.SaveJsonAsset(path, obj))
|
||||
return true;
|
||||
asset = FlaxEngine.Content.LoadAsync<JsonAsset>(path);
|
||||
|
||||
@@ -226,7 +226,9 @@ bool String::IsANSI() const
|
||||
|
||||
bool String::StartsWith(const StringView& prefix, StringSearchCase searchCase) const
|
||||
{
|
||||
if (prefix.IsEmpty() || prefix.Length() > Length())
|
||||
if (prefix.IsEmpty())
|
||||
return true;
|
||||
if (prefix.Length() > Length())
|
||||
return false;
|
||||
if (searchCase == StringSearchCase::IgnoreCase)
|
||||
return !StringUtils::CompareIgnoreCase(this->GetText(), *prefix, prefix.Length());
|
||||
@@ -235,7 +237,9 @@ bool String::StartsWith(const StringView& prefix, StringSearchCase searchCase) c
|
||||
|
||||
bool String::EndsWith(const StringView& suffix, StringSearchCase searchCase) const
|
||||
{
|
||||
if (suffix.IsEmpty() || suffix.Length() > Length())
|
||||
if (suffix.IsEmpty())
|
||||
return true;
|
||||
if (suffix.Length() > Length())
|
||||
return false;
|
||||
if (searchCase == StringSearchCase::IgnoreCase)
|
||||
return !StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix);
|
||||
|
||||
@@ -65,10 +65,11 @@ public:
|
||||
|
||||
/// <summary>
|
||||
/// Lexicographically tests how this string compares to the other given string.
|
||||
/// In case sensitive mode 'A' is less than 'a'.
|
||||
/// </summary>
|
||||
/// <param name="str">The another string test against.</param>
|
||||
/// <param name="searchCase">The case sensitivity mode.</param>
|
||||
/// <returns>0 if equal, -1 if less than, 1 if greater than.</returns>
|
||||
/// <returns>0 if equal, negative number if less than, positive number if greater than.</returns>
|
||||
int32 Compare(const StringBase& str, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
|
||||
{
|
||||
if (searchCase == StringSearchCase::CaseSensitive)
|
||||
@@ -352,7 +353,7 @@ public:
|
||||
bool StartsWith(T c, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
|
||||
{
|
||||
const int32 length = Length();
|
||||
if (searchCase == StringSearchCase::IgnoreCase)
|
||||
if (searchCase == StringSearchCase::CaseSensitive)
|
||||
return length > 0 && _data[0] == c;
|
||||
return length > 0 && StringUtils::ToLower(_data[0]) == StringUtils::ToLower(c);
|
||||
}
|
||||
@@ -360,14 +361,16 @@ public:
|
||||
bool EndsWith(T c, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
|
||||
{
|
||||
const int32 length = Length();
|
||||
if (searchCase == StringSearchCase::IgnoreCase)
|
||||
if (searchCase == StringSearchCase::CaseSensitive)
|
||||
return length > 0 && _data[length - 1] == c;
|
||||
return length > 0 && StringUtils::ToLower(_data[length - 1]) == StringUtils::ToLower(c);
|
||||
}
|
||||
|
||||
bool StartsWith(const StringBase& prefix, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
|
||||
{
|
||||
if (prefix.IsEmpty() || Length() < prefix.Length())
|
||||
if (prefix.IsEmpty())
|
||||
return true;
|
||||
if (Length() < prefix.Length())
|
||||
return false;
|
||||
if (searchCase == StringSearchCase::IgnoreCase)
|
||||
return StringUtils::CompareIgnoreCase(this->GetText(), *prefix, prefix.Length()) == 0;
|
||||
@@ -376,7 +379,9 @@ public:
|
||||
|
||||
bool EndsWith(const StringBase& suffix, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
|
||||
{
|
||||
if (suffix.IsEmpty() || Length() < suffix.Length())
|
||||
if (suffix.IsEmpty())
|
||||
return true;
|
||||
if (Length() < suffix.Length())
|
||||
return false;
|
||||
if (searchCase == StringSearchCase::IgnoreCase)
|
||||
return StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix) == 0;
|
||||
@@ -413,67 +418,94 @@ public:
|
||||
return replacedChars;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all occurences of searchText within current string with replacementText.
|
||||
/// </summary>
|
||||
/// <param name="searchText">String to search for. If empty or null no replacements are done.</param>
|
||||
/// <param name="replacementText">String to replace with. Null is treated as empty string.</param>
|
||||
/// <returns>Number of replacements made. (In case-sensitive mode if search text and replacement text are equal no replacements are done, and zero is returned.)</returns>
|
||||
int32 Replace(const T* searchText, const T* replacementText, StringSearchCase searchCase = StringSearchCase::CaseSensitive)
|
||||
{
|
||||
int32 replacedCount = 0;
|
||||
if (HasChars() && searchText && *searchText && replacementText && (searchCase == StringSearchCase::IgnoreCase || StringUtils::Compare(searchText, replacementText) != 0))
|
||||
const int32 searchTextLength = StringUtils::Length(searchText);
|
||||
const int32 replacementTextLength = StringUtils::Length(replacementText);
|
||||
return Replace(searchText, searchTextLength, replacementText, replacementTextLength, searchCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all occurences of searchText within current string with replacementText.
|
||||
/// </summary>
|
||||
/// <param name="searchText">String to search for.</param>
|
||||
/// <param name="searchTextLength">Length of searchText. Must be greater than zero.</param>
|
||||
/// <param name="replacementText">String to replace with. Null is treated as empty string.</param>
|
||||
/// <param name="replacementTextLength">Length of replacementText.</param>
|
||||
/// <returns>Number of replacements made (in other words number of occurences of searchText).</returns>
|
||||
int32 Replace(const T* searchText, int32 searchTextLength, const T* replacementText, int32 replacementTextLength, StringSearchCase searchCase = StringSearchCase::CaseSensitive)
|
||||
{
|
||||
if (!HasChars())
|
||||
return 0;
|
||||
|
||||
if (searchTextLength == 0)
|
||||
{
|
||||
const int32 searchTextLength = StringUtils::Length(searchText);
|
||||
const int32 replacementTextLength = StringUtils::Length(replacementText);
|
||||
if (searchTextLength == replacementTextLength)
|
||||
ASSERT(false); // Empty search text never makes sense, and is always sign of a bug in calling code.
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 replacedCount = 0;
|
||||
|
||||
if (searchTextLength == replacementTextLength)
|
||||
{
|
||||
T* pos = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(_data, searchText) : StringUtils::Find(_data, searchText));
|
||||
while (pos != nullptr)
|
||||
{
|
||||
T* pos = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(_data, searchText) : StringUtils::Find(_data, searchText));
|
||||
while (pos != nullptr)
|
||||
{
|
||||
replacedCount++;
|
||||
replacedCount++;
|
||||
|
||||
for (int32 i = 0; i < replacementTextLength; i++)
|
||||
pos[i] = replacementText[i];
|
||||
for (int32 i = 0; i < replacementTextLength; i++)
|
||||
pos[i] = replacementText[i];
|
||||
|
||||
if (pos + searchTextLength - **this < Length())
|
||||
pos = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(pos + searchTextLength, searchText) : StringUtils::Find(pos + searchTextLength, searchText));
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (pos + searchTextLength - **this < Length())
|
||||
pos = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(pos + searchTextLength, searchText) : StringUtils::Find(pos + searchTextLength, searchText));
|
||||
else
|
||||
break;
|
||||
}
|
||||
else if (Contains(searchText, searchCase))
|
||||
}
|
||||
else if (Contains(searchText, searchCase))
|
||||
{
|
||||
T* readPosition = _data;
|
||||
T* searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText));
|
||||
while (searchPosition != nullptr)
|
||||
{
|
||||
T* readPosition = _data;
|
||||
T* searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText));
|
||||
while (searchPosition != nullptr)
|
||||
{
|
||||
replacedCount++;
|
||||
readPosition = searchPosition + searchTextLength;
|
||||
searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText));
|
||||
}
|
||||
|
||||
const auto oldLength = _length;
|
||||
const auto oldData = _data;
|
||||
_length += replacedCount * (replacementTextLength - searchTextLength);
|
||||
_data = (T*)Platform::Allocate((_length + 1) * sizeof(T), 16);
|
||||
|
||||
T* writePosition = _data;
|
||||
readPosition = oldData;
|
||||
replacedCount++;
|
||||
readPosition = searchPosition + searchTextLength;
|
||||
searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText));
|
||||
while (searchPosition != nullptr)
|
||||
{
|
||||
const int32 writeOffset = (int32)(searchPosition - readPosition);
|
||||
Platform::MemoryCopy(writePosition, readPosition, writeOffset * sizeof(T));
|
||||
writePosition += writeOffset;
|
||||
|
||||
Platform::MemoryCopy(writePosition, replacementText, replacementTextLength * sizeof(T));
|
||||
writePosition += replacementTextLength;
|
||||
|
||||
readPosition = searchPosition + searchTextLength;
|
||||
searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText));
|
||||
}
|
||||
|
||||
const int32 writeOffset = (int32)(oldData - readPosition) + oldLength;
|
||||
Platform::MemoryCopy(writePosition, readPosition, writeOffset * sizeof(T));
|
||||
|
||||
_data[_length] = 0;
|
||||
Platform::Free(oldData);
|
||||
}
|
||||
|
||||
const auto oldLength = _length;
|
||||
const auto oldData = _data;
|
||||
_length += replacedCount * (replacementTextLength - searchTextLength);
|
||||
_data = (T*)Platform::Allocate((_length + 1) * sizeof(T), 16);
|
||||
|
||||
T* writePosition = _data;
|
||||
readPosition = oldData;
|
||||
searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText));
|
||||
while (searchPosition != nullptr)
|
||||
{
|
||||
const int32 writeOffset = (int32)(searchPosition - readPosition);
|
||||
Platform::MemoryCopy(writePosition, readPosition, writeOffset * sizeof(T));
|
||||
writePosition += writeOffset;
|
||||
|
||||
if (replacementTextLength > 0)
|
||||
Platform::MemoryCopy(writePosition, replacementText, replacementTextLength * sizeof(T));
|
||||
writePosition += replacementTextLength;
|
||||
|
||||
readPosition = searchPosition + searchTextLength;
|
||||
searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText));
|
||||
}
|
||||
|
||||
const int32 writeOffset = (int32)(oldData - readPosition) + oldLength;
|
||||
Platform::MemoryCopy(writePosition, readPosition, writeOffset * sizeof(T));
|
||||
|
||||
_data[_length] = 0;
|
||||
Platform::Free(oldData);
|
||||
}
|
||||
|
||||
return replacedCount;
|
||||
|
||||
@@ -18,12 +18,12 @@ StringView::StringView(const String& str)
|
||||
|
||||
bool StringView::operator==(const String& other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), *other) == 0;
|
||||
return this->Compare(StringView(other)) == 0;
|
||||
}
|
||||
|
||||
bool StringView::operator!=(const String& other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), *other) != 0;
|
||||
return this->Compare(StringView(other)) != 0;
|
||||
}
|
||||
|
||||
StringView StringView::Left(int32 count) const
|
||||
@@ -62,12 +62,12 @@ StringAnsi StringView::ToStringAnsi() const
|
||||
|
||||
bool operator==(const String& a, const StringView& b)
|
||||
{
|
||||
return a.Length() == b.Length() && StringUtils::Compare(a.GetText(), b.GetText(), b.Length()) == 0;
|
||||
return a.Length() == b.Length() && StringUtils::Compare(a.GetText(), b.GetNonTerminatedText(), b.Length()) == 0;
|
||||
}
|
||||
|
||||
bool operator!=(const String& a, const StringView& b)
|
||||
{
|
||||
return a.Length() != b.Length() || StringUtils::Compare(a.GetText(), b.GetText(), b.Length()) != 0;
|
||||
return a.Length() != b.Length() || StringUtils::Compare(a.GetText(), b.GetNonTerminatedText(), b.Length()) != 0;
|
||||
}
|
||||
|
||||
StringAnsiView StringAnsiView::Empty;
|
||||
@@ -79,12 +79,12 @@ StringAnsiView::StringAnsiView(const StringAnsi& str)
|
||||
|
||||
bool StringAnsiView::operator==(const StringAnsi& other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), *other) == 0;
|
||||
return this->Compare(StringAnsiView(other)) == 0;
|
||||
}
|
||||
|
||||
bool StringAnsiView::operator!=(const StringAnsi& other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), *other) != 0;
|
||||
return this->Compare(StringAnsiView(other)) != 0;
|
||||
}
|
||||
|
||||
StringAnsi StringAnsiView::Substring(int32 startIndex) const
|
||||
@@ -111,10 +111,10 @@ StringAnsi StringAnsiView::ToStringAnsi() const
|
||||
|
||||
bool operator==(const StringAnsi& a, const StringAnsiView& b)
|
||||
{
|
||||
return a.Length() == b.Length() && StringUtils::Compare(a.GetText(), b.GetText(), b.Length()) == 0;
|
||||
return a.Length() == b.Length() && StringUtils::Compare(a.GetText(), b.GetNonTerminatedText(), b.Length()) == 0;
|
||||
}
|
||||
|
||||
bool operator!=(const StringAnsi& a, const StringAnsiView& b)
|
||||
{
|
||||
return a.Length() != b.Length() || StringUtils::Compare(a.GetText(), b.GetText(), b.Length()) != 0;
|
||||
return a.Length() != b.Length() || StringUtils::Compare(a.GetText(), b.GetNonTerminatedText(), b.Length()) != 0;
|
||||
}
|
||||
|
||||
@@ -54,18 +54,23 @@ public:
|
||||
|
||||
/// <summary>
|
||||
/// Lexicographically tests how this string compares to the other given string.
|
||||
/// In case sensitive mode 'A' is less than 'a'.
|
||||
/// </summary>
|
||||
/// <param name="str">The another string test against.</param>
|
||||
/// <param name="searchCase">The case sensitivity mode.</param>
|
||||
/// <returns>0 if equal, -1 if less than, 1 if greater than.</returns>
|
||||
/// <returns>0 if equal, negative number if less than, positive number if greater than.</returns>
|
||||
int32 Compare(const StringViewBase& str, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
|
||||
{
|
||||
const int32 lengthDiff = Length() - str.Length();
|
||||
if (lengthDiff != 0)
|
||||
return lengthDiff;
|
||||
if (searchCase == StringSearchCase::CaseSensitive)
|
||||
return StringUtils::Compare(this->GetText(), str.GetText(), Length());
|
||||
return StringUtils::CompareIgnoreCase(this->GetText(), str.GetText(), Length());
|
||||
const bool thisIsShorter = Length() < str.Length();
|
||||
const int32 minLength = thisIsShorter ? Length() : str.Length();
|
||||
const int32 prefixCompare = (searchCase == StringSearchCase::CaseSensitive)
|
||||
? StringUtils::Compare(this->GetNonTerminatedText(), str.GetNonTerminatedText(), minLength)
|
||||
: StringUtils::CompareIgnoreCase(this->GetNonTerminatedText(), str.GetNonTerminatedText(), minLength);
|
||||
if (prefixCompare != 0)
|
||||
return prefixCompare;
|
||||
if (Length() == str.Length())
|
||||
return 0;
|
||||
return thisIsShorter ? -1 : 1;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -95,7 +100,7 @@ public:
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the string.
|
||||
/// Gets the pointer to the string. Pointer can be null, and won't be null-terminated.
|
||||
/// </summary>
|
||||
FORCE_INLINE constexpr const T* operator*() const
|
||||
{
|
||||
@@ -103,7 +108,7 @@ public:
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the string.
|
||||
/// Gets the pointer to the string. Pointer can be null, and won't be null-terminated.
|
||||
/// </summary>
|
||||
FORCE_INLINE constexpr const T* Get() const
|
||||
{
|
||||
@@ -111,9 +116,9 @@ public:
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the string or to the static empty text if string is null. Returned pointer is always valid (read-only).
|
||||
/// Gets the pointer to the string or to the static empty text if string is null. Returned pointer is always non-null, but is not null-terminated.
|
||||
/// </summary>
|
||||
FORCE_INLINE const T* GetText() const
|
||||
FORCE_INLINE const T* GetNonTerminatedText() const
|
||||
{
|
||||
return _data ? _data : (const T*)TEXT("");
|
||||
}
|
||||
@@ -177,15 +182,17 @@ public:
|
||||
{
|
||||
if (prefix.IsEmpty() || Length() < prefix.Length())
|
||||
return false;
|
||||
// We know that this StringView is not empty, and therefore Get() below is valid.
|
||||
if (searchCase == StringSearchCase::IgnoreCase)
|
||||
return StringUtils::CompareIgnoreCase(this->GetText(), *prefix, prefix.Length()) == 0;
|
||||
return StringUtils::Compare(this->GetText(), *prefix, prefix.Length()) == 0;
|
||||
return StringUtils::CompareIgnoreCase(this->Get(), *prefix, prefix.Length()) == 0;
|
||||
return StringUtils::Compare(this->Get(), *prefix, prefix.Length()) == 0;
|
||||
}
|
||||
|
||||
bool EndsWith(const StringViewBase& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
|
||||
{
|
||||
if (suffix.IsEmpty() || Length() < suffix.Length())
|
||||
return false;
|
||||
// We know that this StringView is not empty, and therefore accessing data below is valid.
|
||||
if (searchCase == StringSearchCase::IgnoreCase)
|
||||
return StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix) == 0;
|
||||
return StringUtils::Compare(&(*this)[Length() - suffix.Length()], *suffix) == 0;
|
||||
@@ -232,7 +239,7 @@ public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="str">The characters sequence.</param>
|
||||
/// <param name="str">The characters sequence. If null, constructed StringView will be empty.</param>
|
||||
StringView(const Char* str)
|
||||
{
|
||||
_data = str;
|
||||
@@ -242,7 +249,7 @@ public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="str">The characters sequence.</param>
|
||||
/// <param name="str">The characters sequence. Can be null if length is zero.</param>
|
||||
/// <param name="length">The characters sequence length (excluding null-terminator character).</param>
|
||||
constexpr StringView(const Char* str, int32 length)
|
||||
: StringViewBase<Char>(str, length)
|
||||
@@ -270,7 +277,7 @@ public:
|
||||
/// <returns>True if this string is lexicographically equivalent to the other, otherwise false.</returns>
|
||||
FORCE_INLINE bool operator==(const StringView& other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), other.GetText()) == 0;
|
||||
return this->Compare(other) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -280,7 +287,7 @@ public:
|
||||
/// <returns>True if this string is lexicographically is not equivalent to the other, otherwise false.</returns>
|
||||
FORCE_INLINE bool operator!=(const StringView& other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), other.GetText()) != 0;
|
||||
return this->Compare(other) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -290,7 +297,7 @@ public:
|
||||
/// <returns>True if this string is lexicographically equivalent to the other, otherwise false.</returns>
|
||||
FORCE_INLINE bool operator==(const Char* other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), other ? other : TEXT("")) == 0;
|
||||
return this->Compare(StringView(other)) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -300,7 +307,7 @@ public:
|
||||
/// <returns>True if this string is lexicographically is not equivalent to the other, otherwise false.</returns>
|
||||
FORCE_INLINE bool operator!=(const Char* other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), other ? other : TEXT("")) != 0;
|
||||
return this->Compare(StringView(other)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -459,7 +466,7 @@ public:
|
||||
/// <returns>True if this string is lexicographically equivalent to the other, otherwise false.</returns>
|
||||
FORCE_INLINE bool operator==(const StringAnsiView& other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), other.GetText()) == 0;
|
||||
return this->Compare(other) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -469,7 +476,7 @@ public:
|
||||
/// <returns>True if this string is lexicographically is not equivalent to the other, otherwise false.</returns>
|
||||
FORCE_INLINE bool operator!=(const StringAnsiView& other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), other.GetText()) != 0;
|
||||
return this->Compare(other) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -479,7 +486,7 @@ public:
|
||||
/// <returns>True if this string is lexicographically equivalent to the other, otherwise false.</returns>
|
||||
FORCE_INLINE bool operator==(const char* other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), other ? other : "") == 0;
|
||||
return this->Compare(StringAnsiView(other)) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -489,7 +496,7 @@ public:
|
||||
/// <returns>True if this string is lexicographically is not equivalent to the other, otherwise false.</returns>
|
||||
FORCE_INLINE bool operator!=(const char* other) const
|
||||
{
|
||||
return StringUtils::Compare(this->GetText(), other ? other : "") != 0;
|
||||
return this->Compare(StringAnsiView(other)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -270,8 +270,8 @@ public:
|
||||
explicit operator float() const;
|
||||
explicit operator double() const;
|
||||
explicit operator void*() const;
|
||||
explicit operator StringView() const;
|
||||
explicit operator StringAnsiView() const;
|
||||
explicit operator StringView() const; // Returned StringView, if not empty, is guaranteed to point to a null terminated buffer.
|
||||
explicit operator StringAnsiView() const; // Returned StringView, if not empty, is guaranteed to point to a null terminated buffer.
|
||||
explicit operator ScriptingObject*() const;
|
||||
explicit operator struct _MonoObject*() const;
|
||||
explicit operator Asset*() const;
|
||||
|
||||
@@ -150,6 +150,7 @@ bool CommandLine::Parse(const Char* cmdLine)
|
||||
PARSE_BOOL_SWITCH("-clearcache ", ClearCache);
|
||||
PARSE_BOOL_SWITCH("-clearcooker ", ClearCookerCache);
|
||||
PARSE_ARG_SWITCH("-project ", Project);
|
||||
PARSE_BOOL_SWITCH("-new ", NewProject);
|
||||
PARSE_BOOL_SWITCH("-genprojectfiles ", GenProjectFiles);
|
||||
PARSE_ARG_SWITCH("-build ", Build);
|
||||
PARSE_BOOL_SWITCH("-skipcompile ", SkipCompile);
|
||||
|
||||
@@ -134,6 +134,11 @@ public:
|
||||
/// </summary>
|
||||
String Project;
|
||||
|
||||
/// <summary>
|
||||
/// -new (generates the project files inside the specified project folder or uses current workspace folder)
|
||||
/// </summary>
|
||||
Nullable<bool> NewProject;
|
||||
|
||||
/// <summary>
|
||||
/// -genprojectfiles (generates the scripts project files)
|
||||
/// </summary>
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace FlaxEngine
|
||||
if (colors != null && colors.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(colors));
|
||||
|
||||
if (Internal_UpdateMeshInt(
|
||||
if (Internal_UpdateMeshUInt(
|
||||
__unmanagedPtr,
|
||||
vertices.Length,
|
||||
triangles.Length / 3,
|
||||
@@ -190,7 +190,100 @@ namespace FlaxEngine
|
||||
if (colors != null && colors.Count != vertices.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(colors));
|
||||
|
||||
if (Internal_UpdateMeshInt(
|
||||
if (Internal_UpdateMeshUInt(
|
||||
__unmanagedPtr,
|
||||
vertices.Count,
|
||||
triangles.Count / 3,
|
||||
Utils.ExtractArrayFromList(vertices),
|
||||
Utils.ExtractArrayFromList(triangles),
|
||||
Utils.ExtractArrayFromList(normals),
|
||||
Utils.ExtractArrayFromList(tangents),
|
||||
Utils.ExtractArrayFromList(uv),
|
||||
Utils.ExtractArrayFromList(colors)
|
||||
))
|
||||
throw new FlaxException("Failed to update mesh data.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh vertex and index buffer data.
|
||||
/// Can be used only for virtual assets (see <see cref="Asset.IsVirtual"/> and <see cref="Content.CreateVirtualAsset{T}"/>).
|
||||
/// Mesh data will be cached and uploaded to the GPU with a delay.
|
||||
/// </summary>
|
||||
/// <param name="vertices">The mesh vertices positions. Cannot be null.</param>
|
||||
/// <param name="triangles">The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.</param>
|
||||
/// <param name="normals">The normal vectors (per vertex).</param>
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
public void UpdateMesh(Vector3[] vertices, uint[] triangles, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null, Color32[] colors = null)
|
||||
{
|
||||
// Validate state and input
|
||||
if (!ParentModel.IsVirtual)
|
||||
throw new InvalidOperationException("Only virtual models can be updated at runtime.");
|
||||
if (vertices == null)
|
||||
throw new ArgumentNullException(nameof(vertices));
|
||||
if (triangles == null)
|
||||
throw new ArgumentNullException(nameof(triangles));
|
||||
if (triangles.Length == 0 || triangles.Length % 3 != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(triangles));
|
||||
if (normals != null && normals.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(normals));
|
||||
if (tangents != null && tangents.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(tangents));
|
||||
if (tangents != null && normals == null)
|
||||
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
|
||||
if (uv != null && uv.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(uv));
|
||||
if (colors != null && colors.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(colors));
|
||||
|
||||
if (Internal_UpdateMeshUInt(
|
||||
__unmanagedPtr,
|
||||
vertices.Length,
|
||||
triangles.Length / 3,
|
||||
vertices, triangles,
|
||||
normals,
|
||||
tangents,
|
||||
uv,
|
||||
colors
|
||||
))
|
||||
throw new FlaxException("Failed to update mesh data.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh vertex and index buffer data.
|
||||
/// Can be used only for virtual assets (see <see cref="Asset.IsVirtual"/> and <see cref="Content.CreateVirtualAsset{T}"/>).
|
||||
/// Mesh data will be cached and uploaded to the GPU with a delay.
|
||||
/// </summary>
|
||||
/// <param name="vertices">The mesh vertices positions. Cannot be null.</param>
|
||||
/// <param name="triangles">The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.</param>
|
||||
/// <param name="normals">The normal vectors (per vertex).</param>
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
public void UpdateMesh(List<Vector3> vertices, List<uint> triangles, List<Vector3> normals = null, List<Vector3> tangents = null, List<Vector2> uv = null, List<Color32> colors = null)
|
||||
{
|
||||
// Validate state and input
|
||||
if (!ParentModel.IsVirtual)
|
||||
throw new InvalidOperationException("Only virtual models can be updated at runtime.");
|
||||
if (vertices == null)
|
||||
throw new ArgumentNullException(nameof(vertices));
|
||||
if (triangles == null)
|
||||
throw new ArgumentNullException(nameof(triangles));
|
||||
if (triangles.Count == 0 || triangles.Count % 3 != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(triangles));
|
||||
if (normals != null && normals.Count != vertices.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(normals));
|
||||
if (tangents != null && tangents.Count != vertices.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(tangents));
|
||||
if (tangents != null && normals == null)
|
||||
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
|
||||
if (uv != null && uv.Count != vertices.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(uv));
|
||||
if (colors != null && colors.Count != vertices.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(colors));
|
||||
|
||||
if (Internal_UpdateMeshUInt(
|
||||
__unmanagedPtr,
|
||||
vertices.Count,
|
||||
triangles.Count / 3,
|
||||
@@ -314,7 +407,7 @@ namespace FlaxEngine
|
||||
if (triangles.Length == 0 || triangles.Length % 3 != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(triangles));
|
||||
|
||||
if (Internal_UpdateTrianglesInt(
|
||||
if (Internal_UpdateTrianglesUInt(
|
||||
__unmanagedPtr,
|
||||
triangles.Length / 3,
|
||||
triangles
|
||||
@@ -338,7 +431,7 @@ namespace FlaxEngine
|
||||
if (triangles.Count == 0 || triangles.Count % 3 != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(triangles));
|
||||
|
||||
if (Internal_UpdateTrianglesInt(
|
||||
if (Internal_UpdateTrianglesUInt(
|
||||
__unmanagedPtr,
|
||||
triangles.Count / 3,
|
||||
Utils.ExtractArrayFromList(triangles)
|
||||
@@ -499,10 +592,10 @@ namespace FlaxEngine
|
||||
/// <remarks>If mesh index buffer format (see <see cref="IndexBufferFormat"/>) is <see cref="PixelFormat.R16_UInt"/> then it's faster to call .</remarks>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
public int[] DownloadIndexBuffer(bool forceGpu = false)
|
||||
public uint[] DownloadIndexBuffer(bool forceGpu = false)
|
||||
{
|
||||
var triangles = TriangleCount;
|
||||
var result = new int[triangles * 3];
|
||||
var result = new uint[triangles * 3];
|
||||
if (Internal_DownloadBuffer(__unmanagedPtr, forceGpu, result, (int)InternalBufferType.IB32))
|
||||
throw new FlaxException("Failed to download mesh data.");
|
||||
return result;
|
||||
|
||||
@@ -608,7 +608,7 @@ ScriptingObject* Mesh::GetParentModel()
|
||||
return _model;
|
||||
}
|
||||
|
||||
bool Mesh::UpdateMeshInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj)
|
||||
bool Mesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj)
|
||||
{
|
||||
return ::UpdateMesh<uint32>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
|
||||
}
|
||||
@@ -618,7 +618,7 @@ bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MonoArray* v
|
||||
return ::UpdateMesh<uint16>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
|
||||
}
|
||||
|
||||
bool Mesh::UpdateTrianglesInt(int32 triangleCount, MonoArray* trianglesObj)
|
||||
bool Mesh::UpdateTrianglesUInt(int32 triangleCount, MonoArray* trianglesObj)
|
||||
{
|
||||
return ::UpdateTriangles<uint32>(this, triangleCount, trianglesObj);
|
||||
}
|
||||
|
||||
@@ -404,9 +404,9 @@ private:
|
||||
|
||||
// Internal bindings
|
||||
API_FUNCTION(NoProxy) ScriptingObject* GetParentModel();
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateTrianglesInt(int32 triangleCount, MonoArray* trianglesObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, MonoArray* trianglesObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, MonoArray* trianglesObj);
|
||||
API_FUNCTION(NoProxy) bool DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 typeI);
|
||||
};
|
||||
|
||||
@@ -418,9 +418,9 @@ bool UpdateMesh(SkinnedMesh* mesh, MonoArray* verticesObj, MonoArray* trianglesO
|
||||
return mesh->UpdateMesh(vertexCount, triangleCount, vb.Get(), ib);
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMeshInt(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj)
|
||||
bool SkinnedMesh::UpdateMeshUInt(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj)
|
||||
{
|
||||
return ::UpdateMesh<int32>(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj);
|
||||
return ::UpdateMesh<uint32>(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj);
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMeshUShort(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj)
|
||||
|
||||
@@ -117,6 +117,19 @@ public:
|
||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vb">The vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer in clockwise order.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, uint32* ib)
|
||||
{
|
||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// </summary>
|
||||
@@ -245,7 +258,7 @@ private:
|
||||
|
||||
// Internal bindings
|
||||
API_FUNCTION(NoProxy) ScriptingObject* GetParentModel();
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshInt(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshUInt(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshUShort(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj);
|
||||
API_FUNCTION(NoProxy) bool DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 typeI);
|
||||
};
|
||||
|
||||
@@ -130,7 +130,43 @@ namespace FlaxEngine
|
||||
if (uv != null && uv.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(uv));
|
||||
|
||||
if (Internal_UpdateMeshInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv))
|
||||
if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv))
|
||||
throw new FlaxException("Failed to update mesh data.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the skinned model mesh vertex and index buffer data.
|
||||
/// Can be used only for virtual assets (see <see cref="Asset.IsVirtual"/> and <see cref="Content.CreateVirtualAsset{T}"/>).
|
||||
/// Mesh data will be cached and uploaded to the GPU with a delay.
|
||||
/// </summary>
|
||||
/// <param name="vertices">The mesh vertices positions. Cannot be null.</param>
|
||||
/// <param name="triangles">The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.</param>
|
||||
/// <param name="blendIndices">The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null.</param>
|
||||
/// <param name="blendWeights">The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null.</param>
|
||||
/// <param name="normals">The normal vectors (per vertex).</param>
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
public void UpdateMesh(Vector3[] vertices, uint[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null)
|
||||
{
|
||||
// Validate state and input
|
||||
if (!ParentSkinnedModel.IsVirtual)
|
||||
throw new InvalidOperationException("Only virtual skinned models can be updated at runtime.");
|
||||
if (vertices == null)
|
||||
throw new ArgumentNullException(nameof(vertices));
|
||||
if (triangles == null)
|
||||
throw new ArgumentNullException(nameof(triangles));
|
||||
if (triangles.Length == 0 || triangles.Length % 3 != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(triangles));
|
||||
if (normals != null && normals.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(normals));
|
||||
if (tangents != null && tangents.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(tangents));
|
||||
if (tangents != null && normals == null)
|
||||
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
|
||||
if (uv != null && uv.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(uv));
|
||||
|
||||
if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv))
|
||||
throw new FlaxException("Failed to update mesh data.");
|
||||
}
|
||||
|
||||
@@ -227,10 +263,10 @@ namespace FlaxEngine
|
||||
/// <remarks>If mesh index buffer format (see <see cref="IndexBufferFormat"/>) is <see cref="PixelFormat.R16_UInt"/> then it's faster to call .</remarks>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
public int[] DownloadIndexBuffer(bool forceGpu = false)
|
||||
public uint[] DownloadIndexBuffer(bool forceGpu = false)
|
||||
{
|
||||
var triangles = TriangleCount;
|
||||
var result = new int[triangles * 3];
|
||||
var result = new uint[triangles * 3];
|
||||
if (Internal_DownloadBuffer(__unmanagedPtr, forceGpu, result, (int)InternalBufferType.IB32))
|
||||
throw new FlaxException("Failed to download mesh data.");
|
||||
return result;
|
||||
|
||||
@@ -73,6 +73,7 @@ public:
|
||||
}
|
||||
|
||||
void Update() override;
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
InputService InputServiceInstance;
|
||||
@@ -914,3 +915,24 @@ void InputService::Update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputService::Dispose()
|
||||
{
|
||||
// Dispose input devices
|
||||
if (Input::Mouse)
|
||||
{
|
||||
Input::Mouse->DeleteObject();
|
||||
Input::Mouse = nullptr;
|
||||
}
|
||||
if (Input::Keyboard)
|
||||
{
|
||||
Input::Keyboard->DeleteObject();
|
||||
Input::Keyboard = nullptr;
|
||||
}
|
||||
for (int32 i = 0; i < Input::Gamepads.Count(); i++)
|
||||
Input::Gamepads[i]->DeleteObject();
|
||||
Input::Gamepads.Clear();
|
||||
for (int32 i = 0; i < Input::CustomDevices.Count(); i++)
|
||||
Input::CustomDevices[i]->DeleteObject();
|
||||
Input::CustomDevices.Clear();
|
||||
}
|
||||
|
||||
@@ -1424,10 +1424,24 @@ Actor* Actor::Intersects(const Ray& ray, float& distance, Vector3& normal)
|
||||
}
|
||||
|
||||
void Actor::LookAt(const Vector3& worldPos)
|
||||
{
|
||||
const Quaternion orientation = LookingAt(worldPos);
|
||||
|
||||
SetOrientation(orientation);
|
||||
}
|
||||
|
||||
void Actor::LookAt(const Vector3& worldPos, const Vector3& worldUp)
|
||||
{
|
||||
const Quaternion orientation = LookingAt(worldPos, worldUp);
|
||||
|
||||
SetOrientation(orientation);
|
||||
}
|
||||
|
||||
Quaternion Actor::LookingAt(const Vector3& worldPos)
|
||||
{
|
||||
const Vector3 direction = worldPos - _transform.Translation;
|
||||
if (direction.LengthSquared() < ZeroTolerance)
|
||||
return;
|
||||
return _parent->GetOrientation();
|
||||
|
||||
const Vector3 newForward = Vector3::Normalize(direction);
|
||||
const Vector3 oldForward = _transform.Orientation * Vector3::Forward;
|
||||
@@ -1447,26 +1461,25 @@ void Actor::LookAt(const Vector3& worldPos)
|
||||
orientation = rotQuat * _transform.Orientation;
|
||||
}
|
||||
|
||||
SetOrientation(orientation);
|
||||
return orientation;
|
||||
}
|
||||
|
||||
void Actor::LookAt(const Vector3& worldPos, const Vector3& worldUp)
|
||||
Quaternion Actor::LookingAt(const Vector3& worldPos, const Vector3& worldUp)
|
||||
{
|
||||
const Vector3 direction = worldPos - _transform.Translation;
|
||||
if (direction.LengthSquared() < ZeroTolerance)
|
||||
return;
|
||||
return _parent->GetOrientation();
|
||||
const Vector3 forward = Vector3::Normalize(direction);
|
||||
const Vector3 up = Vector3::Normalize(worldUp);
|
||||
|
||||
if (Math::IsOne(Vector3::Dot(forward, up)))
|
||||
{
|
||||
LookAt(worldPos);
|
||||
return;
|
||||
return LookingAt(worldPos);
|
||||
}
|
||||
|
||||
Quaternion orientation;
|
||||
Quaternion::LookRotation(direction, up, orientation);
|
||||
SetOrientation(orientation);
|
||||
return orientation;
|
||||
}
|
||||
|
||||
void WriteObjectToBytes(SceneObject* obj, rapidjson_flax::StringBuffer& buffer, MemoryWriteStream& output)
|
||||
|
||||
@@ -792,6 +792,19 @@ public:
|
||||
/// <param name="worldUp">The up direction that Constrains y axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel.</param>
|
||||
API_FUNCTION() void LookAt(const Vector3& worldPos, const Vector3& worldUp);
|
||||
|
||||
/// <summary>
|
||||
/// Gets rotation of the actor oriented towards the specified world position.
|
||||
/// </summary>
|
||||
/// <param name="worldPos">The world position to orient towards.</param>
|
||||
API_FUNCTION() Quaternion LookingAt(const Vector3& worldPos);
|
||||
|
||||
/// <summary>
|
||||
/// Gets rotation of the actor oriented towards the specified world position with upwards direction.
|
||||
/// </summary>
|
||||
/// <param name="worldPos">The world position to orient towards.</param>
|
||||
/// <param name="worldUp">The up direction that Constrains y axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel.</param>
|
||||
API_FUNCTION() Quaternion LookingAt(const Vector3& worldPos, const Vector3& worldUp);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -278,18 +278,18 @@ String Localization::GetPluralString(const String& id, int32 n, const String& fa
|
||||
{
|
||||
CHECK_RETURN(n >= 1, fallback);
|
||||
n--;
|
||||
StringView result;
|
||||
const String* result = nullptr;
|
||||
for (auto& e : Instance.LocalizedStringTables)
|
||||
{
|
||||
const auto table = e.Get();
|
||||
const auto messages = table ? table->Entries.TryGet(id) : nullptr;
|
||||
if (messages && messages->Count() > n)
|
||||
{
|
||||
result = messages->At(n);
|
||||
result = &messages->At(n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result.IsEmpty())
|
||||
result = fallback;
|
||||
return String::Format(result.GetText(), n);
|
||||
if (!result)
|
||||
result = &fallback;
|
||||
return String::Format(result->GetText(), n);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ void ENetDriver::Disconnect(const NetworkConnection& connection)
|
||||
void* peer = nullptr;
|
||||
if(_peerMap.TryGet(connectionId, peer))
|
||||
{
|
||||
enet_peer_disconnect_now((ENetPeer*)_peer, 0);
|
||||
enet_peer_disconnect_now((ENetPeer*)peer, 0);
|
||||
_peerMap.Remove(connectionId);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -78,6 +78,21 @@ bool CollisionData::CookCollision(CollisionDataType type, const Span<Vector3>& v
|
||||
return CookCollision(type, &modelData, convexFlags, convexVertexLimit);
|
||||
}
|
||||
|
||||
bool CollisionData::CookCollision(CollisionDataType type, const Span<Vector3>& vertices, const Span<int32>& triangles, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
|
||||
{
|
||||
CHECK_RETURN(vertices.Length() != 0, true);
|
||||
CHECK_RETURN(triangles.Length() != 0 && triangles.Length() % 3 == 0, true);
|
||||
ModelData modelData;
|
||||
modelData.LODs.Resize(1);
|
||||
auto meshData = New<MeshData>();
|
||||
modelData.LODs[0].Meshes.Add(meshData);
|
||||
meshData->Positions.Set(vertices.Get(), vertices.Length());
|
||||
meshData->Indices.Resize(triangles.Length());
|
||||
for (int32 i = 0; i < triangles.Length(); i++)
|
||||
meshData->Indices.Get()[i] = triangles.Get()[i];
|
||||
return CookCollision(type, &modelData, convexFlags, convexVertexLimit);
|
||||
}
|
||||
|
||||
bool CollisionData::CookCollision(CollisionDataType type, ModelData* modelData, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
|
||||
{
|
||||
// Validate state
|
||||
|
||||
@@ -223,6 +223,19 @@ public:
|
||||
/// <param name="convexVertexLimit">The convex mesh vertex limit. Use values in range [8;255]</param>
|
||||
API_FUNCTION() bool CookCollision(CollisionDataType type, const Span<Vector3>& vertices, const Span<uint32>& triangles, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags::None, int32 convexVertexLimit = 255);
|
||||
|
||||
/// <summary>
|
||||
/// Cooks the mesh collision data and updates the virtual asset. action cannot be performed on a main thread.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can be used only for virtual assets (see <see cref="Asset.IsVirtual"/> and <see cref="Content.CreateVirtualAsset{T}"/>).
|
||||
/// </remarks>
|
||||
/// <param name="type">The collision data type.</param>
|
||||
/// <param name="vertices">The source geometry vertex buffer with vertices positions. Cannot be empty.</param>
|
||||
/// <param name="triangles">The source data index buffer (triangles list). Uses 32-bit stride buffer. Cannot be empty. Length must be multiple of 3 (as 3 vertices build a triangle).</param>
|
||||
/// <param name="convexFlags">The convex mesh generation flags.</param>
|
||||
/// <param name="convexVertexLimit">The convex mesh vertex limit. Use values in range [8;255]</param>
|
||||
API_FUNCTION() bool CookCollision(CollisionDataType type, const Span<Vector3>& vertices, const Span<int32>& triangles, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags::None, int32 convexVertexLimit = 255);
|
||||
|
||||
/// <summary>
|
||||
/// Cooks the mesh collision data and updates the virtual asset. action cannot be performed on a main thread.
|
||||
/// </summary>
|
||||
|
||||
@@ -167,7 +167,7 @@ void PlatformBase::Exit()
|
||||
|
||||
#if COMPILE_WITH_PROFILER
|
||||
|
||||
#define TRACY_ENABLE_MEMORY (TRACY_ENABLE && !USE_EDITOR)
|
||||
#define TRACY_ENABLE_MEMORY (TRACY_ENABLE)
|
||||
|
||||
void PlatformBase::OnMemoryAlloc(void* ptr, uint64 size)
|
||||
{
|
||||
|
||||
@@ -169,8 +169,8 @@ public:
|
||||
/// <summary>
|
||||
/// Copy memory region
|
||||
/// </summary>
|
||||
/// <param name="dst">Destination memory address</param>
|
||||
/// <param name="src">Source memory address</param>
|
||||
/// <param name="dst">Destination memory address. Must not be null, even if size is zero.</param>
|
||||
/// <param name="src">Source memory address. Must not be null, even if size is zero.</param>
|
||||
/// <param name="size">Size of the memory to copy in bytes</param>
|
||||
FORCE_INLINE static void MemoryCopy(void* dst, const void* src, uint64 size)
|
||||
{
|
||||
@@ -180,7 +180,7 @@ public:
|
||||
/// <summary>
|
||||
/// Set memory region with given value
|
||||
/// </summary>
|
||||
/// <param name="dst">Destination memory address</param>
|
||||
/// <param name="dst">Destination memory address. Must not be null, even if size is zero.</param>
|
||||
/// <param name="size">Size of the memory to set in bytes</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
FORCE_INLINE static void MemorySet(void* dst, uint64 size, int32 value)
|
||||
@@ -191,7 +191,7 @@ public:
|
||||
/// <summary>
|
||||
/// Clear memory region with zeros
|
||||
/// </summary>
|
||||
/// <param name="dst">Destination memory address</param>
|
||||
/// <param name="dst">Destination memory address. Must not be null, even if size is zero.</param>
|
||||
/// <param name="size">Size of the memory to clear in bytes</param>
|
||||
FORCE_INLINE static void MemoryClear(void* dst, uint64 size)
|
||||
{
|
||||
@@ -201,8 +201,8 @@ public:
|
||||
/// <summary>
|
||||
/// Compare two blocks of the memory.
|
||||
/// </summary>
|
||||
/// <param name="buf1">The first buffer address.</param>
|
||||
/// <param name="buf2">The second buffer address.</param>
|
||||
/// <param name="buf1">The first buffer address. Must not be null, even if size is zero.</param>
|
||||
/// <param name="buf2">The second buffer address. Must not be null, even if size is zero.</param>
|
||||
/// <param name="size">Size of the memory to compare in bytes.</param>
|
||||
FORCE_INLINE static int32 MemoryCompare(const void* buf1, const void* buf2, uint64 size)
|
||||
{
|
||||
|
||||
@@ -1659,7 +1659,7 @@ void LinuxClipboard::SetText(const StringView& text)
|
||||
return;
|
||||
X11::Window window = (X11::Window)mainWindow->GetNativePtr();
|
||||
|
||||
Impl::ClipboardText.Set(text.GetText(), text.Length());
|
||||
Impl::ClipboardText.Set(text.Get(), text.Length());
|
||||
X11::XSetSelectionOwner(xDisplay, xAtomClipboard, window, CurrentTime); // CLIPBOARD
|
||||
X11::XSetSelectionOwner(xDisplay, (X11::Atom)1, window, CurrentTime); // XA_PRIMARY
|
||||
}
|
||||
|
||||
@@ -120,36 +120,36 @@ public:
|
||||
|
||||
public:
|
||||
|
||||
// Compare two strings with case sensitive
|
||||
// Compare two strings with case sensitive. Strings must not be null.
|
||||
static int32 Compare(const Char* str1, const Char* str2);
|
||||
|
||||
// Compare two strings without case sensitive
|
||||
// Compare two strings without case sensitive. Strings must not be null.
|
||||
static int32 Compare(const Char* str1, const Char* str2, int32 maxCount);
|
||||
|
||||
// Compare two strings without case sensitive
|
||||
// Compare two strings without case sensitive. Strings must not be null.
|
||||
static int32 CompareIgnoreCase(const Char* str1, const Char* str2);
|
||||
|
||||
// Compare two strings without case sensitive
|
||||
// Compare two strings without case sensitive. Strings must not be null.
|
||||
static int32 CompareIgnoreCase(const Char* str1, const Char* str2, int32 maxCount);
|
||||
|
||||
// Compare two strings with case sensitive
|
||||
// Compare two strings with case sensitive. Strings must not be null.
|
||||
static int32 Compare(const char* str1, const char* str2);
|
||||
|
||||
// Compare two strings without case sensitive
|
||||
// Compare two strings without case sensitive. Strings must not be null.
|
||||
static int32 Compare(const char* str1, const char* str2, int32 maxCount);
|
||||
|
||||
// Compare two strings without case sensitive
|
||||
// Compare two strings without case sensitive. Strings must not be null.
|
||||
static int32 CompareIgnoreCase(const char* str1, const char* str2);
|
||||
|
||||
// Compare two strings without case sensitive
|
||||
// Compare two strings without case sensitive. Strings must not be null.
|
||||
static int32 CompareIgnoreCase(const char* str1, const char* str2, int32 maxCount);
|
||||
|
||||
public:
|
||||
|
||||
// Get string length
|
||||
// Get string length. Returns 0 if str is null.
|
||||
static int32 Length(const Char* str);
|
||||
|
||||
// Get string length
|
||||
// Get string length. Returns 0 if str is null.
|
||||
static int32 Length(const char* str);
|
||||
|
||||
// Copy string
|
||||
|
||||
@@ -35,7 +35,7 @@ void RunUWP()
|
||||
|
||||
DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
|
||||
{
|
||||
return (DialogResult)CUWPPlatform->ShowMessageDialog(parent ? parent->GetImpl() : nullptr, text.GetText(), caption.GetText(), (UWPPlatformImpl::MessageBoxButtons)buttons, (UWPPlatformImpl::MessageBoxIcon)icon);
|
||||
return (DialogResult)CUWPPlatform->ShowMessageDialog(parent ? parent->GetImpl() : nullptr, String(text).GetText(), String(caption).GetText(), (UWPPlatformImpl::MessageBoxButtons)buttons, (UWPPlatformImpl::MessageBoxIcon)icon);
|
||||
}
|
||||
|
||||
bool UWPPlatform::Init()
|
||||
|
||||
@@ -23,9 +23,12 @@ void WindowsClipboard::Clear()
|
||||
|
||||
void WindowsClipboard::SetText(const StringView& text)
|
||||
{
|
||||
const int32 size = (text.Length() + 1) * sizeof(Char);
|
||||
const HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, size);
|
||||
Platform::MemoryCopy(GlobalLock(hMem), text.GetText(), size);
|
||||
const int32 sizeWithoutNull = text.Length() * sizeof(Char);
|
||||
const HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, sizeWithoutNull + sizeof(Char));
|
||||
|
||||
Char* pMem = static_cast<Char*>(GlobalLock(hMem));
|
||||
Platform::MemoryCopy(pMem, text.GetNonTerminatedText(), sizeWithoutNull);
|
||||
Platform::MemorySet(pMem + text.Length(), sizeof(Char), 0);
|
||||
GlobalUnlock(hMem);
|
||||
|
||||
OpenClipboard(nullptr);
|
||||
|
||||
@@ -435,7 +435,7 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri
|
||||
}
|
||||
|
||||
// Show dialog
|
||||
int result = MessageBoxW(parent ? static_cast<HWND>(parent->GetNativePtr()) : nullptr, text.GetText(), caption.GetText(), flags);
|
||||
int result = MessageBoxW(parent ? static_cast<HWND>(parent->GetNativePtr()) : nullptr, String(text).GetText(), String(caption).GetText(), flags);
|
||||
|
||||
// Translate result to dialog result
|
||||
DialogResult dialogResult;
|
||||
@@ -948,10 +948,12 @@ int32 WindowsPlatform::StartProcess(const StringView& filename, const StringView
|
||||
LOG(Info, "Working directory: {0}", workingDir);
|
||||
}
|
||||
|
||||
String filenameString(filename);
|
||||
|
||||
SHELLEXECUTEINFOW shExecInfo = { 0 };
|
||||
shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
|
||||
shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
shExecInfo.lpFile = filename.GetText();
|
||||
shExecInfo.lpFile = filenameString.GetText();
|
||||
shExecInfo.lpParameters = args.HasChars() ? args.Get() : nullptr;
|
||||
shExecInfo.lpDirectory = workingDir.HasChars() ? workingDir.Get() : nullptr;
|
||||
shExecInfo.nShow = hiddenWindow ? SW_HIDE : SW_SHOW;
|
||||
@@ -1109,7 +1111,7 @@ int32 WindowsPlatform::RunProcess(const StringView& cmdLine, const StringView& w
|
||||
|
||||
// Create the process
|
||||
PROCESS_INFORMATION procInfo;
|
||||
if (!CreateProcessW(nullptr, const_cast<LPWSTR>(cmdLine.GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, workingDir.HasChars() ? workingDir.Get() : nullptr, &startupInfoEx.StartupInfo, &procInfo))
|
||||
if (!CreateProcessW(nullptr, const_cast<LPWSTR>(String(cmdLine).GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, workingDir.HasChars() ? workingDir.Get() : nullptr, &startupInfoEx.StartupInfo, &procInfo))
|
||||
{
|
||||
LOG(Warning, "Cannot start process '{0}'. Error code: 0x{1:x}", cmdLine, static_cast<int64>(GetLastError()));
|
||||
goto ERROR_EXIT;
|
||||
|
||||
@@ -386,33 +386,31 @@ struct TIsPODType<ProfilerCPU::Event>
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
#include "ProfilerSrcLoc.h"
|
||||
|
||||
// Shortcut macros for profiling a single code block execution on CPU
|
||||
// Use ZoneTransient for Tracy for code that can be hot-reloaded (eg. in Editor) or if name can be a variable
|
||||
#define PROFILE_CPU_USE_TRANSIENT_DATA 0
|
||||
|
||||
#define PROFILE_CPU_NAMED(name) ZoneTransientN(___tracy_scoped_zone, name, true); ScopeProfileBlockCPU ProfileBlockCPU(name)
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
#if USE_EDITOR
|
||||
#define PROFILE_CPU() ZoneTransient(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(TEXT(__FUNCTION__))
|
||||
#else
|
||||
#define PROFILE_CPU() ZoneNamed(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(TEXT(__FUNCTION__))
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#if USE_EDITOR
|
||||
#if PROFILE_CPU_USE_TRANSIENT_DATA
|
||||
#define PROFILE_CPU() ZoneTransient(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(__FUNCTION__)
|
||||
#define PROFILE_CPU_NAMED(name) ZoneTransientN(___tracy_scoped_zone, name, true); ScopeProfileBlockCPU ProfileBlockCPU(name)
|
||||
#else
|
||||
#define PROFILE_CPU() ZoneNamed(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(__FUNCTION__)
|
||||
#define PROFILE_CPU_NAMED(name) ZoneNamedN(___tracy_scoped_zone, name, true); ScopeProfileBlockCPU ProfileBlockCPU(name)
|
||||
#endif
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
#define PROFILE_CPU_SRC_LOC(srcLoc) tracy::ScopedZone ___tracy_scoped_zone( (tracy::SourceLocationData*)&(srcLoc) ); ScopeProfileBlockCPU ProfileBlockCPU((srcLoc).name)
|
||||
#else
|
||||
#define PROFILE_CPU_SRC_LOC(srcLoc) ScopeProfileBlockCPU ProfileBlockCPU((srcLoc).name)
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
// Empty macros for disabled profiler
|
||||
#define PROFILE_CPU_NAMED(name)
|
||||
#define PROFILE_CPU()
|
||||
#define PROFILE_CPU_NAMED(name)
|
||||
#define PROFILE_CPU_SRC_LOC(srcLoc)
|
||||
|
||||
#endif
|
||||
|
||||
18
Source/Engine/Profiler/ProfilerSrcLoc.h
Normal file
18
Source/Engine/Profiler/ProfilerSrcLoc.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if COMPILE_WITH_PROFILER
|
||||
|
||||
#include "Engine/Core/Types/BaseTypes.h"
|
||||
|
||||
struct FLAXENGINE_API SourceLocationData
|
||||
{
|
||||
const char* name;
|
||||
const char* function;
|
||||
const char* file;
|
||||
uint32 line;
|
||||
uint32 color;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -60,18 +60,23 @@ MMethod::MMethod(MonoMethod* monoMethod, const char* name, MClass* parentClass)
|
||||
ProfilerName.Get()[className.Length()] = ':';
|
||||
ProfilerName.Get()[className.Length() + 1] = ':';
|
||||
Platform::MemoryCopy(ProfilerName.Get() + className.Length() + 2, _name.Get(), _name.Length());
|
||||
ProfilerData.name = ProfilerName.Get();
|
||||
ProfilerData.function = _name.Get();
|
||||
ProfilerData.file = nullptr;
|
||||
ProfilerData.line = 0;
|
||||
ProfilerData.color = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
MonoObject* MMethod::Invoke(void* instance, void** params, MonoObject** exception) const
|
||||
{
|
||||
PROFILE_CPU_NAMED(*ProfilerName);
|
||||
PROFILE_CPU_SRC_LOC(ProfilerData);
|
||||
return mono_runtime_invoke(_monoMethod, instance, params, exception);
|
||||
}
|
||||
|
||||
MonoObject* MMethod::InvokeVirtual(MonoObject* instance, void** params, MonoObject** exception) const
|
||||
{
|
||||
PROFILE_CPU_NAMED(*ProfilerName);
|
||||
PROFILE_CPU_SRC_LOC(ProfilerData);
|
||||
MonoMethod* virtualMethod = mono_object_get_virtual_method(instance, _monoMethod);
|
||||
return mono_runtime_invoke(virtualMethod, instance, params, exception);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#if COMPILE_WITH_PROFILER
|
||||
#include "Engine/Profiler/ProfilerSrcLoc.h"
|
||||
#endif
|
||||
#include "MTypes.h"
|
||||
|
||||
/// <summary>
|
||||
@@ -42,6 +45,7 @@ public:
|
||||
|
||||
#if COMPILE_WITH_PROFILER
|
||||
MString ProfilerName;
|
||||
SourceLocationData ProfilerData;
|
||||
#endif
|
||||
|
||||
#if USE_MONO
|
||||
|
||||
@@ -9,13 +9,29 @@
|
||||
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
||||
#include "Engine/Core/ObjectsRemovalService.h"
|
||||
#include "Engine/Profiler/Profiler.h"
|
||||
#if TRACY_ENABLE && !PROFILE_CPU_USE_TRANSIENT_DATA
|
||||
#include "Engine/Core/Collections/ChunkedArray.h"
|
||||
#endif
|
||||
#include "Engine/Core/Types/Pair.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/mono-gc.h>
|
||||
|
||||
namespace ProfilerInternal
|
||||
{
|
||||
#if COMPILE_WITH_PROFILER
|
||||
Array<int32> ManagedEventsGPU;
|
||||
Array<int32, InlinedAllocation<32>> ManagedEventsGPU;
|
||||
#if TRACY_ENABLE && !PROFILE_CPU_USE_TRANSIENT_DATA
|
||||
CriticalSection ManagedSourceLocationsLocker;
|
||||
|
||||
struct Location
|
||||
{
|
||||
String Name;
|
||||
StringAnsi NameAnsi;
|
||||
tracy::SourceLocationData SrcLocation;
|
||||
};
|
||||
|
||||
ChunkedArray<Location, 256> ManagedSourceLocations;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void BeginEvent(MonoString* nameObj)
|
||||
@@ -24,7 +40,34 @@ namespace ProfilerInternal
|
||||
const StringView name((const Char*)mono_string_chars(nameObj), mono_string_length(nameObj));
|
||||
ProfilerCPU::BeginEvent(*name);
|
||||
#if TRACY_ENABLE
|
||||
#if PROFILE_CPU_USE_TRANSIENT_DATA
|
||||
tracy::ScopedZone::Begin(__LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), name.Get(), name.Length() );
|
||||
#else
|
||||
ScopeLock lock(ManagedSourceLocationsLocker);
|
||||
tracy::SourceLocationData* srcLoc = nullptr;
|
||||
for (auto e = ManagedSourceLocations.Begin(); e.IsNotEnd(); ++e)
|
||||
{
|
||||
if (name == e->Name)
|
||||
{
|
||||
srcLoc = &e->SrcLocation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!srcLoc)
|
||||
{
|
||||
auto& e = ManagedSourceLocations.AddOne();
|
||||
e.Name = name;
|
||||
e.NameAnsi = name.Get();
|
||||
srcLoc = &e.SrcLocation;
|
||||
srcLoc->name = e.NameAnsi.Get();
|
||||
srcLoc->function = nullptr;
|
||||
srcLoc->file = nullptr;
|
||||
srcLoc->line = 0;
|
||||
srcLoc->color = 0;
|
||||
}
|
||||
//static constexpr tracy::SourceLocationData tracySrcLoc{ nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 };
|
||||
tracy::ScopedZone::Begin(srcLoc);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
14
Source/Engine/Tests/TestMain.cpp
Normal file
14
Source/Engine/Tests/TestMain.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if PLATFORM_WINDOWS || PLATFORM_LINUX
|
||||
|
||||
#define CATCH_CONFIG_RUNNER
|
||||
#include <ThirdParty/catch2/catch.hpp>
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int result = Catch::Session().run(argc, argv);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
331
Source/Engine/Tests/TestString.cpp
Normal file
331
Source/Engine/Tests/TestString.cpp
Normal file
@@ -0,0 +1,331 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include <ThirdParty/catch2/catch.hpp>
|
||||
|
||||
TEST_CASE("String Replace works") {
|
||||
SECTION("Char, case sensitive") {
|
||||
String str("hello HELLO");
|
||||
CHECK(str.Replace('l', 'x', StringSearchCase::CaseSensitive) == 2);
|
||||
CHECK(str == String("hexxo HELLO"));
|
||||
}
|
||||
|
||||
SECTION("Char, ignore case") {
|
||||
String str("hello HELLO");
|
||||
CHECK(str.Replace('l', 'x', StringSearchCase::IgnoreCase) == 4);
|
||||
CHECK(str == String("hexxo HExxO"));
|
||||
}
|
||||
|
||||
SECTION("case sensitive") {
|
||||
String str("hello HELLO this is me saying hello");
|
||||
CHECK(str.Replace(TEXT("hello"), TEXT("hi"), StringSearchCase::CaseSensitive) == 2);
|
||||
CHECK(str == String("hi HELLO this is me saying hi"));
|
||||
}
|
||||
|
||||
SECTION("ignore case") {
|
||||
String str("hello HELLO this is me saying hello");
|
||||
CHECK(str.Replace(TEXT("hello"), TEXT("hi"), StringSearchCase::IgnoreCase) == 3);
|
||||
CHECK(str == String("hi hi this is me saying hi"));
|
||||
}
|
||||
|
||||
SECTION("case sensitive, search and replace texts identical") {
|
||||
String str("hello HELLO this is me saying hello");
|
||||
CHECK(str.Replace(TEXT("hello"), TEXT("hello"), StringSearchCase::CaseSensitive) == 2);
|
||||
CHECK(str == String("hello HELLO this is me saying hello"));
|
||||
}
|
||||
|
||||
SECTION("ignore case, search and replace texts identical") {
|
||||
String str("hello HELLO this is me saying hello");
|
||||
CHECK(str.Replace(TEXT("hello"), TEXT("hello"), StringSearchCase::IgnoreCase) == 3);
|
||||
CHECK(str == String("hello hello this is me saying hello"));
|
||||
}
|
||||
|
||||
SECTION("case sensitive, replace text empty") {
|
||||
String str("hello HELLO this is me saying hello");
|
||||
CHECK(str.Replace(TEXT("hello"), TEXT(""), StringSearchCase::CaseSensitive) == 2);
|
||||
CHECK(str == String(" HELLO this is me saying "));
|
||||
}
|
||||
|
||||
SECTION("ignore case, replace text empty") {
|
||||
String str("hello HELLO this is me saying hello");
|
||||
CHECK(str.Replace(TEXT("hello"), TEXT(""), StringSearchCase::IgnoreCase) == 3);
|
||||
CHECK(str == String(" this is me saying "));
|
||||
}
|
||||
|
||||
SECTION("no finds") {
|
||||
String str("hello HELLO this is me saying hello");
|
||||
CHECK(str.Replace(TEXT("bye"), TEXT("hi"), StringSearchCase::CaseSensitive) == 0);
|
||||
CHECK(str.Replace(TEXT("bye"), TEXT("hi"), StringSearchCase::IgnoreCase) == 0);
|
||||
CHECK(str == String("hello HELLO this is me saying hello"));
|
||||
}
|
||||
|
||||
SECTION("empty input") {
|
||||
String str("");
|
||||
CHECK(str.Replace(TEXT("bye"), TEXT("hi"), StringSearchCase::CaseSensitive) == 0);
|
||||
CHECK(str.Replace(TEXT("bye"), TEXT("hi"), StringSearchCase::IgnoreCase) == 0);
|
||||
CHECK(str == String(""));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("String Starts/EndsWith works") {
|
||||
SECTION("StartsWith, case sensitive") {
|
||||
SECTION("Char") {
|
||||
CHECK(String("").StartsWith('h', StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").StartsWith('h', StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").StartsWith('H', StringSearchCase::CaseSensitive) == false);
|
||||
}
|
||||
|
||||
SECTION("String") {
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("hello")), StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("HELLO")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("")), StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("xxx")), StringSearchCase::CaseSensitive) == false);
|
||||
|
||||
CHECK(String("").StartsWith(String(TEXT("x")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("hello HELLOx")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("xhello HELLO")), StringSearchCase::CaseSensitive) == false);
|
||||
}
|
||||
|
||||
SECTION("StringView") {
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("hello")), StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("HELLO")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(), StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("")), StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("xxx")), StringSearchCase::CaseSensitive) == false);
|
||||
|
||||
CHECK(String("").StartsWith(StringView(TEXT("x")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("hello HELLOx")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("xhello HELLO")), StringSearchCase::CaseSensitive) == false);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("StartsWith, ignore case") {
|
||||
SECTION("Char") {
|
||||
CHECK(String("").StartsWith('h', StringSearchCase::IgnoreCase) == false);
|
||||
CHECK(String("hello HELLO").StartsWith('h', StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").StartsWith('H', StringSearchCase::IgnoreCase) == true);
|
||||
}
|
||||
|
||||
SECTION("String") {
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("hello")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("HELLO")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("xxx")), StringSearchCase::IgnoreCase) == false);
|
||||
|
||||
CHECK(String("").StartsWith(String(TEXT("x")), StringSearchCase::IgnoreCase) == false);
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("hello HELLOx")), StringSearchCase::IgnoreCase) == false);
|
||||
CHECK(String("hello HELLO").StartsWith(String(TEXT("xhello HELLO")), StringSearchCase::IgnoreCase) == false);
|
||||
}
|
||||
|
||||
SECTION("StringView") {
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("hello")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("HELLO")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("xxx")), StringSearchCase::IgnoreCase) == false);
|
||||
|
||||
CHECK(String("").StartsWith(StringView(TEXT("x")), StringSearchCase::IgnoreCase) == false);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("hello HELLOx")), StringSearchCase::IgnoreCase) == false);
|
||||
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("xhello HELLO")), StringSearchCase::IgnoreCase) == false);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("EndsWith, case sensitive") {
|
||||
SECTION("Char") {
|
||||
CHECK(String("").EndsWith('h', StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").EndsWith('O', StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").EndsWith('o', StringSearchCase::CaseSensitive) == false);
|
||||
}
|
||||
|
||||
SECTION("String") {
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("HELLO")), StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("hello")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("")), StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("xxx")), StringSearchCase::CaseSensitive) == false);
|
||||
|
||||
CHECK(String("").EndsWith(String(TEXT("x")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("hello HELLOx")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("xhello HELLO")), StringSearchCase::CaseSensitive) == false);
|
||||
}
|
||||
|
||||
SECTION("StringView") {
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("HELLO")), StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("hello")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(), StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("")), StringSearchCase::CaseSensitive) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("xxx")), StringSearchCase::CaseSensitive) == false);
|
||||
|
||||
CHECK(String("").EndsWith(StringView(TEXT("x")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("hello HELLOx")), StringSearchCase::CaseSensitive) == false);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("xhello HELLO")), StringSearchCase::CaseSensitive) == false);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("EndsWith, ignore case") {
|
||||
SECTION("Char") {
|
||||
CHECK(String("").EndsWith('h', StringSearchCase::IgnoreCase) == false);
|
||||
CHECK(String("hello HELLO").EndsWith('O', StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").EndsWith('o', StringSearchCase::IgnoreCase) == true);
|
||||
}
|
||||
|
||||
SECTION("String") {
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("HELLO")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("hello")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("xxx")), StringSearchCase::IgnoreCase) == false);
|
||||
|
||||
CHECK(String("").EndsWith(String(TEXT("x")), StringSearchCase::IgnoreCase) == false);
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("hello HELLOx")), StringSearchCase::IgnoreCase) == false);
|
||||
CHECK(String("hello HELLO").EndsWith(String(TEXT("xhello HELLO")), StringSearchCase::IgnoreCase) == false);
|
||||
}
|
||||
|
||||
SECTION("StringView") {
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("HELLO")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("hello")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("")), StringSearchCase::IgnoreCase) == true);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("xxx")), StringSearchCase::IgnoreCase) == false);
|
||||
|
||||
CHECK(String("").EndsWith(StringView(TEXT("x")), StringSearchCase::IgnoreCase) == false);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("hello HELLOx")), StringSearchCase::IgnoreCase) == false);
|
||||
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("xhello HELLO")), StringSearchCase::IgnoreCase) == false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("String Compare works") {
|
||||
SECTION("String") {
|
||||
SECTION("case sensitive") {
|
||||
// Empty strings
|
||||
CHECK(String("").Compare(String(TEXT("")), StringSearchCase::CaseSensitive) == 0);
|
||||
CHECK(String("").Compare(String(TEXT("xxx")), StringSearchCase::CaseSensitive) < 0);
|
||||
CHECK(String("xxx").Compare(String(TEXT("")), StringSearchCase::CaseSensitive) > 0);
|
||||
|
||||
// Equal lengths, difference at end
|
||||
CHECK(String("xxx").Compare(String(TEXT("xxx")), StringSearchCase::CaseSensitive) == 0);
|
||||
CHECK(String("abc").Compare(String(TEXT("abd")), StringSearchCase::CaseSensitive) < 0);
|
||||
CHECK(String("abd").Compare(String(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
|
||||
|
||||
// Equal lengths, difference in the middle
|
||||
CHECK(String("abcx").Compare(String(TEXT("abdx")), StringSearchCase::CaseSensitive) < 0);
|
||||
CHECK(String("abdx").Compare(String(TEXT("abcx")), StringSearchCase::CaseSensitive) > 0);
|
||||
|
||||
// Different lengths, same prefix
|
||||
CHECK(String("abcxx").Compare(String(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
|
||||
CHECK(String("abc").Compare(String(TEXT("abcxx")), StringSearchCase::CaseSensitive) < 0);
|
||||
|
||||
// Different lengths, different prefix
|
||||
CHECK(String("abcx").Compare(String(TEXT("abd")), StringSearchCase::CaseSensitive) < 0);
|
||||
CHECK(String("abd").Compare(String(TEXT("abcx")), StringSearchCase::CaseSensitive) > 0);
|
||||
CHECK(String("abc").Compare(String(TEXT("abdx")), StringSearchCase::CaseSensitive) < 0);
|
||||
CHECK(String("abdx").Compare(String(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
|
||||
|
||||
// Case differences
|
||||
CHECK(String("a").Compare(String(TEXT("A")), StringSearchCase::CaseSensitive) > 0);
|
||||
CHECK(String("A").Compare(String(TEXT("a")), StringSearchCase::CaseSensitive) < 0);
|
||||
}
|
||||
|
||||
SECTION("ignore case") {
|
||||
// Empty strings
|
||||
CHECK(String("").Compare(String(TEXT("")), StringSearchCase::IgnoreCase) == 0);
|
||||
CHECK(String("").Compare(String(TEXT("xxx")), StringSearchCase::IgnoreCase) < 0);
|
||||
CHECK(String("xxx").Compare(String(TEXT("")), StringSearchCase::IgnoreCase) > 0);
|
||||
|
||||
// Equal lengths, difference at end
|
||||
CHECK(String("xxx").Compare(String(TEXT("xxx")), StringSearchCase::IgnoreCase) == 0);
|
||||
CHECK(String("abc").Compare(String(TEXT("abd")), StringSearchCase::IgnoreCase) < 0);
|
||||
CHECK(String("abd").Compare(String(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
|
||||
|
||||
// Equal lengths, difference in the middle
|
||||
CHECK(String("abcx").Compare(String(TEXT("abdx")), StringSearchCase::IgnoreCase) < 0);
|
||||
CHECK(String("abdx").Compare(String(TEXT("abcx")), StringSearchCase::IgnoreCase) > 0);
|
||||
|
||||
// Different lengths, same prefix
|
||||
CHECK(String("abcxx").Compare(String(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
|
||||
CHECK(String("abc").Compare(String(TEXT("abcxx")), StringSearchCase::IgnoreCase) < 0);
|
||||
|
||||
// Different lengths, different prefix
|
||||
CHECK(String("abcx").Compare(String(TEXT("abd")), StringSearchCase::IgnoreCase) < 0);
|
||||
CHECK(String("abd").Compare(String(TEXT("abcx")), StringSearchCase::IgnoreCase) > 0);
|
||||
CHECK(String("abc").Compare(String(TEXT("abdx")), StringSearchCase::IgnoreCase) < 0);
|
||||
CHECK(String("abdx").Compare(String(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
|
||||
|
||||
// Case differences
|
||||
CHECK(String("a").Compare(String(TEXT("A")), StringSearchCase::IgnoreCase) == 0);
|
||||
CHECK(String("A").Compare(String(TEXT("a")), StringSearchCase::IgnoreCase) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("StringView") {
|
||||
SECTION("case sensitive") {
|
||||
// Null string views
|
||||
CHECK(StringView().Compare(StringView(), StringSearchCase::CaseSensitive) == 0);
|
||||
CHECK(StringView().Compare(StringView(TEXT("xxx")), StringSearchCase::CaseSensitive) < 0);
|
||||
CHECK(StringView(TEXT("xxx")).Compare(StringView(), StringSearchCase::CaseSensitive) > 0);
|
||||
|
||||
// Empty strings
|
||||
CHECK(StringView(TEXT("")).Compare(StringView(TEXT("")), StringSearchCase::CaseSensitive) == 0);
|
||||
CHECK(StringView(TEXT("")).Compare(StringView(TEXT("xxx")), StringSearchCase::CaseSensitive) < 0);
|
||||
CHECK(StringView(TEXT("xxx")).Compare(StringView(TEXT("")), StringSearchCase::CaseSensitive) > 0);
|
||||
|
||||
// Equal lengths, difference at end
|
||||
CHECK(StringView(TEXT("xxx")).Compare(StringView(TEXT("xxx")), StringSearchCase::CaseSensitive) == 0);
|
||||
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abd")), StringSearchCase::CaseSensitive) < 0);
|
||||
CHECK(StringView(TEXT("abd")).Compare(StringView(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
|
||||
|
||||
// Equal lengths, difference in the middle
|
||||
CHECK(StringView(TEXT("abcx")).Compare(StringView(TEXT("abdx")), StringSearchCase::CaseSensitive) < 0);
|
||||
CHECK(StringView(TEXT("abdx")).Compare(StringView(TEXT("abcx")), StringSearchCase::CaseSensitive) > 0);
|
||||
|
||||
// Different lengths, same prefix
|
||||
CHECK(StringView(TEXT("abcxx")).Compare(StringView(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
|
||||
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abcxx")), StringSearchCase::CaseSensitive) < 0);
|
||||
|
||||
// Different lengths, different prefix
|
||||
CHECK(StringView(TEXT("abcx")).Compare(StringView(TEXT("abd")), StringSearchCase::CaseSensitive) < 0);
|
||||
CHECK(StringView(TEXT("abd")).Compare(StringView(TEXT("abcx")), StringSearchCase::CaseSensitive) > 0);
|
||||
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abdx")), StringSearchCase::CaseSensitive) < 0);
|
||||
CHECK(StringView(TEXT("abdx")).Compare(StringView(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
|
||||
|
||||
// Case differences
|
||||
CHECK(StringView(TEXT("a")).Compare(StringView(TEXT("A")), StringSearchCase::CaseSensitive) > 0);
|
||||
CHECK(StringView(TEXT("A")).Compare(StringView(TEXT("a")), StringSearchCase::CaseSensitive) < 0);
|
||||
}
|
||||
|
||||
SECTION("ignore case") {
|
||||
//Null string views
|
||||
CHECK(StringView().Compare(StringView(), StringSearchCase::IgnoreCase) == 0);
|
||||
CHECK(StringView().Compare(StringView(TEXT("xxx")), StringSearchCase::IgnoreCase) < 0);
|
||||
CHECK(StringView(TEXT("xxx")).Compare(StringView(), StringSearchCase::IgnoreCase) > 0);
|
||||
|
||||
// Empty strings
|
||||
CHECK(StringView(TEXT("")).Compare(StringView(TEXT("")), StringSearchCase::IgnoreCase) == 0);
|
||||
CHECK(StringView(TEXT("")).Compare(StringView(TEXT("xxx")), StringSearchCase::IgnoreCase) < 0);
|
||||
CHECK(StringView(TEXT("xxx")).Compare(StringView(TEXT("")), StringSearchCase::IgnoreCase) > 0);
|
||||
|
||||
// Equal lengths, difference at end
|
||||
CHECK(StringView(TEXT("xxx")).Compare(StringView(TEXT("xxx")), StringSearchCase::IgnoreCase) == 0);
|
||||
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abd")), StringSearchCase::IgnoreCase) < 0);
|
||||
CHECK(StringView(TEXT("abd")).Compare(StringView(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
|
||||
|
||||
// Equal lengths, difference in the middle
|
||||
CHECK(StringView(TEXT("abcx")).Compare(StringView(TEXT("abdx")), StringSearchCase::IgnoreCase) < 0);
|
||||
CHECK(StringView(TEXT("abdx")).Compare(StringView(TEXT("abcx")), StringSearchCase::IgnoreCase) > 0);
|
||||
|
||||
// Different lengths, same prefix
|
||||
CHECK(StringView(TEXT("abcxx")).Compare(StringView(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
|
||||
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abcxx")), StringSearchCase::IgnoreCase) < 0);
|
||||
|
||||
// Different lengths, different prefix
|
||||
CHECK(StringView(TEXT("abcx")).Compare(StringView(TEXT("abd")), StringSearchCase::IgnoreCase) < 0);
|
||||
CHECK(StringView(TEXT("abd")).Compare(StringView(TEXT("abcx")), StringSearchCase::IgnoreCase) > 0);
|
||||
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abdx")), StringSearchCase::IgnoreCase) < 0);
|
||||
CHECK(StringView(TEXT("abdx")).Compare(StringView(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
|
||||
|
||||
// Case differences
|
||||
CHECK(StringView(TEXT("a")).Compare(StringView(TEXT("A")), StringSearchCase::IgnoreCase) == 0);
|
||||
CHECK(StringView(TEXT("A")).Compare(StringView(TEXT("a")), StringSearchCase::IgnoreCase) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Source/Engine/Tests/Tests.Build.cs
Normal file
21
Source/Engine/Tests/Tests.Build.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Flax.Build;
|
||||
|
||||
/// <summary>
|
||||
/// Engine tests module.
|
||||
/// </summary>
|
||||
public class Tests : EngineModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Tests()
|
||||
{
|
||||
Deploy = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void GetFilesToDeploy(List<string> files)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -134,9 +134,13 @@ void JobSystemService::Dispose()
|
||||
|
||||
for (int32 i = 0; i < ThreadsCount; i++)
|
||||
{
|
||||
if (Threads[i] && Threads[i]->IsRunning())
|
||||
Threads[i]->Kill(true);
|
||||
Threads[i] = nullptr;
|
||||
if (Threads[i])
|
||||
{
|
||||
if (Threads[i]->IsRunning())
|
||||
Threads[i]->Kill(true);
|
||||
Delete(Threads[i]);
|
||||
Threads[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace FlaxEngine.Utilities
|
||||
/// <summary>
|
||||
/// The index buffer.
|
||||
/// </summary>
|
||||
public int[] IndexBuffer;
|
||||
public uint[] IndexBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// The vertex buffer.
|
||||
|
||||
65
Source/FlaxTests.Build.cs
Normal file
65
Source/FlaxTests.Build.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Flax.Build;
|
||||
using Flax.Build.NativeCpp;
|
||||
|
||||
/// <summary>
|
||||
/// Target that builds standalone, native tests.
|
||||
/// </summary>
|
||||
public class FlaxTestsTarget : EngineTarget
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
// Initialize
|
||||
OutputName = "FlaxTests";
|
||||
ConfigurationName = "Tests";
|
||||
IsPreBuilt = false;
|
||||
UseSymbolsExports = false;
|
||||
Platforms = new[]
|
||||
{
|
||||
TargetPlatform.Windows,
|
||||
TargetPlatform.Linux,
|
||||
};
|
||||
Architectures = new[]
|
||||
{
|
||||
TargetArchitecture.x64,
|
||||
};
|
||||
Configurations = new[]
|
||||
{
|
||||
TargetConfiguration.Development,
|
||||
};
|
||||
|
||||
Modules.Remove("Main");
|
||||
Modules.Add("Tests");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetupTargetEnvironment(BuildOptions options)
|
||||
{
|
||||
base.SetupTargetEnvironment(options);
|
||||
|
||||
options.LinkEnv.LinkAsConsoleProgram = true;
|
||||
|
||||
// Setup output folder for Test binaries
|
||||
var platformName = options.Platform.Target.ToString();
|
||||
var architectureName = options.Architecture.ToString();
|
||||
var configurationName = options.Configuration.ToString();
|
||||
options.OutputFolder = Path.Combine(options.WorkingDirectory, "Binaries", "Tests", platformName, architectureName, configurationName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Target SelectReferencedTarget(ProjectInfo project, Target[] projectTargets)
|
||||
{
|
||||
var testTargetName = "FlaxNativeTests"; // Should this be added to .flaxproj, similarly as "GameTarget" and "EditorTarget"?
|
||||
var result = projectTargets.FirstOrDefault(x => x.Name == testTargetName);
|
||||
if (result == null)
|
||||
throw new Exception(string.Format("Invalid or missing test target {0} specified in project {1} (referenced by project {2}).", testTargetName, project.Name, Project.Name));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
23
Source/ThirdParty/catch2/LICENSE.txt
vendored
Normal file
23
Source/ThirdParty/catch2/LICENSE.txt
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
17959
Source/ThirdParty/catch2/catch.hpp
vendored
Normal file
17959
Source/ThirdParty/catch2/catch.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
20
Source/ThirdParty/catch2/catch2.Build.cs
vendored
Normal file
20
Source/ThirdParty/catch2/catch2.Build.cs
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Flax.Build;
|
||||
|
||||
/// <summary>
|
||||
/// https://github.com/catchorg/Catch2
|
||||
/// </summary>
|
||||
public class catch2 : HeaderOnlyModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
LicenseType = LicenseTypes.BoostSoftwareLicense;
|
||||
LicenseFilePath = "LICENSE.txt";
|
||||
}
|
||||
}
|
||||
17
Source/ThirdParty/tracy/client/TracyScoped.hpp
vendored
17
Source/ThirdParty/tracy/client/TracyScoped.hpp
vendored
@@ -12,8 +12,22 @@
|
||||
|
||||
namespace tracy
|
||||
{
|
||||
void ScopedZone::Begin(const SourceLocationData* srcloc)
|
||||
{
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
if (!GetProfiler().IsConnected()) return;
|
||||
#endif
|
||||
TracyLfqPrepare( QueueType::ZoneBegin );
|
||||
MemWrite( &item->zoneBegin.time, Profiler::GetTime() );
|
||||
MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc );
|
||||
TracyLfqCommit;
|
||||
}
|
||||
|
||||
void ScopedZone::Begin(uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const Char* name, size_t nameSz)
|
||||
{
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
if (!GetProfiler().IsConnected()) return;
|
||||
#endif
|
||||
TracyLfqPrepare( QueueType::ZoneBeginAllocSrcLoc );
|
||||
const auto srcloc = Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz );
|
||||
MemWrite( &item->zoneBegin.time, Profiler::GetTime() );
|
||||
@@ -23,6 +37,9 @@ void ScopedZone::Begin(uint32_t line, const char* source, size_t sourceSz, const
|
||||
|
||||
void ScopedZone::End()
|
||||
{
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
if (!GetProfiler().IsConnected()) return;
|
||||
#endif
|
||||
TracyLfqPrepare( QueueType::ZoneEnd );
|
||||
MemWrite( &item->zoneEnd.time, Profiler::GetTime() );
|
||||
TracyLfqCommit;
|
||||
|
||||
@@ -46,6 +46,7 @@ struct TRACY_API SourceLocationData
|
||||
class TRACY_API ScopedZone
|
||||
{
|
||||
public:
|
||||
static void Begin( const SourceLocationData* srcloc );
|
||||
static void Begin( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const Char* name, size_t nameSz );
|
||||
static void End();
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ namespace Flax.Build.Bindings
|
||||
if (typeInfo.Type == "String")
|
||||
return $"(StringView){value}";
|
||||
if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char")
|
||||
return $"((StringView){value}).GetText()";
|
||||
return $"((StringView){value}).GetNonTerminatedText()"; // (StringView)Variant, if not empty, is guaranteed to point to a null-terminated buffer.
|
||||
if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference")
|
||||
return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})";
|
||||
if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference")
|
||||
@@ -1028,7 +1028,7 @@ namespace Flax.Build.Bindings
|
||||
contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Script.ScriptVTable;");
|
||||
contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableIndex}]);");
|
||||
contents.AppendLine($" auto method = scriptVTable[{scriptVTableIndex}];");
|
||||
contents.AppendLine(" PROFILE_CPU_NAMED(*method->ProfilerName);");
|
||||
contents.AppendLine($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::{functionInfo.Name}\");");
|
||||
contents.AppendLine(" MonoObject* exception = nullptr;");
|
||||
|
||||
contents.AppendLine(" auto prevWrapperCallInstance = WrapperCallInstance;");
|
||||
@@ -1290,7 +1290,7 @@ namespace Flax.Build.Bindings
|
||||
contents.Append(" if (!mmethod)").AppendLine();
|
||||
contents.AppendFormat(" mmethod = {1}::TypeInitializer.GetType().ManagedClass->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine();
|
||||
contents.Append(" CHECK(mmethod);").AppendLine();
|
||||
contents.Append(" PROFILE_CPU_NAMED(*mmethod->ProfilerName);").AppendLine();
|
||||
contents.Append($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::On{eventInfo.Name}\");").AppendLine();
|
||||
contents.Append(" MonoObject* exception = nullptr;").AppendLine();
|
||||
if (paramsCount == 0)
|
||||
contents.AppendLine(" void** params = nullptr;");
|
||||
|
||||
@@ -42,6 +42,11 @@ namespace Flax.Build
|
||||
/// </summary>
|
||||
public bool BuildCSharp = true;
|
||||
|
||||
/// <summary>
|
||||
/// True if module can be deployed.
|
||||
/// </summary>
|
||||
public bool Deploy = true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Module"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -76,6 +76,11 @@ namespace Flax.Build.NativeCpp
|
||||
/// </summary>
|
||||
public bool GenerateWindowsMetadata = false;
|
||||
|
||||
/// <summary>
|
||||
/// Use CONSOLE subsystem on Windows instead of the WINDOWS one.
|
||||
/// </summary>
|
||||
public bool LinkAsConsoleProgram = false;
|
||||
|
||||
/// <summary>
|
||||
/// Enables documentation generation.
|
||||
/// </summary>
|
||||
@@ -114,6 +119,7 @@ namespace Flax.Build.NativeCpp
|
||||
LinkTimeCodeGeneration = LinkTimeCodeGeneration,
|
||||
UseIncrementalLinking = UseIncrementalLinking,
|
||||
GenerateWindowsMetadata = GenerateWindowsMetadata,
|
||||
LinkAsConsoleProgram = LinkAsConsoleProgram,
|
||||
GenerateDocumentation = GenerateDocumentation
|
||||
};
|
||||
foreach (var e in InputFiles)
|
||||
|
||||
@@ -86,6 +86,8 @@ namespace Flax.Deploy
|
||||
var files = new List<string>();
|
||||
foreach (var module in rules.Modules)
|
||||
{
|
||||
if (!module.Deploy)
|
||||
continue;
|
||||
module.GetFilesToDeploy(files);
|
||||
files.Add(module.FilePath);
|
||||
foreach (var file in files)
|
||||
|
||||
@@ -675,7 +675,14 @@ namespace Flax.Build.Platforms
|
||||
}
|
||||
|
||||
// Specify subsystem
|
||||
args.Add("/SUBSYSTEM:WINDOWS");
|
||||
if (linkEnvironment.LinkAsConsoleProgram)
|
||||
{
|
||||
args.Add("/SUBSYSTEM:CONSOLE");
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Add("/SUBSYSTEM:WINDOWS");
|
||||
}
|
||||
|
||||
// Generate Windows Metadata
|
||||
if (linkEnvironment.GenerateWindowsMetadata)
|
||||
|
||||
Reference in New Issue
Block a user