From d3ff60af2121fd1d75868195ab042e7ab6d281bb Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 28 Aug 2025 17:05:26 -0700 Subject: [PATCH] remove all uses of std::string in filament - libbackend (except webgpu) - libfilament - libutils std::string generates a lot of code bloat, we use CString instead. We also update CString to be more compatible with std::string's api. --- filament/backend/src/CommandStream.cpp | 10 +- filament/backend/src/Handle.cpp | 13 +- .../src/opengl/ShaderCompilerService.cpp | 31 +- .../backend/src/vulkan/VulkanCommands.cpp | 17 +- filament/backend/src/vulkan/VulkanCommands.h | 18 +- filament/backend/src/vulkan/VulkanHandles.cpp | 3 +- .../backend/src/vulkan/memory/Resource.cpp | 2 +- filament/backend/src/vulkan/memory/Resource.h | 2 +- filament/src/details/Material.cpp | 4 +- filament/src/fg/DependencyGraph.cpp | 18 +- filament/src/fg/FrameGraph.cpp | 15 +- filament/src/fg/PassNode.cpp | 26 +- filament/src/fg/ResourceNode.cpp | 33 +- libs/utils/include/utils/CString.h | 137 +++++- libs/utils/include/utils/Panic.h | 17 +- libs/utils/src/CString.cpp | 74 ++- libs/utils/test/test_CString.cpp | 431 +++++++++++++++++- 17 files changed, 725 insertions(+), 126 deletions(-) diff --git a/filament/backend/src/CommandStream.cpp b/filament/backend/src/CommandStream.cpp index ad88a7cb8e..02ededfa78 100644 --- a/filament/backend/src/CommandStream.cpp +++ b/filament/backend/src/CommandStream.cpp @@ -57,7 +57,7 @@ static void printParameterPack(io::ostream& out, const FIRST& first, const REMAI printParameterPack(out, rest...); } -static UTILS_NOINLINE UTILS_UNUSED std::string extractMethodName(std::string& command) noexcept { +static UTILS_NOINLINE UTILS_UNUSED std::string_view extractMethodName(std::string_view command) noexcept { constexpr const char startPattern[] = "::Command<&filament::backend::Driver::"; auto pos = command.rfind(startPattern); auto end = command.rfind('('); @@ -133,9 +133,9 @@ template void CommandType::Command::log(std::index_sequence) noexcept { #if DEBUG_COMMAND_STREAM static_assert(UTILS_HAS_RTTI, "DEBUG_COMMAND_STREAM can only be used with RTTI"); - std::string command = utils::CallStack::demangleTypeName(typeid(Command).name()).c_str(); - DLOG(INFO) << extractMethodName(command) << " : size=" << sizeof(Command); - utils::io::sstream parameterPack; + CString command = CallStack::demangleTypeName(typeid(Command).name()); + DLOG(INFO) << extractMethodName({command.data(), command.size()}) << " : size=" << sizeof(Command); + io::sstream parameterPack; printParameterPack(parameterPack, std::get(mArgs)...); DLOG(INFO) << "\t" << parameterPack.c_str(); #endif @@ -164,7 +164,7 @@ void CommandType::Command::log() noexcept { // ------------------------------------------------------------------------------------------------ void CustomCommand::execute(Driver&, CommandBase* base, intptr_t* next) { - *next = CustomCommand::align(sizeof(CustomCommand)); + *next = align(sizeof(CustomCommand)); static_cast(base)->mCommand(); static_cast(base)->~CustomCommand(); } diff --git a/filament/backend/src/Handle.cpp b/filament/backend/src/Handle.cpp index 77c1fbc6be..c4101197fa 100644 --- a/filament/backend/src/Handle.cpp +++ b/filament/backend/src/Handle.cpp @@ -21,7 +21,8 @@ #include #ifndef NDEBUG -# include +# include +# include #endif #include @@ -36,11 +37,11 @@ static char const * const kOurNamespace = "filament::backend::"; // removes all occurrences of "what" from "str" UTILS_NOINLINE -static std::string& removeAll(std::string& str, const std::string& what) noexcept { +static CString& removeAll(CString& str, const std::string_view what) noexcept { if (!what.empty()) { - const std::string empty; + const CString empty; size_t pos = 0; - while ((pos = str.find(what, pos)) != std::string::npos) { + while ((pos = std::string_view{ str.data(), str.size() }.find(what, pos)) != std::string_view::npos) { str.replace(pos, what.length(), empty); } } @@ -49,13 +50,13 @@ static std::string& removeAll(std::string& str, const std::string& what) noexcep template UTILS_NOINLINE -static io::ostream& logHandle(io::ostream& out, std::string& typeName, T id) noexcept { +static io::ostream& logHandle(io::ostream& out, CString& typeName, T id) noexcept { return out << removeAll(typeName, kOurNamespace) << " @ " << id; } template io::ostream& operator<<(io::ostream& out, const Handle& h) noexcept { - std::string s(CallStack::typeName>().c_str()); + CString s{ CallStack::typeName>() }; return logHandle(out, s, h.getId()); } diff --git a/filament/backend/src/opengl/ShaderCompilerService.cpp b/filament/backend/src/opengl/ShaderCompilerService.cpp index 99f1fda839..aa0dc61953 100644 --- a/filament/backend/src/opengl/ShaderCompilerService.cpp +++ b/filament/backend/src/opengl/ShaderCompilerService.cpp @@ -46,7 +46,6 @@ #include #include #include -#include #include #include #include @@ -67,9 +66,9 @@ constexpr uint32_t MAX_NUM_SYNCHRONOUS_PROGRAMS_PER_FRAME = 1; // ------------------------------------------------------------------------------------------------ -static std::string to_string(bool const b) { return b ? "true" : "false"; } -static std::string to_string(int const i) { return std::to_string(i); } -static std::string to_string(float const f) { return "float(" + std::to_string(f) + ")"; } +static CString to_string(bool const b) { return CString{ b ? "true" : "false" }; } +static CString to_string(int const i) { return utils::to_string(i); } +static CString to_string(float const f) { return "float(" + utils::to_string(f) + ")"; } static void logCompilationError(ShaderStage shaderType, const char* name, GLuint shaderId, Program::ShaderBlob const& sourceCode) noexcept; @@ -696,14 +695,14 @@ void ShaderCompilerService::cancelPendingSynchronousProgram(program_token_t cons bool multiview, program_token_t const& token) noexcept { FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT); - auto const appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) { - s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' '; + auto const appendSpecConstantString = +[](CString& s, Program::SpecializationConstant const& sc) { + s += "#define SPIRV_CROSS_CONSTANT_ID_" + utils::to_string(sc.id) + ' '; s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value); s += '\n'; return s; }; - std::string specializationConstantString; + CString specializationConstantString; int32_t numViews = 2; for (auto const& sc: specializationConstants) { appendSpecConstantString(specializationConstantString, sc); @@ -766,8 +765,10 @@ void ShaderCompilerService::cancelPendingSynchronousProgram(program_token_t cons } std::array sources = { - version, prolog, specializationConstantString, packingFunctions, - { body.data(), body.size() - 1 }// null-terminated + version, prolog, + { specializationConstantString.data(), specializationConstantString.size() }, + packingFunctions, + { body.data(), body.size() - 1 } // null-terminated }; // Some of the sources may be zero-length. Remove them as to avoid passing lengths of @@ -966,16 +967,18 @@ UTILS_NOINLINE sourceCode.size() }; size_t lc = 1; size_t start = 0; - std::string line; + CString line; while (true) { size_t const end = shader.find('\n', start); - if (end == std::string::npos) { - line = shader.substr(start); + if (end == std::string_view::npos) { + auto sv = shader.substr(start); + line = { sv.data(), sv.size() }; } else { - line = shader.substr(start, end - start); + auto sv = shader.substr(start, end - start); + line = { sv.data(), sv.size() }; } LOG(ERROR) << lc++ << ": " << line.c_str(); - if (end == std::string::npos) { + if (end == std::string_view::npos) { break; } start = end + 1; diff --git a/filament/backend/src/vulkan/VulkanCommands.cpp b/filament/backend/src/vulkan/VulkanCommands.cpp index 2d9c6da707..3ad44395d6 100644 --- a/filament/backend/src/vulkan/VulkanCommands.cpp +++ b/filament/backend/src/vulkan/VulkanCommands.cpp @@ -24,6 +24,7 @@ #include "VulkanConstants.h" #include "VulkanContext.h" +#include #include #include #include @@ -57,26 +58,26 @@ VkCommandBuffer createCommandBuffer(VkDevice device, VkCommandPool pool) { } // anonymous namespace #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) -void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept { +void VulkanGroupMarkers::push(CString const& marker, Timestamp start) noexcept { mMarkers.push_back({marker, start.time_since_epoch().count() > 0.0 ? start : std::chrono::high_resolution_clock::now()}); } -std::pair VulkanGroupMarkers::pop() noexcept { +std::pair VulkanGroupMarkers::pop() noexcept { auto ret = mMarkers.back(); mMarkers.pop_back(); return ret; } -std::pair VulkanGroupMarkers::pop_bottom() noexcept { +std::pair VulkanGroupMarkers::pop_bottom() noexcept { auto ret = mMarkers.front(); mMarkers.pop_front(); return ret; } -std::pair const& VulkanGroupMarkers::top() const { +std::pair const& VulkanGroupMarkers::top() const { assert_invariant(!empty()); return mMarkers.back(); } @@ -353,7 +354,7 @@ void CommandBufferPool::waitFor(VkSemaphore previousAction, VkPipelineStageFlags } #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) -std::string CommandBufferPool::topMarker() const { +CString CommandBufferPool::topMarker() const { if (!mGroupMarkers || mGroupMarkers->empty()) { return ""; } @@ -364,11 +365,11 @@ void CommandBufferPool::pushMarker(char const* marker, VulkanGroupMarkers::Times if (!mGroupMarkers) { mGroupMarkers = std::make_unique(); } - mGroupMarkers->push(marker, timestamp); + mGroupMarkers->push(CString{ marker }, timestamp); getRecording().pushMarker(marker); } -std::pair CommandBufferPool::popMarker() { +std::pair CommandBufferPool::popMarker() { assert_invariant(mGroupMarkers && !mGroupMarkers->empty()); auto ret = mGroupMarkers->pop(); @@ -528,7 +529,7 @@ void VulkanCommands::insertEventMarker(char const* str, uint32_t len) { } } -std::string VulkanCommands::getTopGroupMarker() const { +CString VulkanCommands::getTopGroupMarker() const { if (mProtectedPool) { return mProtectedPool->topMarker(); } diff --git a/filament/backend/src/vulkan/VulkanCommands.h b/filament/backend/src/vulkan/VulkanCommands.h index 203a762fb1..2ec8b2641d 100644 --- a/filament/backend/src/vulkan/VulkanCommands.h +++ b/filament/backend/src/vulkan/VulkanCommands.h @@ -28,6 +28,7 @@ #include "vulkan/utils/StaticVector.h" #include +#include #include #include @@ -35,7 +36,6 @@ #include #include -#include #include namespace filament::backend { @@ -47,14 +47,14 @@ class VulkanGroupMarkers { public: using Timestamp = std::chrono::time_point; - void push(std::string const& marker, Timestamp start = {}) noexcept; - std::pair pop() noexcept; - std::pair pop_bottom() noexcept; - std::pair const& top() const; + void push(utils::CString const& marker, Timestamp start = {}) noexcept; + std::pair pop() noexcept; + std::pair pop_bottom() noexcept; + std::pair const& top() const; bool empty() const noexcept; private: - std::list> mMarkers; + std::list> mMarkers; }; #endif // FVK_DEBUG_GROUP_MARKERS @@ -148,9 +148,9 @@ struct CommandBufferPool { void waitFor(VkSemaphore previousAction, VkPipelineStageFlags waitStage); #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - std::string topMarker() const; + utils::CString topMarker() const; void pushMarker(char const* marker, VulkanGroupMarkers::Timestamp timestamp); - std::pair popMarker(); + std::pair popMarker(); void insertEvent(char const* marker); #endif @@ -249,7 +249,7 @@ public: void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {}); void popGroupMarker(); void insertEventMarker(char const* string, uint32_t len); - std::string getTopGroupMarker() const; + utils::CString getTopGroupMarker() const; #endif private: diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index b66a82f963..99cae92faf 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -32,6 +32,7 @@ #include // UTILS_FALLTHROUGH #include // ASSERT_POSTCONDITION +#include using namespace bluevk; @@ -285,7 +286,7 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept << " error=" << static_cast(result); #if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) - std::string name{ builder.getName().c_str(), builder.getName().size() }; + utils::CString name{ builder.getName().c_str(), builder.getName().size() }; switch (static_cast(i)) { case ShaderStage::VERTEX: name += "_vs"; diff --git a/filament/backend/src/vulkan/memory/Resource.cpp b/filament/backend/src/vulkan/memory/Resource.cpp index 13669e9a9a..6c19e633d4 100644 --- a/filament/backend/src/vulkan/memory/Resource.cpp +++ b/filament/backend/src/vulkan/memory/Resource.cpp @@ -95,7 +95,7 @@ ResourceType getTypeEnum() noexcept { return ResourceType::UNDEFINED_TYPE; } -std::string getTypeStr(ResourceType type) { +std::string_view getTypeStr(ResourceType type) { switch (type) { case ResourceType::BUFFER_OBJECT: return "BufferObject"; diff --git a/filament/backend/src/vulkan/memory/Resource.h b/filament/backend/src/vulkan/memory/Resource.h index ff79a8f79d..ccb2cc3a77 100644 --- a/filament/backend/src/vulkan/memory/Resource.h +++ b/filament/backend/src/vulkan/memory/Resource.h @@ -58,7 +58,7 @@ enum class ResourceType : uint8_t { template ResourceType getTypeEnum() noexcept; -std::string getTypeStr(ResourceType type); +std::string_view getTypeStr(ResourceType type); inline bool isThreadSafeType(ResourceType type) { return type == ResourceType::FENCE || type == ResourceType::TIMER_QUERY; diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 552e8fffc0..d81f80d231 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -86,9 +86,9 @@ static std::unique_ptr createParser(Backend const backend, MaterialParser::ParseResult const materialResult = materialParser->parse(); if (UTILS_UNLIKELY(materialResult == MaterialParser::ParseResult::ERROR_MISSING_BACKEND)) { - std::string languageNames; + CString languageNames; for (auto it = languages.begin(); it != languages.end(); ++it) { - languageNames.append(shaderLanguageToString(*it)); + languageNames.append(CString{shaderLanguageToString(*it)}); if (std::next(it) != languages.end()) { languageNames.append(", "); } diff --git a/filament/src/fg/DependencyGraph.cpp b/filament/src/fg/DependencyGraph.cpp index 242c1277f4..f1d85c7d51 100644 --- a/filament/src/fg/DependencyGraph.cpp +++ b/filament/src/fg/DependencyGraph.cpp @@ -18,7 +18,13 @@ #include +#include +#include +#include +#include + #include +#include namespace filament { @@ -254,25 +260,23 @@ char const* DependencyGraph::Node::getName() const noexcept { utils::CString DependencyGraph::Node::graphvizify() const noexcept { #ifndef NDEBUG - std::string s; - s.reserve(128); + utils::CString s; - uint32_t id = getId(); + uint32_t const id = getId(); const char* const nodeName = getName(); uint32_t const refCount = getRefCount(); s.append("[label=\""); s.append(nodeName); s.append("\\nrefs: "); - s.append(std::to_string(refCount)); + s.append(utils::to_string(refCount)); s.append(", id: "); - s.append(std::to_string(id)); + s.append(utils::to_string(id)); s.append("\", style=filled, fillcolor="); s.append(refCount ? "skyblue" : "skyblue4"); s.append("]"); - s.shrink_to_fit(); - return utils::CString{ s.c_str() }; + return s; #else return {}; #endif diff --git a/filament/src/fg/FrameGraph.cpp b/filament/src/fg/FrameGraph.cpp index 51ed5f6448..0c9df6e43e 100644 --- a/filament/src/fg/FrameGraph.cpp +++ b/filament/src/fg/FrameGraph.cpp @@ -547,21 +547,16 @@ fgviewer::FrameGraphInfo FrameGraph::getFrameGraphInfo(const char *viewName) con // resource type so it works auto descriptor = static_cast const*>( getResource(resourceHandle))->descriptor; - emplace_resource_property("width", - utils::CString(std::to_string(descriptor.width).data())); - emplace_resource_property("height", - utils::CString(std::to_string(descriptor.height).data())); - emplace_resource_property("depth", - utils::CString(std::to_string(descriptor.depth).data())); - emplace_resource_property("format", - utils::to_string(descriptor.format)); + emplace_resource_property("width", utils::to_string(descriptor.width)); + emplace_resource_property("height", utils::to_string(descriptor.height)); + emplace_resource_property("depth", utils::to_string(descriptor.depth)); + emplace_resource_property("format", utils::to_string(descriptor.format)); }; if (resourceNode->getParentNode() != nullptr) { emplace_resource_property("is_subresource_of", - utils::CString(std::to_string( - resourceNode->getParentHandle().index).data())); + utils::to_string(resourceNode->getParentHandle().index)); } emplace_resource_descriptor(resourceHandle); resources.emplace(resourceHandle.index, fgviewer::FrameGraphInfo::Resource( diff --git a/filament/src/fg/PassNode.cpp b/filament/src/fg/PassNode.cpp index 6fce0d79b1..4efa7cd61d 100644 --- a/filament/src/fg/PassNode.cpp +++ b/filament/src/fg/PassNode.cpp @@ -23,6 +23,10 @@ #include
+#include +#include +#include + #include using namespace filament::backend; @@ -294,7 +298,7 @@ RenderPassNode::RenderPassData const* RenderPassNode::getRenderPassData(uint32_t utils::CString RenderPassNode::graphvizify() const noexcept { #ifndef NDEBUG - std::string s; + utils::CString s; uint32_t const id = getId(); const char* const nodeName = getName(); @@ -303,17 +307,17 @@ utils::CString RenderPassNode::graphvizify() const noexcept { s.append("[label=\""); s.append(nodeName); s.append("\\nrefs: "); - s.append(std::to_string(refCount)); + s.append(utils::to_string(refCount)); s.append(", id: "); - s.append(std::to_string(id)); + s.append(utils::to_string(id)); for (auto const& rt :mRenderTargetData) { s.append("\\nS:"); - s.append(utils::to_string(rt.backend.params.flags.discardStart).c_str()); + s.append(utils::to_string(rt.backend.params.flags.discardStart)); s.append(", E:"); - s.append(utils::to_string(rt.backend.params.flags.discardEnd).c_str()); + s.append(utils::to_string(rt.backend.params.flags.discardEnd)); s.append(", C:"); - s.append(utils::to_string(rt.backend.params.flags.clear).c_str()); + s.append(utils::to_string(rt.backend.params.flags.clear)); } s.append("\", "); @@ -322,7 +326,7 @@ utils::CString RenderPassNode::graphvizify() const noexcept { s.append(refCount ? "darkorange" : "darkorange4"); s.append("]"); - return utils::CString{ s.c_str() }; + return s; #else return {}; #endif @@ -342,14 +346,12 @@ char const* PresentPassNode::getName() const noexcept { utils::CString PresentPassNode::graphvizify() const noexcept { #ifndef NDEBUG - std::string s; - s.reserve(128); + utils::CString s; uint32_t const id = getId(); s.append("[label=\"Present , id: "); - s.append(std::to_string(id)); + s.append(utils::to_string(id)); s.append("\", style=filled, fillcolor=red3]"); - s.shrink_to_fit(); - return utils::CString{ s.c_str() }; + return s; #else return {}; #endif diff --git a/filament/src/fg/ResourceNode.cpp b/filament/src/fg/ResourceNode.cpp index 2b4cf2af66..ca75e33d57 100644 --- a/filament/src/fg/ResourceNode.cpp +++ b/filament/src/fg/ResourceNode.cpp @@ -14,10 +14,21 @@ * limitations under the License. */ +#include "FrameGraphId.h" + +#include "details/DependencyGraph.h" + #include "fg/FrameGraph.h" #include "fg/details/PassNode.h" #include "fg/details/ResourceNode.h" +#include +#include +#include + +#include +#include + namespace filament { ResourceNode::ResourceNode(FrameGraph& fg, FrameGraphHandle const h, FrameGraphHandle const parent) noexcept @@ -105,20 +116,20 @@ bool ResourceNode::hasWriteFrom(PassNode const* node) const noexcept { void ResourceNode::setParentReadDependency(ResourceNode* parent) noexcept { if (!mParentReadEdge) { - mParentReadEdge = new DependencyGraph::Edge(mFrameGraph.getGraph(), parent, this); + mParentReadEdge = new(std::nothrow) DependencyGraph::Edge(mFrameGraph.getGraph(), parent, this); } } void ResourceNode::setParentWriteDependency(ResourceNode* parent) noexcept { if (!mParentWriteEdge) { - mParentWriteEdge = new DependencyGraph::Edge(mFrameGraph.getGraph(), this, parent); + mParentWriteEdge = new(std::nothrow) DependencyGraph::Edge(mFrameGraph.getGraph(), this, parent); } } void ResourceNode::setForwardResourceDependency(ResourceNode* source) noexcept { assert_invariant(!mForwardedEdge); - mForwardedEdge = new DependencyGraph::Edge(mFrameGraph.getGraph(), this, source); + mForwardedEdge = new(std::nothrow) DependencyGraph::Edge(mFrameGraph.getGraph(), this, source); } @@ -132,24 +143,23 @@ void ResourceNode::resolveResourceUsage(DependencyGraph& graph) noexcept { utils::CString ResourceNode::graphvizify() const noexcept { #ifndef NDEBUG - std::string s; - s.reserve(128); + utils::CString s; uint32_t const id = getId(); const char* const nodeName = getName(); - VirtualResource* const pResource = mFrameGraph.getResource(resourceHandle); + VirtualResource const* const pResource = mFrameGraph.getResource(resourceHandle); FrameGraph::ResourceSlot const& slot = mFrameGraph.getResourceSlot(resourceHandle); s.append("[label=\""); s.append(nodeName); s.append("\\nrefs: "); - s.append(std::to_string(pResource->refcount)); + s.append(utils::to_string(pResource->refcount)); s.append(", id: "); - s.append(std::to_string(id)); + s.append(utils::to_string(id)); s.append("\\nversion: "); - s.append(std::to_string(resourceHandle.version)); + s.append(utils::to_string(resourceHandle.version)); s.append("/"); - s.append(std::to_string(slot.version)); + s.append(utils::to_string(slot.version)); if (pResource->isImported()) { s.append(", imported"); } @@ -160,9 +170,8 @@ utils::CString ResourceNode::graphvizify() const noexcept { s.append("style=filled, fillcolor="); s.append(pResource->refcount ? "skyblue" : "skyblue4"); s.append("]"); - s.shrink_to_fit(); - return utils::CString{ s.c_str() }; + return s; #else return {}; #endif diff --git a/libs/utils/include/utils/CString.h b/libs/utils/include/utils/CString.h index f6fbcd9190..710c776265 100644 --- a/libs/utils/include/utils/CString.h +++ b/libs/utils/include/utils/CString.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -51,6 +52,10 @@ struct hashCStrings { template using StringLiteral = const char[N]; +namespace details { + template + constexpr bool is_char_pointer_v = std::is_pointer_v && std::is_same_v>>; +} // namespace details // ------------------------------------------------------------------------------------------------ @@ -127,8 +132,7 @@ public: // replace template - CString& replace(size_type const pos, - size_type const len, const StringLiteral& str) & noexcept { + CString& replace(size_type const pos, size_type const len, const StringLiteral& str) & noexcept { return replace(pos, len, str, N - 1); } @@ -136,36 +140,83 @@ public: return replace(pos, len, str.c_str_safe(), str.size()); } + template >> + CString& replace(size_type pos, size_type len, T str) & noexcept { + if (str) { + return replace(pos, len, str, strlen(str)); + } + return replace(pos, len, "", 0); + } + template - CString&& replace(size_type const pos, - size_type const len, const StringLiteral& str) && noexcept { - return std::move(replace(pos, len, str)); + CString&& replace(size_type pos, size_type len, const StringLiteral& str) && noexcept { + this->replace(pos, len, str); + return std::move(*this); } CString&& replace(size_type const pos, size_type const len, const CString& str) && noexcept { - return std::move(replace(pos, len, str)); + this->replace(pos, len, str); + return std::move(*this); } + template >> + CString&& replace(size_type pos, size_type len, T str) && noexcept { + this->replace(pos, len, str); + return std::move(*this); + } + + // insert + CString& insert(size_type pos, char c) & noexcept { + const char s[1] = { c }; + return replace(pos, 0, s, 1); + } + template CString& insert(size_type const pos, const StringLiteral& str) & noexcept { - return replace(pos, 0, str); + return replace(pos, 0, str, N - 1); } CString& insert(size_type const pos, const CString& str) & noexcept { - return replace(pos, 0, str); + return replace(pos, 0, str.c_str_safe(), str.size()); + } + + template >> + CString& insert(size_type pos, T str) & noexcept { + if (str) { + return replace(pos, 0, str, strlen(str)); + } + return *this; + } + + CString&& insert(size_type pos, char c) && noexcept { + this->insert(pos, c); + return std::move(*this); } template - CString&& insert(size_type const pos, const StringLiteral& str) && noexcept { - return std::move(*this).replace(pos, 0, str); + CString&& insert(size_type pos, const StringLiteral& str) && noexcept { + this->insert(pos, str); + return std::move(*this); } CString&& insert(size_type const pos, const CString& str) && noexcept { - return std::move(*this).replace(pos, 0, str); + this->insert(pos, str); + return std::move(*this); } + template >> + CString&& insert(size_type pos, T str) && noexcept { + this->insert(pos, str); + return std::move(*this); + } + + // append + CString& append(char c) & noexcept { + return insert(length(), c); + } + template CString& append(const StringLiteral& str) & noexcept { return insert(length(), str); @@ -175,15 +226,49 @@ public: return insert(length(), str); } + template>> + CString& append(T str) & noexcept { + return insert(length(), str); + } + + CString&& append(char c) && noexcept { + this->append(c); + return std::move(*this); + } + template CString&& append(const StringLiteral& str) && noexcept { - return std::move(*this).insert(length(), str); + this->append(str); + return std::move(*this); } CString&& append(const CString& str) && noexcept { - return std::move(*this).insert(length(), str); + this->append(str); + return std::move(*this); } + template>> + CString&& append(T str) && noexcept { + this->append(str); + return std::move(*this); + } + + // operator+= + CString& operator+=(char c) & noexcept { + return append(c); + } + + CString& operator+=(const CString& str) & noexcept { + return append(str); + } + template + CString& operator+=(const StringLiteral& str) & noexcept { + return append(str); + } + template >> + CString& operator+=(T str) & noexcept { + return append(str); + } const_reference operator[](size_type const pos) const noexcept { assert(pos < size()); @@ -282,6 +367,32 @@ private: } }; +// operator+ +inline CString operator+(CString lhs, const CString& rhs) { + lhs += rhs; + return lhs; +} + +inline CString operator+(CString lhs, const char* rhs) { + lhs += rhs; + return lhs; +} + +inline CString operator+(const char* lhs, CString rhs) { + rhs.insert(0, lhs); + return rhs; +} + +inline CString operator+(CString lhs, char rhs) { + lhs += rhs; + return lhs; +} + +inline CString operator+(char lhs, CString rhs) { + rhs.insert(0, lhs); + return rhs; +} + // Implement this for your type for automatic conversion to CString. Failing to do so leads // to a compile-time failure. template diff --git a/libs/utils/include/utils/Panic.h b/libs/utils/include/utils/Panic.h index 9c613d48d0..acd1946814 100644 --- a/libs/utils/include/utils/Panic.h +++ b/libs/utils/include/utils/Panic.h @@ -377,7 +377,8 @@ public: * @param file the file where the above function in implemented * @param line the line in the above file where the error was detected * @param literal a literal version of the error message - * @param format printf style string describing the error + * @param format printf style format string describing the error + * @param ... printf style arguments * @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC * @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC * @see setMode() @@ -448,8 +449,8 @@ void panicLog( class UTILS_PUBLIC PreconditionPanic final : public TPanic { // Programming error, can be avoided // e.g.: invalid arguments - using TPanic::TPanic; - friend class TPanic; + using TPanic::TPanic; + friend class TPanic; constexpr static auto type = "Precondition"; }; @@ -462,8 +463,8 @@ class UTILS_PUBLIC PreconditionPanic final : public TPanic { class UTILS_PUBLIC PostconditionPanic final : public TPanic { // Usually only detectable at runtime // e.g.: deadlock would occur, arithmetic errors - using TPanic::TPanic; - friend class TPanic; + using TPanic::TPanic; + friend class TPanic; constexpr static auto type = "Postcondition"; }; @@ -476,8 +477,8 @@ class UTILS_PUBLIC PostconditionPanic final : public TPanic class UTILS_PUBLIC ArithmeticPanic final : public TPanic { // A common case of post-condition error // e.g.: underflow, overflow, internal computations errors - using TPanic::TPanic; - friend class TPanic; + using TPanic::TPanic; + friend class TPanic; constexpr static auto type = "Arithmetic"; }; @@ -547,7 +548,7 @@ public: template class FlagGuardedStream : public PanicStream { public: - FlagGuardedStream(bool enable, char const* function, char const* file, int line, + FlagGuardedStream(bool const enable, char const* function, char const* file, int const line, char const* condition) : PanicStream(function, file, line, condition), mEnablePanic(enable) {} diff --git a/libs/utils/src/CString.cpp b/libs/utils/src/CString.cpp index 519303ed03..13c30d7daf 100644 --- a/libs/utils/src/CString.cpp +++ b/libs/utils/src/CString.cpp @@ -20,7 +20,11 @@ #include #include +#include +#include +#include #include +#include #include @@ -113,5 +117,73 @@ io::ostream& operator<<(io::ostream& out, const CString& rhs) { } #endif -} // namespace utils +namespace { +// use a C-style variadic function to avoid code bloat from templates +UTILS_NOINLINE +CString to_string_impl(const char* format, ...) noexcept { + va_list args1; + va_start(args1, format); + va_list args2; + va_copy(args2, args1); + int const len = vsnprintf(nullptr, 0, format, args1); + va_end(args1); + if (len >= 0) { + CString s(len); + vsnprintf(s.data(), len + 1, format, args2); + va_end(args2); + return s; + } + va_end(args2); + return {}; +} + +} // anonymous namespace + +template<> +CString to_string(int const value) noexcept { + return to_string_impl("%d", value); +} + +template<> +CString to_string(unsigned int const value) noexcept { + return to_string_impl("%u", value); +} + +template<> +CString to_string(short const value) noexcept { + return to_string_impl("%hd", value); +} + +template<> +CString to_string(unsigned short const value) noexcept { + return to_string_impl("%hu", value); +} + +template<> +CString to_string(long const value) noexcept { + return to_string_impl("%ld", value); +} + +template<> +CString to_string(unsigned long const value) noexcept { + return to_string_impl("%lu", value); +} + +template<> +CString to_string(long long const value) noexcept { + return to_string_impl("%lld", value); +} + +template<> +CString to_string(unsigned long long const value) noexcept { + return to_string_impl("%llu", value); +} + + +template<> +CString to_string(float const value) noexcept { + return to_string_impl("%f", value); +} + +} // namespace utils diff --git a/libs/utils/test/test_CString.cpp b/libs/utils/test/test_CString.cpp index a19de9b7a4..a69b59397c 100644 --- a/libs/utils/test/test_CString.cpp +++ b/libs/utils/test/test_CString.cpp @@ -18,53 +18,416 @@ #include +#include +#include +#include + using namespace utils; TEST(CString, EmptyString) { - CString emptyString(""); + CString const emptyString(""); EXPECT_STREQ("", emptyString.c_str_safe()); + EXPECT_EQ(0, emptyString.length()); + EXPECT_TRUE(emptyString.empty()); +} + +TEST(CString, Constructors) { + // CString() + { + CString str; + EXPECT_EQ(nullptr, str.c_str()); + EXPECT_STREQ("", str.c_str_safe()); + EXPECT_EQ(0, str.length()); + EXPECT_TRUE(str.empty()); + } + // CString(const char* cstr, size_t length) + { + CString str("foobar", 3); + EXPECT_STREQ("foo", str.c_str()); + EXPECT_EQ(3, str.length()); + } + // CString(size_t length) + { + CString str(5); + EXPECT_EQ(5, str.length()); + // The memory is zero-initialized + EXPECT_EQ(0, str.c_str()[0]); + EXPECT_EQ(0, str.c_str()[4]); + } + // CString(const char* cstr) + { + CString str("hello"); + EXPECT_STREQ("hello", str.c_str()); + EXPECT_EQ(5, str.length()); + } + // CString(StringLiteral) + { + CString str("literal"); // this uses the template constructor + EXPECT_STREQ("literal", str.c_str()); + EXPECT_EQ(7, str.length()); + } + // Copy constructor + { + CString s1("copy me"); + CString s2(s1); + EXPECT_STREQ("copy me", s2.c_str()); + EXPECT_NE(s1.c_str(), s2.c_str()); // should be a deep copy + } + // Move constructor + { + CString s1("move me"); + const char* original_cstr = s1.c_str(); + CString s2(std::move(s1)); + EXPECT_STREQ("move me", s2.c_str()); + EXPECT_EQ(original_cstr, s2.c_str()); // pointer should be moved + EXPECT_EQ(nullptr, s1.c_str()); // original should be empty + } +} + +TEST(CString, Assignment) { + // Copy assignment + { + CString s1("copy"); + CString s2; + s2 = s1; + EXPECT_STREQ("copy", s2.c_str()); + EXPECT_NE(s1.c_str(), s2.c_str()); + } + // Move assignment + { + CString s1("move"); + const char* original_cstr = s1.c_str(); + CString s2; + s2 = std::move(s1); + EXPECT_STREQ("move", s2.c_str()); + EXPECT_EQ(original_cstr, s2.c_str()); + EXPECT_EQ(nullptr, s1.c_str()); + } + // self-assignment + { + CString s1("self"); + const char* original_cstr = s1.c_str(); + s1 = s1; + EXPECT_STREQ("self", s1.c_str()); + EXPECT_EQ(original_cstr, s1.c_str()); + } +} + +TEST(CString, Swap) { + CString s1("first"); + CString s2("second"); + + const char* p1 = s1.c_str(); + const char* p2 = s2.c_str(); + size_t l1 = s1.length(); + size_t l2 = s2.length(); + + s1.swap(s2); + + EXPECT_STREQ("second", s1.c_str()); + EXPECT_STREQ("first", s2.c_str()); + EXPECT_EQ(p2, s1.c_str()); + EXPECT_EQ(p1, s2.c_str()); + EXPECT_EQ(l2, s1.length()); + EXPECT_EQ(l1, s2.length()); +} + +TEST(CString, Concatenation) { + // operator+= + { + CString s("hello"); + s += CString(" world"); + EXPECT_STREQ("hello world", s.c_str()); + } + { + CString s("hello"); + s += " world"; + EXPECT_STREQ("hello world", s.c_str()); + } + { + CString s("hello"); + const char* world = " world"; + s += world; + EXPECT_STREQ("hello world", s.c_str()); + } + { + CString s("hello"); + s += ""; + EXPECT_STREQ("hello", s.c_str()); + } + { + CString s; + s += "world"; + EXPECT_STREQ("world", s.c_str()); + } + + // operator+ + { + CString s1("hello"); + CString s2(" world"); + CString s3 = s1 + s2; + EXPECT_STREQ("hello world", s3.c_str()); + } + { + CString s1("hello"); + CString s2 = s1 + " world"; + EXPECT_STREQ("hello world", s2.c_str()); + } + { + CString s1(" world"); + CString s2 = "hello" + s1; + EXPECT_STREQ("hello world", s2.c_str()); + } + { + CString s1("hello"); + const char* world = " world"; + CString s2 = s1 + world; + EXPECT_STREQ("hello world", s2.c_str()); + } + { + CString s1(" world"); + const char* hello = "hello"; + CString s2 = hello + s1; + EXPECT_STREQ("hello world", s2.c_str()); + } + { + CString s = CString("a") + CString("b") + "c" + "d"; + EXPECT_STREQ("abcd", s.c_str()); + } +} + +TEST(CString, Comparison) { + CString s1("abc"); + CString s2("abc"); + CString s3("def"); + CString s4("ab"); + + EXPECT_TRUE(s1 == s2); + EXPECT_FALSE(s1 == s3); + + EXPECT_TRUE(s1 != s3); + EXPECT_FALSE(s1 != s2); + + EXPECT_TRUE(s1 < s3); + EXPECT_FALSE(s3 < s1); + + EXPECT_TRUE(s3 > s1); + EXPECT_FALSE(s1 > s3); + + EXPECT_TRUE(s1 <= s2); + EXPECT_TRUE(s1 <= s3); + EXPECT_FALSE(s3 <= s1); + + EXPECT_TRUE(s2 >= s1); + EXPECT_TRUE(s3 >= s1); + EXPECT_FALSE(s1 >= s3); + + EXPECT_TRUE(s4 < s1); + EXPECT_TRUE(s1 > s4); +} + +TEST(CString, ElementAccess) { + CString str("01234"); + const CString cstr("const"); + + EXPECT_EQ('0', str.front()); + EXPECT_EQ('4', str.back()); + EXPECT_EQ('c', cstr.front()); + EXPECT_EQ('t', cstr.back()); + + EXPECT_EQ('2', str[2]); + EXPECT_EQ('n', cstr[2]); + + str[0] = 'A'; + EXPECT_STREQ("A1234", str.c_str()); + + EXPECT_EQ('3', str.at(3)); + EXPECT_EQ('t', cstr.at(4)); + + // iterators + std::string s(str.begin(), str.end()); + EXPECT_EQ("A1234", s); + + EXPECT_TRUE(std::equal(cstr.begin(), cstr.end(), "const")); +} + +TEST(CString, Append) { + // Append CString + { + CString str("foo"); + str.append(CString("bar")); + EXPECT_STREQ("foobar", str.c_str()); + } + // Append string literal + { + CString str("foo"); + str.append("bar"); + EXPECT_STREQ("foobar", str.c_str()); + } + // Append const char* + { + CString str("foo"); + const char* bar = "bar"; + str.append(bar); + EXPECT_STREQ("foobar", str.c_str()); + } + // Append nullptr + { + CString str("foo"); + const char* bar = nullptr; + str.append(bar); + EXPECT_STREQ("foo", str.c_str()); + } + // Append to empty + { + CString str; + str.append("foo"); + EXPECT_STREQ("foo", str.c_str()); + } + { + CString str; + str.append(CString("foo")); + EXPECT_STREQ("foo", str.c_str()); + } + { + CString str; + const char* foo = "foo"; + str.append(foo); + EXPECT_STREQ("foo", str.c_str()); + } + // Append empty + { + CString str("foo"); + str.append(""); + EXPECT_STREQ("foo", str.c_str()); + } + { + CString str("foo"); + str.append(CString("")); + EXPECT_STREQ("foo", str.c_str()); + } + { + CString str("foo"); + const char* empty = ""; + str.append(empty); + EXPECT_STREQ("foo", str.c_str()); + } + // Chaining + { + CString str; + str.append("foo").append("bar").append(CString("baz")); + EXPECT_STREQ("foobarbaz", str.c_str()); + } + // r-value appends + { + CString str = CString("foo").append("bar"); + EXPECT_STREQ("foobar", str.c_str()); + } +} + +TEST(CString, Insert) { + // with CString + { + CString str("foobaz"); + str.insert(3, CString("bar")); + EXPECT_STREQ("foobarbaz", str.c_str()); + } + // with string literal + { + CString str("world"); + str.insert(0, "hello "); + EXPECT_STREQ("hello world", str.c_str()); + } + // with const char* + { + CString str("foo"); + const char* bar = "bar"; + str.insert(3, bar); + EXPECT_STREQ("foobar", str.c_str()); + } + // with nullptr + { + CString str("foo"); + const char* bar = nullptr; + str.insert(1, bar); + EXPECT_STREQ("foo", str.c_str()); + } + // r-value insert + { + const char* bar = "bar"; + CString str = CString("foo").insert(3, bar); + EXPECT_STREQ("foobar", str.c_str()); + } } TEST(CString, Replace) { - { - CString str("foo bar baz"); - str.replace(0, 0, CString("lkj")); - EXPECT_STREQ("lkjfoo bar baz", str.c_str()); - } + // with CString { CString str("foo bar baz"); str.replace(4, 3, CString("dpa")); EXPECT_STREQ("foo dpa baz", str.c_str()); } + // with string literal { CString str("foo bar baz"); - str.replace(4, 3, CString("")); + str.replace(4, 3, "dpa"); + EXPECT_STREQ("foo dpa baz", str.c_str()); + } + // with const char* + { + CString str("foo bar baz"); + const char* dpa = "dpa"; + str.replace(4, 3, dpa); + EXPECT_STREQ("foo dpa baz", str.c_str()); + } + // with nullptr + { + CString str("foo bar baz"); + const char* dpa = nullptr; + str.replace(4, 3, dpa); EXPECT_STREQ("foo baz", str.c_str()); } + // with empty string { CString str("foo bar baz"); - str.replace(4, 3, CString("a")); - EXPECT_STREQ("foo a baz", str.c_str()); + str.replace(4, 3, ""); + EXPECT_STREQ("foo baz", str.c_str()); } + // replace that grows the string { CString str("foo bar baz"); - str.replace(4, 3, CString("abcdef")); + str.replace(4, 3, "abcdef"); EXPECT_STREQ("foo abcdef baz", str.c_str()); } + // replace that shrinks the string { CString str("foo bar baz"); - str.replace(0, 3, CString("abcdef")); - EXPECT_STREQ("abcdef bar baz", str.c_str()); + str.replace(4, 3, "a"); + EXPECT_STREQ("foo a baz", str.c_str()); } + // replace at the beginning { CString str("foo bar baz"); - str.replace(8, 3, CString("abcdef")); - EXPECT_STREQ("foo bar abcdef", str.c_str()); + str.replace(0, 3, "lkj"); + EXPECT_STREQ("lkj bar baz", str.c_str()); } + // replace at the end { CString str("foo bar baz"); - str.replace(0, 11, CString("abcdef")); - EXPECT_STREQ("abcdef", str.c_str()); + str.replace(8, 3, "lkj"); + EXPECT_STREQ("foo bar lkj", str.c_str()); + } + // replace all + { + CString str("foo bar baz"); + str.replace(0, 11, "lkj"); + EXPECT_STREQ("lkj", str.c_str()); + } + // r-value replace + { + const char* dpa = "dpa"; + CString str = CString("foo bar baz").replace(4, 3, dpa); + EXPECT_STREQ("foo dpa baz", str.c_str()); } } @@ -94,6 +457,42 @@ TEST(CString, ReplacePastEndOfString) { } } +TEST(CString, ToString) { + EXPECT_STREQ("0", to_string(0).c_str()); + EXPECT_STREQ("123", to_string(123).c_str()); + EXPECT_STREQ("-456", to_string(-456).c_str()); + EXPECT_STREQ("2147483647", to_string(INT_MAX).c_str()); + EXPECT_STREQ("-2147483648", to_string(INT_MIN).c_str()); + + EXPECT_STREQ("0", to_string(0u).c_str()); + EXPECT_STREQ("4294967295", to_string(UINT_MAX).c_str()); + + EXPECT_STREQ("0", to_string((short)0).c_str()); + EXPECT_STREQ("32767", to_string(SHRT_MAX).c_str()); + EXPECT_STREQ("-32768", to_string(SHRT_MIN).c_str()); + + EXPECT_STREQ("0", to_string((unsigned short)0).c_str()); + EXPECT_STREQ("65535", to_string(USHRT_MAX).c_str()); + +#if LONG_MAX == 2147483647 + EXPECT_STREQ("2147483647", to_string(LONG_MAX).c_str()); + EXPECT_STREQ("-2147483648", to_string(LONG_MIN).c_str()); + EXPECT_STREQ("4294967295", to_string(ULONG_MAX).c_str()); +#else + EXPECT_STREQ("9223372036854775807", to_string(LONG_MAX).c_str()); + EXPECT_STREQ("-9223372036854775808", to_string(LONG_MIN).c_str()); + EXPECT_STREQ("18446744073709551615", to_string(ULONG_MAX).c_str()); +#endif + + EXPECT_STREQ("9223372036854775807", to_string(LLONG_MAX).c_str()); + EXPECT_STREQ("-9223372036854775808", to_string(LLONG_MIN).c_str()); + EXPECT_STREQ("18446744073709551615", to_string(ULLONG_MAX).c_str()); + + EXPECT_STREQ("0.000000", to_string(0.0f).c_str()); + EXPECT_STREQ("1.500000", to_string(1.5f).c_str()); + EXPECT_STREQ("-3.140000", to_string(-3.14f).c_str()); +} + TEST(FixedSizeString, EmptyString) { { FixedSizeString<32> str;