Skip to content

Commit ad3cbfc

Browse files
author
Adrien GIVRY
committed
Implementing a new asset duplication naming system
Previously, the asset duplication naming system was adding `(Copy)` after the name of each duplicated asset. However, like in our previous actor duplication naming system, this nomenclature was very heavy and caused relatively long asset names after multiple duplications. In order to avoid code duplication, the actor duplication naming system has been generalized in OvTools::Utils::String as a function able to generate a unique string satisfying a predicate. This new function (Responsible of adding integer suffixes between parentheses) is based on the previous actor duplication naming system. It is now used by both the asset browser and the hierarchy to generate unique names for actors and assets.
1 parent 027b579 commit ad3cbfc

4 files changed

Lines changed: 70 additions & 53 deletions

File tree

Sources/Overload/OvEditor/src/OvEditor/Core/EditorActions.cpp

Lines changed: 5 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -568,58 +568,16 @@ bool OvEditor::Core::EditorActions::DestroyActor(OvCore::ECS::Actor & p_actor)
568568

569569
std::string FindDuplicatedActorUniqueName(OvCore::ECS::Actor& p_duplicated, OvCore::ECS::Actor& p_newActor, OvCore::SceneSystem::Scene& p_scene)
570570
{
571-
const auto sourceName = p_duplicated.GetName();
572-
auto sourceNameWithoutSuffix = sourceName;
573-
574-
auto suffixOpeningParenthesisPos = std::string::npos;
575-
auto suffixClosingParenthesisPos = std::string::npos;
576-
577-
// Keep track of the current character position when iterating onto `sourceName`
578-
auto currentPos = decltype(std::string::npos){sourceName.length() - 1};
579-
580-
// Here we search for `(` and `)` positions. (Needed to extract the number between those parenthesis)
581-
for (auto it = sourceName.rbegin(); it < sourceName.rend(); ++it, --currentPos)
582-
{
583-
auto c = *it;
584-
585-
if (suffixClosingParenthesisPos == std::string::npos && c == ')') suffixClosingParenthesisPos = currentPos;
586-
if (suffixClosingParenthesisPos != std::string::npos && c == '(') suffixOpeningParenthesisPos = currentPos;
587-
}
588-
589-
// We need to declare our `duplicationCounter` here to store the number between found parenthesis OR 1 (In the case no parenthesis, AKA, suffix, has been found)
590-
auto duplicationCounter = uint32_t{ 1 };
591-
592-
// If the two parenthis have been found AND the closing parenthesis is the last character AND there is a space before the opening parenthesis
593-
if (suffixOpeningParenthesisPos != std::string::npos && suffixClosingParenthesisPos == sourceName.length() - 1 && suffixOpeningParenthesisPos > 0 && sourceName[suffixOpeningParenthesisPos - 1] == ' ')
594-
{
595-
// Extract the string between those parenthesis
596-
const auto between = sourceName.substr(suffixOpeningParenthesisPos + 1, suffixClosingParenthesisPos - suffixOpeningParenthesisPos - 1);
597-
598-
// If the `between` string is composed of digits (AKA, `between` is a number)
599-
if (!between.empty() && std::find_if(between.begin(), between.end(), [](unsigned char c) { return !std::isdigit(c); }) == between.end())
600-
{
601-
duplicationCounter = static_cast<uint32_t>(std::atoi(between.c_str()));
602-
sourceNameWithoutSuffix = sourceName.substr(0, suffixOpeningParenthesisPos - 1);
603-
}
604-
}
605-
606571
const auto parent = p_newActor.GetParent();
607572
const auto adjacentActors = parent ? parent->GetChildren() : p_scene.GetActors();
608573

609-
auto foundName = sourceNameWithoutSuffix;
610-
611-
// Lambda that checks if the current `foundName` is used by the actor given in parameter (Also ensure that the given actor is adjacent to our new actor)
612-
// We call "adjacent" two actors that shares the same hierarchical level. (Ex: If [A] and [B] are both direction children of [C], then, they are adjacents)
613-
const auto isActorNameTaken = [&foundName, parent](auto actor) { return (parent || !actor->GetParent()) && actor->GetName() == foundName; };
614-
615-
// While there is an adjacent actor with the current `foundName`, we keep generating new names
616-
while (std::find_if(adjacentActors.begin(), adjacentActors.end(), isActorNameTaken) != adjacentActors.end())
574+
auto availabilityChecker = [&parent, &adjacentActors](std::string target) -> bool
617575
{
618-
// New names are composed of the `sourceNameWithoutSuffix` (Ex: "Cube (1)" name without suffix is "Cube")
619-
foundName = sourceNameWithoutSuffix + " (" + std::to_string(duplicationCounter++) + ")";
620-
}
576+
const auto isActorNameTaken = [&target, parent](auto actor) { return (parent || !actor->GetParent()) && actor->GetName() == target; };
577+
return std::find_if(adjacentActors.begin(), adjacentActors.end(), isActorNameTaken) == adjacentActors.end();
578+
};
621579

622-
return foundName;
580+
return OvTools::Utils::String::GenerateUnique(p_duplicated.GetName(), availabilityChecker);
623581
}
624582

625583
void OvEditor::Core::EditorActions::DuplicateActor(OvCore::ECS::Actor & p_toDuplicate, OvCore::ECS::Actor* p_forcedParent, bool p_focus)

