Core: Add generic ParameterConfig

This still needs to be improved
This commit is contained in:
SirLynix 2023-11-12 12:54:34 +01:00
parent 86e26008b3
commit f0fd3b232c
4 changed files with 446 additions and 244 deletions

View File

@ -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 <NazaraUtils/Prerequisites.hpp>
#include <Nazara/Core/Config.hpp>
#include <NazaraUtils/FixedVector.hpp>
#include <NazaraUtils/FunctionRef.hpp>
#include <NazaraUtils/FunctionTraits.hpp>
#include <NazaraUtils/TypeList.hpp>
#include <functional>
#include <string>
#include <type_traits>
namespace Nz
{
class Stream;
class NAZARA_CORE_API ParameterFile
{
public:
inline ParameterFile(Stream& stream);
ParameterFile(const ParameterFile&) = delete;
ParameterFile(ParameterFile&&) = delete;
~ParameterFile() = default;
template<typename... Args> void Block(Args&&... args);
template<typename... Args> 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<void(ParameterFile& file)>;
struct KeyValue
{
std::string_view key;
ValueHandler handler;
};
template<typename T> static std::string_view BuildBlockKey(T&& key);
template<typename... Args> static ValueHandler BuildBlockHandler(ParameterFile& file, ValueHandler handler);
template<typename T> static ValueHandler BuildBlockHandler(ParameterFile& file, T* value);
template<typename T> static ValueHandler BuildBlockHandler(ParameterFile& file, T&& handler, std::enable_if_t<IsFunctor_v<T>>* = nullptr);
template<typename... Args> static ValueHandler BuildBlockHandler(ParameterFile& file, FunctionRef<void(Args...)> handler);
template<std::size_t N, typename K, typename V, typename... Rest> static void BuildKeyValues(ParameterFile& file, FixedVector<KeyValue, N>& keyValues, K&& key, V&& value, Rest&&... rest);
template<typename V, typename... Rest> static ValueHandler GetSingleHandler(ParameterFile& file, V&& value, Rest&&... rest);
template<typename... Args> void HandleInner(std::string_view listEnd, Args&&... args);
template<typename T> static T ReadValue(ParameterFile& file);
template<typename T> static constexpr bool ShouldIgnoreImpl = std::is_same_v<T, Array_t> || std::is_same_v<T, OptionalBlock_t>;
template<typename T> static constexpr bool ShouldIgnore = ShouldIgnoreImpl<std::decay_t<std::remove_const_t<T>>>;
bool EnsureLine(bool peek = false);
std::string ReadKeyword(bool peek = false);
std::string ReadString();
std::string m_currentLine;
Stream& m_stream;
};
}
#include <Nazara/Core/ParameterFile.inl>
#endif // NAZARA_CORE_PARAMETERFILE_HPP

View File

