From f0fd3b232c4c5fe25a1ebcdd8ea1fb09f48a7369 Mon Sep 17 00:00:00 2001 From: SirLynix Date: Sun, 12 Nov 2023 12:54:34 +0100 Subject: [PATCH] Core: Add generic ParameterConfig This still needs to be improved --- include/Nazara/Core/ParameterFile.hpp | 85 +++++ include/Nazara/Core/ParameterFile.inl | 171 +++++++++ src/Nazara/Core/ParameterFile.cpp | 110 ++++++ .../Formats/PipelinePassListLoader.cpp | 324 +++++------------- 4 files changed, 446 insertions(+), 244 deletions(-) create mode 100644 include/Nazara/Core/ParameterFile.hpp create mode 100644 include/Nazara/Core/ParameterFile.inl create mode 100644 src/Nazara/Core/ParameterFile.cpp diff --git a/include/Nazara/Core/ParameterFile.hpp b/include/Nazara/Core/ParameterFile.hpp new file mode 100644 index 000000000..a20625f4e --- /dev/null +++ b/include/Nazara/Core/ParameterFile.hpp @@ -0,0 +1,85 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_CORE_PARAMETERFILE_HPP +#define NAZARA_CORE_PARAMETERFILE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class Stream; + + class NAZARA_CORE_API ParameterFile + { + public: + inline ParameterFile(Stream& stream); + ParameterFile(const ParameterFile&) = delete; + ParameterFile(ParameterFile&&) = delete; + ~ParameterFile() = default; + + template void Block(Args&&... args); + template void Handle(Args&&... args); + + ParameterFile& operator=(const ParameterFile&) = delete; + ParameterFile& operator=(ParameterFile&&) = delete; + + struct Keyword + { + std::string str; + }; + + struct Array_t {}; + struct OptionalBlock_t {}; + + static constexpr Array_t Array{}; + static constexpr OptionalBlock_t OptionalBlock{}; + + private: + using ValueHandler = std::function; + + struct KeyValue + { + std::string_view key; + ValueHandler handler; + }; + + template static std::string_view BuildBlockKey(T&& key); + template static ValueHandler BuildBlockHandler(ParameterFile& file, ValueHandler handler); + template static ValueHandler BuildBlockHandler(ParameterFile& file, T* value); + template static ValueHandler BuildBlockHandler(ParameterFile& file, T&& handler, std::enable_if_t>* = nullptr); + template static ValueHandler BuildBlockHandler(ParameterFile& file, FunctionRef handler); + + template static void BuildKeyValues(ParameterFile& file, FixedVector& keyValues, K&& key, V&& value, Rest&&... rest); + template static ValueHandler GetSingleHandler(ParameterFile& file, V&& value, Rest&&... rest); + + template void HandleInner(std::string_view listEnd, Args&&... args); + + template static T ReadValue(ParameterFile& file); + + template static constexpr bool ShouldIgnoreImpl = std::is_same_v || std::is_same_v; + template static constexpr bool ShouldIgnore = ShouldIgnoreImpl>>; + + bool EnsureLine(bool peek = false); + std::string ReadKeyword(bool peek = false); + std::string ReadString(); + + std::string m_currentLine; + Stream& m_stream; + }; +} + +#include + +#endif // NAZARA_CORE_PARAMETERFILE_HPP diff --git a/include/Nazara/Core/ParameterFile.inl b/include/Nazara/Core/ParameterFile.inl new file mode 100644 index 000000000..91040cb69 --- /dev/null +++ b/include/Nazara/Core/ParameterFile.inl @@ -0,0 +1,171 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + inline ParameterFile::ParameterFile(Stream& stream) : + m_stream(stream) + { + } + + template + void ParameterFile::Block(Args&&... args) + { + using Types = TypeListTransform, std::remove_const>, std::decay>; + + constexpr bool IsOptionalBlock = TypeListHas; + if constexpr (IsOptionalBlock) + { + std::string nextKeyword = ReadKeyword(true); + if (nextKeyword != "{") + return; + } + + std::string beginToken = ReadKeyword(); + if (beginToken != "{") + throw std::runtime_error(Format("expected \"{{\" token, got {}", beginToken)); + + HandleInner("}", std::forward(args)...); + + std::string endToken = ReadKeyword(); + if (endToken != "}") + throw std::runtime_error(Format("expected \"}}\" token, got {}", endToken)); + } + + template + void ParameterFile::Handle(Args&&... args) + { + HandleInner({}, std::forward(args)...); + } + + template + void ParameterFile::BuildKeyValues(ParameterFile& file, FixedVector& keyValues, K&& key, V&& value, Rest&&... rest) + { + if constexpr (ShouldIgnore) + return BuildKeyValues(file, keyValues, std::forward(value), std::forward(rest)...); + else + { + auto& keyValue = keyValues.emplace_back(); + keyValue.key = BuildBlockKey(std::forward(key)); + keyValue.handler = BuildBlockHandler(file, std::forward(value)); + + if constexpr (sizeof...(Rest) > 0) + BuildKeyValues(file, keyValues, std::forward(rest)...); + } + } + + template + auto ParameterFile::GetSingleHandler(ParameterFile& file, V&& value, Rest&&... rest) -> ValueHandler + { + if constexpr (ShouldIgnore) + { + static_assert(sizeof...(Rest) > 0, "expected a handler"); + return GetSingleHandler(file, std::forward(rest)...); + } + else + { + static_assert(sizeof...(Rest) == 0, "expected a single handler"); + return BuildBlockHandler(file, std::forward(value)); + } + } + + template + std::string_view ParameterFile::BuildBlockKey(T&& key) + { + static_assert(std::is_constructible_v, "parameter key must be convertible to a std::string_view"); + return std::string_view(std::forward(key)); + } + + template + auto ParameterFile::BuildBlockHandler(ParameterFile& /*file*/, ValueHandler handler) -> ValueHandler + { + return handler; + } + + template + auto ParameterFile::BuildBlockHandler(ParameterFile& /*file*/, T* value) -> ValueHandler + { + return [value](ParameterFile& file) + { + *value = ReadValue(file); + }; + } + + template + auto ParameterFile::BuildBlockHandler(ParameterFile& file, T&& handler, std::enable_if_t>*) -> ValueHandler + { + using FunctionType = typename FunctionTraits::FuncType; + return BuildBlockHandler(file, FunctionRef(handler)); + } + + template + auto ParameterFile::BuildBlockHandler(ParameterFile& /*file*/, FunctionRef handler) -> ValueHandler + { + return [handler](ParameterFile& file) + { + std::tuple args{ ReadValue(file)... }; + std::apply(handler, std::move(args)); + }; + } + + template + void ParameterFile::HandleInner(std::string_view listEnd, Args&&... args) + { + using Types = TypeListTransform, std::remove_const>, std::decay>; + constexpr bool IsArray = TypeListHas; + + if constexpr (IsArray) + { + ValueHandler handler = GetSingleHandler(*this, std::forward(args)...); + + for (;;) + { + std::string nextKeyword = ReadKeyword(true); + if (nextKeyword == listEnd) + break; + + handler(*this); + } + } + else + { + FixedVector keys; + BuildKeyValues(*this, keys, std::forward(args)...); + + for (;;) + { + std::string nextKeyword = ReadKeyword(true); + if (nextKeyword == listEnd) + break; + + auto it = std::find_if(keys.begin(), keys.end(), [nextKeyword = ReadKeyword()](const KeyValue& keyValue) { return keyValue.key == nextKeyword; }); + if (it == keys.end()) + throw std::runtime_error(Format("unexpected keyword \"{}\"", nextKeyword)); + + const ValueHandler& handler = it->handler; + handler(*this); + } + } + } + + template + T ParameterFile::ReadValue(ParameterFile& file) + { + if constexpr (std::is_same_v) + return file.ReadString(); + else if constexpr (std::is_same_v) + return Keyword{ file.ReadKeyword() }; + else + static_assert(AlwaysFalse(), "unsupported type"); + } +} + +#include diff --git a/src/Nazara/Core/ParameterFile.cpp b/src/Nazara/Core/ParameterFile.cpp new file mode 100644 index 000000000..7ec181516 --- /dev/null +++ b/src/Nazara/Core/ParameterFile.cpp @@ -0,0 +1,110 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +namespace Nz +{ + bool ParameterFile::EnsureLine(bool peek) + { + while (m_currentLine.find_first_not_of(" \r\t\n") == m_currentLine.npos) + { + m_currentLine.clear(); + m_stream.ReadLine(m_currentLine); + if (m_currentLine.empty()) + { + if (!peek) + throw std::runtime_error("unexpected end of file"); + + return false; + } + } + + return true; + } + + std::string ParameterFile::ReadKeyword(bool peek) + { + std::size_t beginOffset; + do + { + if (!EnsureLine(peek)) + return {}; + + beginOffset = m_currentLine.find_first_not_of(" \r\t\n"); + } while (beginOffset == m_currentLine.npos); + + if (m_currentLine[beginOffset] == '"') + throw std::runtime_error("expected a keyword, got a string"); + + std::size_t endOffset = m_currentLine.find_first_of(" \r\t\n", beginOffset + 1); + if (endOffset == m_currentLine.npos) + endOffset = m_currentLine.size(); + + std::string currentToken = std::string(m_currentLine.substr(beginOffset, endOffset - beginOffset)); + if (!peek) + m_currentLine.erase(m_currentLine.begin(), m_currentLine.begin() + endOffset); + + return currentToken; + } + + std::string ParameterFile::ReadString() + { + std::size_t beginOffset; + do + { + EnsureLine(); + beginOffset = m_currentLine.find_first_not_of(" \r\t\n"); + } while (beginOffset == m_currentLine.npos); + + if (m_currentLine[beginOffset] != '"') + throw std::runtime_error("expected a string, got a keyword"); + + std::string str; + for (std::size_t i = beginOffset + 1; i < m_currentLine.size(); ++i) + { + switch (m_currentLine[i]) + { + case '\0': + case '\n': + case '\r': + throw std::runtime_error("expected a string, got a keyword"); + + case '"': + { + m_currentLine.erase(m_currentLine.begin(), m_currentLine.begin() + beginOffset + i); + + return str; + } + + case '\\': + { + i++; + char character; + switch (m_currentLine[i]) + { + case 'n': character = '\n'; break; + case 'r': character = '\r'; break; + case 't': character = '\t'; break; + case '"': character = '"'; break; + case '\\': character = '\\'; break; + default: + throw std::runtime_error(Format("unrecognized character {}", character)); + } + + str.push_back(character); + break; + } + + default: + str.push_back(m_currentLine[i]); + } + } + + throw std::runtime_error("unfinished string"); + } +} diff --git a/src/Nazara/Graphics/Formats/PipelinePassListLoader.cpp b/src/Nazara/Graphics/Formats/PipelinePassListLoader.cpp index dacc6ae3c..93a73a099 100644 --- a/src/Nazara/Graphics/Formats/PipelinePassListLoader.cpp +++ b/src/Nazara/Graphics/Formats/PipelinePassListLoader.cpp @@ -3,6 +3,7 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include #include #include #include @@ -15,7 +16,7 @@ namespace Nz::Loaders { public: PassListLoader(Stream& stream, const PipelinePassListParams& /*parameters*/) : - m_stream(stream) + m_paramFile(stream) { } @@ -23,59 +24,45 @@ namespace Nz::Loaders { try { - ExpectKeyword("passlist"); + std::string finalOutputAttachment; - std::string passListName = ReadString(); + m_paramFile.Handle( + "passlist", [&](std::string passListName) + { + m_current.emplace(); + m_current->passList = std::make_shared(); + m_paramFile.Block( + "attachment", [this](std::string attachmentName) + { + HandleAttachment(std::move(attachmentName)); + }, + "attachmentproxy", [this](std::string proxyName, std::string targetName) + { + HandleAttachmentProxy(std::move(proxyName), std::move(targetName)); + }, + "pass", [this](std::string passName) + { + HandlePass(std::move(passName)); + }, + "output", &finalOutputAttachment + ); + } + ); - m_current.emplace(); - m_current->passList = std::make_shared(); - Block([this] + if (finalOutputAttachment.empty()) { - std::string kw = ReadKeyword(); - if (kw == "attachment") - HandleAttachment(); - else if (kw == "attachmentproxy") - { - std::string proxyName = ReadString(); - std::string targetName = ReadString(); + NazaraError("missing passlist output attachment"); + throw ResourceLoadingError::DecodingError; + } - auto it = m_current->attachmentsByName.find(targetName); - if (it == m_current->attachmentsByName.end()) - { - NazaraErrorFmt("unknown attachment {}", targetName); - throw ResourceLoadingError::DecodingError; - } + auto it = m_current->attachmentsByName.find(finalOutputAttachment); + if (it == m_current->attachmentsByName.end()) + { + NazaraErrorFmt("unknown attachment {}", finalOutputAttachment); + throw ResourceLoadingError::DecodingError; + } - if (m_current->attachmentsByName.find(proxyName) != m_current->attachmentsByName.end()) - { - NazaraErrorFmt("attachment {} already exists", proxyName); - throw ResourceLoadingError::DecodingError; - } - - std::size_t proxyId = m_current->passList->AddAttachmentProxy(proxyName, it->second); - m_current->attachmentsByName.emplace(std::move(proxyName), proxyId); - } - else if (kw == "pass") - HandlePass(); - else if (kw == "output") - { - std::string attachmentName = ReadString(); - - auto it = m_current->attachmentsByName.find(attachmentName); - if (it == m_current->attachmentsByName.end()) - { - NazaraErrorFmt("unknown attachment {}", attachmentName); - throw ResourceLoadingError::DecodingError; - } - - m_current->passList->SetFinalOutput(it->second); - } - else - { - NazaraErrorFmt("unexpected keyword {}", kw); - throw ResourceLoadingError::DecodingError; - } - }); + m_current->passList->SetFinalOutput(it->second); return Ok(std::move(m_current->passList)); } @@ -86,58 +73,13 @@ namespace Nz::Loaders } private: - void Block(const FunctionRef& callback) + void HandleAttachment(std::string attachmentName) { - std::string beginToken = ReadKeyword(); - if (beginToken != "{") - { - NazaraErrorFmt("expected \"{{\" token, got {}", beginToken); - throw ResourceLoadingError::DecodingError; - } - - for (;;) - { - std::string nextKeyword = ReadKeyword(true); - if (nextKeyword == "}") - break; - - callback(); - } - - std::string endToken = ReadKeyword(); - if (endToken != "}") - { - NazaraErrorFmt("expected \"}}\" token, got {}", endToken); - throw ResourceLoadingError::DecodingError; - } - } - - void ExpectKeyword(std::string_view expectedKeyword) - { - std::string keyword = ReadKeyword(); - if (keyword != expectedKeyword) - { - NazaraErrorFmt("expected \"{}\" keyword, got {}", expectedKeyword, keyword); - throw ResourceLoadingError::DecodingError; - } - } - - void HandleAttachment() - { - std::string attachmentName = ReadString(); - std::string format; - Block([&] - { - std::string kw = ReadKeyword(); - if (kw == "format") - format = ReadString(); - else - { - NazaraErrorFmt("unexpected keyword {}", kw); - throw ResourceLoadingError::DecodingError; - } - }); + + m_paramFile.Block( + "format", &format + ); if (format.empty()) { @@ -172,11 +114,28 @@ namespace Nz::Loaders m_current->attachmentsByName.emplace(attachmentName, attachmentId); } - - void HandlePass() - { - std::string passName = ReadString(); + void HandleAttachmentProxy(std::string proxyName, std::string targetName) + { + auto it = m_current->attachmentsByName.find(targetName); + if (it == m_current->attachmentsByName.end()) + { + NazaraErrorFmt("unknown attachment {}", targetName); + throw ResourceLoadingError::DecodingError; + } + + if (m_current->attachmentsByName.find(proxyName) != m_current->attachmentsByName.end()) + { + NazaraErrorFmt("attachment {} already exists", proxyName); + throw ResourceLoadingError::DecodingError; + } + + std::size_t proxyId = m_current->passList->AddAttachmentProxy(proxyName, it->second); + m_current->attachmentsByName.emplace(std::move(proxyName), proxyId); + } + + void HandlePass(std::string passName) + { struct InputOutput { std::string name; @@ -191,58 +150,37 @@ namespace Nz::Loaders std::vector outputs; std::vector flags; - Block([&] - { - std::string kw = ReadKeyword(); - - if (kw == "impl") + m_paramFile.Block( + "depthstencilinput", &depthstencilInput, + "depthstenciloutput", &depthstencilOutput, + "impl", [&](std::string passImpl) { - impl = ReadString(); - - std::string nextKeyword = ReadKeyword(true); - if (nextKeyword == "{") + impl = std::move(passImpl); + m_paramFile.Block(ParameterFile::OptionalBlock, + ParameterFile::Array, [&](ParameterFile::Keyword key, std::string value) { - Block([&] - { - std::string key = ReadKeyword(); - std::string value = ReadString(); - - implConfig.SetParameter(std::move(key), std::move(value)); - }); - } - } - else if (kw == "depthstencilinput") - depthstencilInput = ReadString(); - else if (kw == "depthstenciloutput") - depthstencilOutput = ReadString(); - else if (kw == "flag") - flags.push_back(ReadString()); - else if (kw == "input") + implConfig.SetParameter(std::move(key.str), std::move(value)); + }); + }, + "input", [&](std::string name, std::string attachment) { - std::string name = ReadString(); - std::string attachment = ReadString(); - inputs.push_back({ std::move(name), std::move(attachment), }); - } - else if (kw == "output") + }, + "output", [&](std::string name, std::string attachment) { - std::string name = ReadString(); - std::string attachment = ReadString(); - outputs.push_back({ std::move(name), std::move(attachment), }); - } - else + }, + "flag", [&](std::string flag) { - NazaraErrorFmt("unexpected keyword {}", kw); - throw ResourceLoadingError::DecodingError; + flags.push_back(std::move(flag)); } - }); + ); FramePipelinePassRegistry& passRegistry = Graphics::Instance()->GetFramePipelinePassRegistry(); @@ -329,107 +267,6 @@ namespace Nz::Loaders } } - std::string ReadKeyword(bool peek = false) - { - std::size_t beginOffset; - do - { - EnsureLine(); - beginOffset = m_currentLine.find_first_not_of(" \r\t\n"); - } - while (beginOffset == m_currentLine.npos); - - if (m_currentLine[beginOffset] == '"') - { - NazaraError("expected a keyword, got a string"); - throw ResourceLoadingError::DecodingError; - } - - std::size_t endOffset = m_currentLine.find_first_of(" \r\t\n", beginOffset + 1); - if (endOffset == m_currentLine.npos) - endOffset = m_currentLine.size(); - - std::string currentToken = std::string(m_currentLine.substr(beginOffset, endOffset - beginOffset)); - if (!peek) - m_currentLine.erase(m_currentLine.begin(), m_currentLine.begin() + endOffset); - - return currentToken; - } - - std::string ReadString() - { - std::size_t beginOffset; - do - { - EnsureLine(); - beginOffset = m_currentLine.find_first_not_of(" \r\t\n"); - } - while (beginOffset == m_currentLine.npos); - - if (m_currentLine[beginOffset] != '"') - { - NazaraError("expected a string, got a keyword"); - throw ResourceLoadingError::DecodingError; - } - - std::string str; - for (std::size_t i = beginOffset + 1; i < m_currentLine.size(); ++i) - { - switch (m_currentLine[i]) - { - case '\0': - case '\n': - case '\r': - NazaraError("unfinished string"); - throw ResourceLoadingError::DecodingError; - - case '"': - { - m_currentLine.erase(m_currentLine.begin(), m_currentLine.begin() + beginOffset + i); - - return str; - } - - case '\\': - { - i++; - char character; - switch (m_currentLine[i]) - { - case 'n': character = '\n'; break; - case 'r': character = '\r'; break; - case 't': character = '\t'; break; - case '"': character = '"'; break; - case '\\': character = '\\'; break; - default: - NazaraErrorFmt("unrecognized character {}", character); - throw ResourceLoadingError::DecodingError; - } - - str.push_back(character); - break; - } - - default: - str.push_back(m_currentLine[i]); - } - } - - NazaraError("unfinished string"); - throw ResourceLoadingError::DecodingError; - } - - void EnsureLine() - { - while (m_currentLine.find_first_not_of(" \r\t\n") == m_currentLine.npos) - { - m_currentLine.clear(); - m_stream.ReadLine(m_currentLine); - if (m_currentLine.empty()) - throw ResourceLoadingError::DecodingError; - } - } - struct CurrentPassList { std::shared_ptr passList; @@ -438,8 +275,7 @@ namespace Nz::Loaders std::optional m_current; std::string m_currentLine; - - Stream& m_stream; + ParameterFile m_paramFile; }; }