Sources/Overload/OvEditor/src/OvEditor/Panels/AssetBrowser.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <OvWindowing/Dialogs/OpenFileDialog.h>
2222
#include <OvTools/Utils/SystemCalls.h>
2323
#include <OvTools/Utils/PathParser.h>
24+
#include <OvTools/Utils/String.h>
2425

2526
#include <OvCore/Global/ServiceLocator.h>
2627
#include <OvCore/ResourceManagement/ModelManager.h>
@@ -593,12 +594,14 @@ class FileContextualMenu : public BrowserItemContextualMenu
593594
if (size_t pos = filePathWithoutExtension.rfind('.'); pos != std::string::npos)
594595
filePathWithoutExtension = filePathWithoutExtension.substr(0, pos);
595596

596-
std::string newNameWithoutExtension = filePathWithoutExtension + " (Copy)";
597597
std::string extension = "." + OvTools::Utils::PathParser::GetExtension(filePath);
598-
while (std::filesystem::exists(newNameWithoutExtension + extension))
599-
{
600-
newNameWithoutExtension += " (Copy)";
601-
}
598+
599+
auto filenameAvailable = [&extension](const std::string& target)
600+
{
601+
return !std::filesystem::exists(target + extension);
602+
};
603+
604+
const auto newNameWithoutExtension = OvTools::Utils::String::GenerateUnique(filePathWithoutExtension, filenameAvailable);
602605

603606
std::string finalPath = newNameWithoutExtension + extension;
604607
std::filesystem::copy(filePath, finalPath);

Sources/Overload/OvTools/include/OvTools/Utils/String.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#pragma once
88

99
#include <string>
10+
#include <functional>
1011

1112
#include "OvTools/API/Export.h"
1213

@@ -38,5 +39,12 @@ namespace OvTools::Utils
3839
* @param p_to
3940
*/
4041
static void ReplaceAll(std::string& p_target, const std::string& p_from, const std::string& p_to);
42+
43+
/**
44+
* Generate a unique string satisfying the availability predicate
45+
* @param p_source
46+
* @param p_isAvailable (A callback that must returning true if the input string is available)
47+
*/
48+
static std::string GenerateUnique(const std::string& p_source, std::function<bool(std::string)> p_isAvailable);
4149
};
4250
}

Sources/Overload/OvTools/src/OvTools/Utils/String.cpp

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,52 @@ void OvTools::Utils::String::ReplaceAll(std::string& p_target, const std::string
2929
p_target.replace(start_pos, p_from.length(), p_to);
3030
start_pos += p_to.length();
3131
}
32-
}
32+
}
33+
34+
std::string OvTools::Utils::String::GenerateUnique(const std::string& p_source, std::function<bool(std::string)> p_isAvailable)
35+
{
36+
auto suffixlessSource = p_source;
37+
38+
auto suffixOpeningParenthesisPos = std::string::npos;
39+
auto suffixClosingParenthesisPos = std::string::npos;
40+
41+
// Keep track of the current character position when iterating onto `p_source`
42+
auto currentPos = decltype(std::string::npos){p_source.length() - 1};
43+
44+
// Here we search for `(` and `)` positions. (Needed to extract the number between those parenthesis)
45+
for (auto it = p_source.rbegin(); it < p_source.rend(); ++it, --currentPos)
46+
{
47+
const auto c = *it;
48+
49+
if (suffixClosingParenthesisPos == std::string::npos && c == ')') suffixClosingParenthesisPos = currentPos;
50+
if (suffixClosingParenthesisPos != std::string::npos && c == '(') suffixOpeningParenthesisPos = currentPos;
51+
}
52+
53+
// We need to declare our `counter` here to store the number between found parenthesis OR 1 (In the case no parenthesis, AKA, suffix, has been found)
54+
auto counter = uint32_t{ 1 };
55+
56+
// If the two parenthis have been found AND the closing parenthesis is the last character AND there is a space before the opening parenthesis
57+
if (suffixOpeningParenthesisPos != std::string::npos && suffixClosingParenthesisPos == p_source.length() - 1 && suffixOpeningParenthesisPos > 0 && p_source[suffixOpeningParenthesisPos - 1] == ' ')
58+
{
59+
// Extract the string between those parenthesis
60+
const auto between = p_source.substr(suffixOpeningParenthesisPos + 1, suffixClosingParenthesisPos - suffixOpeningParenthesisPos - 1);
61+
62+
// If the `between` string is composed of digits (AKA, `between` is a number)
63+
if (!between.empty() && std::find_if(between.begin(), between.end(), [](unsigned char c) { return !std::isdigit(c); }) == between.end())
64+
{
65+
counter = static_cast<uint32_t>(std::atoi(between.c_str()));
66+
suffixlessSource = p_source.substr(0, suffixOpeningParenthesisPos - 1);
67+
}
68+
}
69+
70+
auto result = suffixlessSource;
71+
72+
// While `result` isn't available, we keep generating new strings
73+
while (!p_isAvailable(result))
74+
{
75+
// New strings are composed of the `suffixlessSource` (Ex: "Foo (1)" without suffix is "Foo")
76+
result = suffixlessSource + " (" + std::to_string(counter++) + ")";
77+
}
78+
79+
return result;
80+
}

0 commit comments

Comments
 (0)