@ -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 <Nazara/Core/Log.hpp>
#include <array>
#include <stdexcept>
#include <tuple>
#include <utility>
#include <Nazara/Core/Debug.hpp>
namespace Nz
{
inline ParameterFile::ParameterFile(Stream& stream) :
m_stream(stream)
{
}
template<typename... Args>
void ParameterFile::Block(Args&&... args)
{
using Types = TypeListTransform<TypeListTransform<TypeList<Args...>, std::remove_const>, std::decay>;
constexpr bool IsOptionalBlock = TypeListHas<Types, OptionalBlock_t>;
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>(args)...);
std::string endToken = ReadKeyword();
if (endToken != "}")
throw std::runtime_error(Format("expected \"}}\" token, got {}", endToken));
}
template<typename... Args>
void ParameterFile::Handle(Args&&... args)
{
HandleInner({}, std::forward<Args>(args)...);
}
template<std::size_t N, typename K, typename V, typename... Rest>
void ParameterFile::BuildKeyValues(ParameterFile& file, FixedVector<KeyValue, N>& keyValues, K&& key, V&& value, Rest&&... rest)
{
if constexpr (ShouldIgnore<K>)
return BuildKeyValues(file, keyValues, std::forward<V>(value), std::forward<Rest>(rest)...);
else
{
auto& keyValue = keyValues.emplace_back();
keyValue.key = BuildBlockKey(std::forward<K>(key));
keyValue.handler = BuildBlockHandler(file, std::forward<V>(value));
if constexpr (sizeof...(Rest) > 0)
BuildKeyValues(file, keyValues, std::forward<Rest>(rest)...);
}
}
template<typename V, typename ...Rest>
auto ParameterFile::GetSingleHandler(ParameterFile& file, V&& value, Rest&&... rest) -> ValueHandler
{
if constexpr (ShouldIgnore<V>)
{
static_assert(sizeof...(Rest) > 0, "expected a handler");
return GetSingleHandler(file, std::forward<Rest>(rest)...);
}
else
{
static_assert(sizeof...(Rest) == 0, "expected a single handler");
return BuildBlockHandler(file, std::forward<V>(value));
}
}
template<typename T>
std::string_view ParameterFile::BuildBlockKey(T&& key)
{
static_assert(std::is_constructible_v<std::string_view, T>, "parameter key must be convertible to a std::string_view");
return std::string_view(std::forward<T>(key));
}
template<typename... Args>
auto ParameterFile::BuildBlockHandler(ParameterFile& /*file*/, ValueHandler handler) -> ValueHandler
{
return handler;
}
template<typename T>
auto ParameterFile::BuildBlockHandler(ParameterFile& /*file*/, T* value) -> ValueHandler
{
return [value](ParameterFile& file)
{
*value = ReadValue<T>(file);
};
}
template<typename T>
auto ParameterFile::BuildBlockHandler(ParameterFile& file, T&& handler, std::enable_if_t<IsFunctor_v<T>>*) -> ValueHandler
{
using FunctionType = typename FunctionTraits<T>::FuncType;
return BuildBlockHandler(file, FunctionRef<FunctionType>(handler));
}
template<typename... Args>
auto ParameterFile::BuildBlockHandler(ParameterFile& /*file*/, FunctionRef<void(Args...)> handler) -> ValueHandler
{
return [handler](ParameterFile& file)
{
std::tuple<Args...> args{ ReadValue<Args>(file)... };
std::apply(handler, std::move(args));
};
}
template<typename... Args>
void ParameterFile::HandleInner(std::string_view listEnd, Args&&... args)
{
using Types = TypeListTransform<TypeListTransform<TypeList<Args...>, std::remove_const>, std::decay>;
constexpr bool IsArray = TypeListHas<Types, Array_t>;
if constexpr (IsArray)
{
ValueHandler handler = GetSingleHandler(*this, std::forward<Args>(args)...);
for (;;)
{
std::string nextKeyword = ReadKeyword(true);
if (nextKeyword == listEnd)
break;
handler(*this);
}
}
else
{
FixedVector<KeyValue, sizeof...(Args) / 2> keys;
BuildKeyValues(*this, keys, std::forward<Args>(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<typename T>
T ParameterFile::ReadValue(ParameterFile& file)
{
if constexpr (std::is_same_v<T, std::string>)
return file.ReadString();
else if constexpr (std::is_same_v<T, Keyword>)
return Keyword{ file.ReadKeyword() };
else
static_assert(AlwaysFalse<T>(), "unsupported type");
}
}
#include <Nazara/Core/DebugOff.hpp>

View File

@ -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 <Nazara/Core/ParameterFile.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/Stream.hpp>
#include <Nazara/Core/Debug.hpp>
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");
}
}

View File

@ -3,6 +3,7 @@
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/Formats/PipelinePassListLoader.hpp>
#include <Nazara/Core/ParameterFile.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <optional>
#include <Nazara/Graphics/Debug.hpp>
@ -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<PipelinePassList>();
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<PipelinePassList>();
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<void()>& 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<InputOutput> outputs;
std::vector<std::string> 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<PipelinePassList> passList;
@ -438,8 +275,7 @@ namespace Nz::Loaders
std::optional<CurrentPassList> m_current;
std::string m_currentLine;
Stream& m_stream;
ParameterFile m_paramFile;
};
}