Created
December 15, 2023 19:02
-
-
Save bryceschober/7cb64eec96c6043f3e98f1e7a9eb174c to your computer and use it in GitHub Desktop.
compile-init-build single-header build cib.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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. | |
*/ | |
/* | |
* cib - Compile-time Initialization and Build | |
* v1.7.0-335-g822b06d | |
* | |
* For the documentation and the latest version, see the official github repo: | |
* https://github.com/intel/compile-time-init-build | |
*/ | |
#include <algorithm> | |
#include <array> | |
#include <boost/mp11/algorithm.hpp> | |
#include <boost/mp11/list.hpp> | |
#include <conc/concurrency.hpp> | |
#include <concepts> | |
#include <cstddef> | |
#include <cstdint> | |
#include <fmt/compile.h> | |
#include <fmt/format.h> | |
#include <iterator> | |
#include <limits> | |
#include <optional> | |
#include <span> | |
#include <stdx/compiler.hpp> | |
#include <stdx/concepts.hpp> | |
#include <stdx/ct_conversions.hpp> | |
#include <stdx/ct_string.hpp> | |
#include <stdx/cx_multimap.hpp> | |
#include <stdx/cx_set.hpp> | |
#include <stdx/cx_vector.hpp> | |
#include <stdx/panic.hpp> | |
#include <stdx/tuple.hpp> | |
#include <stdx/tuple_algorithms.hpp> | |
#include <stdx/type_traits.hpp> | |
#include <stdx/utility.hpp> | |
#include <string_view> | |
#include <type_traits> | |
#include <utility> | |
namespace cib { | |
/** | |
* Describe a builder to cib. | |
* | |
* @tparam Builder | |
* The initial builder type cib should use when creating a builder. | |
* This is only the initial type, the Builder::add(...) function | |
* may return a different type and cib will track that correctly. | |
* | |
* @tparam Interface | |
* The type-erased interface services built with this builder | |
* will implement. For example, cib::callback allows many other | |
* callables to get executed when its service gets invoked. The | |
* type-erased interface for cib::callback is a function pointer. | |
* | |
* @see cib::built | |
* | |
* @example cib::callback_meta | |
*/ | |
template <typename Builder, typename Interface> struct builder_meta { | |
using builder_t = Builder; | |
using interface_t = Interface; | |
}; | |
template <typename BuilderMeta> | |
using builder_t = typename BuilderMeta::builder_t; | |
template <typename BuilderMeta> | |
using interface_t = typename BuilderMeta::interface_t; | |
} // namespace cib | |
namespace cib { | |
/** | |
* Pointer to a built service implementation. | |
* | |
* @tparam ServiceMeta | |
* Tag name of the service. | |
* | |
* @see cib::builder_meta | |
*/ | |
template <typename ServiceMeta> interface_t<ServiceMeta> service; | |
} // namespace cib | |
namespace cib { | |
/** | |
* Builder for simple callbacks. | |
* | |
* Components can add their own callback function to this builder to be | |
* executed when the service is executed with the same function arguments. | |
* | |
* @tparam NumFuncs | |
* The number of functions currently registered with this builder. | |
* | |
* @tparam ArgTypes | |
* List of argument types that must be passed into the callback when it is | |
* invoked. | |
* | |
* @see cib::callback_meta | |
*/ | |
template <int NumFuncs = 0, typename... ArgTypes> struct callback { | |
using func_ptr_t = void (*)(ArgTypes...); | |
std::array<func_ptr_t, NumFuncs> funcs{}; | |
/** | |
* Add a function to be executed when the callback service is invoked. | |
* | |
* Do not call this function directly. The library will add functions | |
* to service builders based on a project's cib::config and cib::extend | |
* declarations. | |
* | |
* @return | |
* A version of this callback builder with the addition of func. | |
* | |
* @see cib::extend | |
* @see cib::nexus | |
*/ | |
template <std::convertible_to<func_ptr_t>... Fs> | |
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) | |
[[nodiscard]] constexpr auto add(Fs &&...fs) const { | |
return [&]<std::size_t... Is>(std::index_sequence<Is...>) { | |
return callback<NumFuncs + sizeof...(Fs), ArgTypes...>{ | |
{funcs[Is]..., std::forward<Fs>(fs)...}}; | |
}(std::make_index_sequence<NumFuncs>{}); | |
} | |
/** | |
* Build and return a function pointer to the implemented callback | |
* builder. Used by cib nexus to automatically build an initialized | |
* builder. | |
* | |
* Do not call directly. | |
* | |
* @tparam BuilderValue | |
* Struct that contains a "static constexpr auto value" field with the | |
* initialized builder. | |
* | |
* @return | |
* Function pointer to the implemented callback service. | |
*/ | |
template <typename BuilderValue> | |
[[nodiscard]] CONSTEVAL static auto build() { | |
return run<BuilderValue>; | |
} | |
private: | |
/** | |
* Runtime implementation of a callback service. | |
* | |
* Calls each registered function in an undefined order. The order | |
* functions are called should not be depended upon and could | |
* change from one release to the next. | |
* | |
* This function will be available from nexus::builder<...> or | |
* cib::built<...>. | |
* | |
* @tparam BuilderValue | |
* A type that contains a constexpr static value field with the | |
* fully initialized callback builder. | |
* | |
* @param args | |
* The arguments to be passed to every registered function. | |
* | |
* @see cib::nexus | |
* @see cib::built | |
*/ | |
template <typename BuilderValue> static void run(ArgTypes... args) { | |
constexpr auto handler_builder = BuilderValue::value; | |
[&]<std::size_t... Is>(std::index_sequence<Is...>) { | |
(handler_builder.funcs[Is](args...), ...); | |
}(std::make_index_sequence<NumFuncs>{}); | |
} | |
}; | |
/** | |
* Extend this to create named callback services. | |
* | |
* Types that extend callback_meta can be used as unique names with | |
* cib::exports and cib::extend. | |
* | |
* @tparam ArgTypes | |
* The function arguments that must be passed into the callback | |
* services implementation. any_t function registered with this | |
* callback service must also have a compatible signature. | |
* | |
* @see cib::exports | |
* @see cib::extend | |
*/ | |
template <typename... ArgTypes> | |
struct callback_meta : public cib::builder_meta<callback<0, ArgTypes...>, | |
void (*)(ArgTypes...)> {}; | |
} // namespace cib | |
namespace cib::detail { | |
struct config_item { | |
template <typename... Args> | |
[[nodiscard]] constexpr auto extends_tuple(Args const &...) const { | |
return stdx::make_tuple(); | |
} | |
template <typename... InitArgs> | |
[[nodiscard]] constexpr auto exports_tuple(InitArgs const &...) const { | |
return stdx::make_tuple(); | |
} | |
}; | |
} // namespace cib::detail | |
namespace cib::detail { | |
template <typename... Components> | |
struct components : public detail::config_item { | |
template <typename... Args> | |
[[nodiscard]] constexpr auto extends_tuple(Args const &...args) const { | |
return stdx::tuple_cat(Components::config.extends_tuple(args...)...); | |
} | |
template <typename... Args> | |
[[nodiscard]] constexpr auto exports_tuple(Args const &...args) const { | |
return stdx::tuple_cat(Components::config.exports_tuple(args...)...); | |
} | |
}; | |
} // namespace cib::detail | |
namespace cib::detail { | |
template <auto Value> | |
constexpr static auto as_constant_v = | |
std::integral_constant<std::remove_cvref_t<decltype(Value)>, Value>{}; | |
template <auto... Args> struct args { | |
constexpr static auto value = stdx::make_tuple(as_constant_v<Args>...); | |
}; | |
template <typename ConfigArgs, typename... ConfigTs> | |
struct config : public detail::config_item { | |
stdx::tuple<ConfigTs...> configs_tuple; | |
CONSTEVAL explicit config(ConfigArgs, ConfigTs const &...configs) | |
: configs_tuple{configs...} {} | |
template <typename... Args> | |
[[nodiscard]] constexpr auto extends_tuple(Args const &...args) const { | |
return ConfigArgs::value.apply([&](auto const &...config_args) { | |
return configs_tuple.apply([&](auto const &...configs_pack) { | |
return stdx::tuple_cat( | |
configs_pack.extends_tuple(args..., config_args...)...); | |
}); | |
}); | |
} | |
template <typename... Args> | |
[[nodiscard]] constexpr auto exports_tuple(Args const &...args) const { | |
return ConfigArgs::value.apply([&](auto const &...config_args) { | |
return configs_tuple.apply([&](auto const &...configs_pack) { | |
return stdx::tuple_cat( | |
configs_pack.exports_tuple(args..., config_args...)...); | |
}); | |
}); | |
} | |
}; | |
} // namespace cib::detail | |
namespace cib::detail { | |
template <typename Pred, typename... Configs> | |
requires std::is_default_constructible_v<Pred> | |
struct conditional : config_item { | |
detail::config<detail::args<>, Configs...> body; | |
CONSTEVAL explicit conditional(Configs const &...configs) | |
: body{{}, configs...} {} | |
template <typename... Args> | |
[[nodiscard]] constexpr auto extends_tuple(Args const &...) const { | |
if constexpr (Pred{}(Args{}...)) { | |
return body.extends_tuple(Args{}...); | |
} else { | |
return stdx::tuple<>{}; | |
} | |
} | |
template <typename... Args> | |
[[nodiscard]] constexpr auto exports_tuple(Args const &...) const { | |
if constexpr (Pred{}(Args{}...)) { | |
return body.exports_tuple(Args{}...); | |
} else { | |
return stdx::tuple<>{}; | |
} | |
} | |
}; | |
} // namespace cib::detail | |
namespace cib::detail { | |
template <typename ServiceType, typename... Args> | |
struct extend : public config_item { | |
using service_type = ServiceType; | |
constexpr static auto builder = cib::builder_t<service_type>{}; | |
stdx::tuple<Args...> args_tuple; | |
CONSTEVAL explicit extend(Args const &...args) : args_tuple{args...} {} | |
template <typename... InitArgs> | |
[[nodiscard]] constexpr auto extends_tuple(InitArgs const &...) const { | |
return stdx::make_tuple(*this); | |
} | |
}; | |
} // namespace cib::detail | |
namespace cib::detail { | |
template <typename ServiceT, typename BuilderT> struct service_entry { | |
using Service = ServiceT; | |
BuilderT builder; | |
}; | |
template <typename... Services> struct exports : public detail::config_item { | |
template <typename... InitArgs> | |
[[nodiscard]] constexpr auto extends_tuple(InitArgs const &...) const { | |
return stdx::make_tuple(extend<Services>{}...); | |
} | |
template <typename... InitArgs> | |
[[nodiscard]] constexpr auto exports_tuple(InitArgs const &...) const { | |
return stdx::make_tuple(Services{}...); | |
} | |
}; | |
} // namespace cib::detail | |
namespace cib { | |
/** | |
* List of arguments to configure compile-time initialization of components. | |
* | |
* @see cib::conditional | |
*/ | |
template <auto... Args> constexpr static detail::args<Args...> args{}; | |
/** | |
* Container for project and component configuration declarations. | |
* | |
* Each component or project type must contain a static constexpr "config" | |
* field that contains the cib configuration for that component or project. | |
* cib::config can be used to compose multiple configuration declarations. | |
* | |
* @see cib::components | |
* @see cib::extend | |
* @see cib::exports | |
* @see cib::conditional | |
*/ | |
template <typename... Configs> | |
[[nodiscard]] CONSTEVAL auto config(Configs const &...configs) { | |
return detail::config{args<>, configs...}; | |
} | |
template <auto... Args, typename... Configs> | |
[[nodiscard]] CONSTEVAL auto config(detail::args<Args...> config_args, | |
Configs const &...configs) { | |
return detail::config{config_args, configs...}; | |
} | |
/** | |
* Compose one or more components into a project or larger component. | |
* | |
* @tparam Args | |
* Template instantiation of cib::args filled with compile-time | |
* configurations values. | |
* | |
* @tparam Components | |
* List of components to be added to the configuration. | |
* | |
* @see cib::args | |
*/ | |
template <typename Args, typename... Components> | |
constexpr static detail::components<Args, Components...> components{}; | |
/** | |
* Declare a list of services for use in the project. | |
* | |
* @tparam Services | |
*/ | |
template <typename... Services> | |
constexpr static detail::exports<Services...> exports{}; | |
/** | |
* Extend a service with new functionality. | |
* | |
* @tparam Service | |
* Type name of the service to extend. | |
* | |
* @tparam ServiceTemplateArgs | |
* Template arguments to be passed to the service's | |
* builder add function. | |
* | |
* @param args | |
* Value arguments to be passed to the service's builder add function. | |
*/ | |
template <typename Service, typename... Args> | |
[[nodiscard]] CONSTEVAL auto extend(Args const &...args) { | |
return detail::extend<Service, Args...>{args...}; | |
} | |
/** | |
* Include configs based on predicate. | |
* | |
* If predicate evaluates to true, then the configs will be added to the | |
* configuration. Otherwise the configs contained in this conditional | |
* will not be added. | |
*/ | |
template <typename Predicate, typename... Configs> | |
requires std::is_default_constructible_v<Predicate> | |
[[nodiscard]] CONSTEVAL auto conditional(Predicate const &, | |
Configs const &...configs) { | |
return detail::conditional<Predicate, Configs...>{configs...}; | |
} | |
} // namespace cib | |
namespace cib { | |
template <typename T> using extract_service_tag = typename T::Service; | |
template <typename T> | |
using get_service = typename std::remove_cvref_t<T>::service_type; | |
template <typename T> | |
using get_service_from_tuple = typename std::remove_cvref_t< | |
decltype(std::declval<T>()[stdx::index<0>])>::service_type; | |
template <typename Config> | |
constexpr static auto initialized_builders = transform<extract_service_tag>( | |
[](auto extensions) { | |
using exports_tuple = decltype(Config::config.exports_tuple()); | |
using service = get_service_from_tuple<decltype(extensions)>; | |
static_assert(stdx::contains_type<exports_tuple, service>); | |
constexpr auto initial_builder = extensions[stdx::index<0>].builder; | |
auto built_service = extensions.fold_right( | |
initial_builder, [](auto extension, auto outer_builder) { | |
return extension.args_tuple.apply( | |
[&](auto... args) { return outer_builder.add(args...); }); | |
}); | |
return detail::service_entry<service, decltype(built_service)>{ | |
built_service}; | |
}, | |
chunk_by<get_service>(sort<get_service>(Config::config.extends_tuple()))); | |
template <typename Config, typename Tag> struct initialized { | |
constexpr static auto value = | |
initialized_builders<Config>.get(stdx::tag<Tag>).builder; | |
}; | |
} // namespace cib | |
namespace cib { | |
/** | |
* Combines all components in a single location so their features can | |
* extend services. | |
* | |
* @tparam Config | |
* Project configuration class that contains a single constexpr static | |
* "config" field describing the cib::config | |
* | |
* @see cib::config | |
*/ | |
template <typename Config> struct nexus { | |
private: | |
using this_t = nexus<Config>; | |
// Workaround unfortunate bug in clang where it can't deduce "auto" sometimes | |
#define CIB_BUILD_SERVICE \ | |
initialized<Config, Tag>::value.template build<initialized<Config, Tag>>() | |
public: | |
template <typename Tag> | |
constexpr static decltype(CIB_BUILD_SERVICE) service = CIB_BUILD_SERVICE; | |
#undef CIB_BUILD_SERVICE | |
static void init() { | |
auto const service = []<typename T> { | |
using from_t = std::remove_cvref_t<decltype(this_t::service<T>)>; | |
using to_t = std::remove_cvref_t<decltype(cib::service<T>)>; | |
auto &service_impl = this_t::service<T>; | |
if constexpr (std::is_convertible_v<from_t, to_t>) { | |
cib::service<T> = service_impl; | |
} else { | |
if constexpr (std::is_pointer_v<from_t>) { | |
cib::service<T> = service_impl; | |
} else { | |
cib::service<T> = &service_impl; | |
} | |
} | |
}; | |
initialized_builders<Config>.apply([&]<typename... Ts>(Ts const &...) { | |
(service.template | |
operator()<std::remove_cvref_t<typename Ts::Service>>(), | |
...); | |
}); | |
} | |
}; | |
} // namespace cib | |
namespace flow { | |
using FunctionPtr = auto (*)() -> void; | |
} // namespace flow | |
namespace flow::dsl { | |
template <typename T> | |
concept node = requires { typename stdx::remove_cvref_t<T>::is_node; }; | |
template <typename Source, typename Dest> struct edge { | |
using source_t = Source; | |
using dest_t = Dest; | |
}; | |
constexpr inline class get_initials_t { | |
template <node N> friend constexpr auto tag_invoke(get_initials_t, N &&n) { | |
return stdx::make_tuple(std::forward<N>(n)); | |
} | |
public: | |
template <typename... Ts> | |
constexpr auto operator()(Ts &&...ts) const | |
noexcept(noexcept(tag_invoke(std::declval<get_initials_t>(), | |
std::forward<Ts>(ts)...))) | |
-> decltype(tag_invoke(*this, std::forward<Ts>(ts)...)) { | |
return tag_invoke(*this, std::forward<Ts>(ts)...); | |
} | |
} get_initials{}; | |
constexpr inline class get_finals_t { | |
template <node N> friend constexpr auto tag_invoke(get_finals_t, N &&n) { | |
return stdx::make_tuple(std::forward<N>(n)); | |
} | |
public: | |
template <typename... Ts> | |
constexpr auto operator()(Ts &&...ts) const | |
noexcept(noexcept(tag_invoke(std::declval<get_finals_t>(), | |
std::forward<Ts>(ts)...))) | |
-> decltype(tag_invoke(*this, std::forward<Ts>(ts)...)) { | |
return tag_invoke(*this, std::forward<Ts>(ts)...); | |
} | |
} get_finals{}; | |
constexpr inline class get_nodes_t { | |
template <node N> friend constexpr auto tag_invoke(get_nodes_t, N &&n) { | |
return stdx::make_tuple(std::forward<N>(n)); | |
} | |
public: | |
template <typename... Ts> | |
constexpr auto operator()(Ts &&...ts) const | |
noexcept(noexcept(tag_invoke(std::declval<get_nodes_t>(), | |
std::forward<Ts>(ts)...))) | |
-> decltype(tag_invoke(*this, std::forward<Ts>(ts)...)) { | |
return tag_invoke(*this, std::forward<Ts>(ts)...); | |
} | |
} get_nodes{}; | |
constexpr inline class get_edges_t { | |
friend constexpr auto tag_invoke(get_edges_t, node auto const &) { | |
return stdx::tuple{}; | |
} | |
public: | |
template <typename... Ts> | |
constexpr auto operator()(Ts &&...ts) const | |
noexcept(noexcept(tag_invoke(std::declval<get_edges_t>(), | |
std::forward<Ts>(ts)...))) | |
-> decltype(tag_invoke(*this, std::forward<Ts>(ts)...)) { | |
return tag_invoke(*this, std::forward<Ts>(ts)...); | |
} | |
} get_edges{}; | |
} // namespace flow::dsl | |
namespace logging { | |
// enum assignment is according to Mipi_Sys-T Severity definition | |
enum level { | |
MAX = 0, | |
FATAL = 1, | |
ERROR = 2, | |
WARN = 3, | |
INFO = 4, | |
USER1 = 5, | |
USER2 = 6, | |
TRACE = 7 | |
}; | |
[[nodiscard]] constexpr auto to_text(level l) -> std::string_view { | |
switch (l) { | |
case level::TRACE: | |
return "TRACE"; | |
case level::INFO: | |
return "INFO"; | |
case level::WARN: | |
return "WARN"; | |
case level::ERROR: | |
return "ERROR"; | |
case level::FATAL: | |
return "FATAL"; | |
default: | |
return "UNKNOWN"; | |
} | |
} | |
template <level L> struct level_constant : std::integral_constant<level, L> {}; | |
} // namespace logging | |
namespace sc { | |
template <int value> constexpr static std::integral_constant<int, value> int_{}; | |
template <unsigned int value> | |
constexpr static std::integral_constant<unsigned int, value> uint_{}; | |
template <bool value> | |
constexpr static std::integral_constant<bool, value> bool_{}; | |
template <char value> | |
constexpr static std::integral_constant<char, value> char_{}; | |
template <auto enumValue> | |
constexpr static std::integral_constant<decltype(enumValue), enumValue> enum_{}; | |
template <typename T> struct type_name { | |
constexpr explicit type_name(T) {} | |
constexpr type_name() = default; | |
}; | |
template <typename T> constexpr static type_name<T> type_{}; | |
template <typename CharT, CharT... chars> struct string_constant; | |
} // namespace sc | |
template <class T, T... chars> | |
constexpr auto operator""_sc() -> sc::string_constant<T, chars...> { | |
return {}; | |
} | |
namespace sc { | |
template <typename StringConstant, typename ArgTuple> | |
struct lazy_string_format { | |
constexpr static StringConstant str{}; | |
ArgTuple args{}; | |
constexpr lazy_string_format() = default; | |
constexpr lazy_string_format(StringConstant, ArgTuple newArgs) | |
: args{newArgs} {} | |
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) | |
template <typename F> constexpr auto apply(F &&f) const { | |
return args.apply( | |
[&](auto const &...as) { return std::forward<F>(f)(str, as...); }); | |
} | |
}; | |
template <class CharT, CharT... charsLhs, typename ArgsTupleLhs, | |
CharT... charsRhs, typename ArgsTupleRhs> | |
[[nodiscard]] constexpr auto operator==( | |
lazy_string_format<string_constant<CharT, charsLhs...>, ArgsTupleLhs> lhs, | |
lazy_string_format<string_constant<CharT, charsRhs...>, ArgsTupleRhs> | |
rhs) noexcept -> bool { | |
return (lhs.str == rhs.str) && (lhs.args == rhs.args); | |
} | |
template <typename StringConstantLhs, typename TupleArgsLhs, | |
typename StringConstantRhs, typename TupleArgsRhs> | |
[[nodiscard]] constexpr auto | |
operator+(lazy_string_format<StringConstantLhs, TupleArgsLhs> lhs, | |
lazy_string_format<StringConstantRhs, TupleArgsRhs> rhs) noexcept { | |
return lazy_string_format{lhs.str + rhs.str, | |
stdx::tuple_cat(lhs.args, rhs.args)}; | |
} | |
template <typename StringConstantLhs, typename TupleArgsLhs, typename CharT, | |
CharT... chars> | |
[[nodiscard]] constexpr auto | |
operator+(lazy_string_format<StringConstantLhs, TupleArgsLhs> lhs, | |
string_constant<CharT, chars...> rhs) noexcept { | |
return lazy_string_format{lhs.str + rhs, lhs.args}; | |
} | |
template <typename CharT, CharT... chars, typename StringConstantRhs, | |
typename TupleArgsRhs> | |
[[nodiscard]] constexpr auto | |
operator+(string_constant<CharT, chars...> lhs, | |
lazy_string_format<StringConstantRhs, TupleArgsRhs> rhs) noexcept { | |
return lazy_string_format{lhs + rhs.str, rhs.args}; | |
} | |
} // namespace sc | |
namespace sc { | |
template <typename CharT, CharT... chars> struct string_constant { | |
private: | |
using view_t = std::basic_string_view<CharT>; | |
constexpr static std::array<CharT, sizeof...(chars)> storage{chars...}; | |
using size_type = int; | |
using const_iterator = typename view_t::const_iterator; | |
constexpr static size_type npos = std::numeric_limits<size_type>::max(); | |
public: | |
constexpr static view_t value{storage.data(), sizeof...(chars)}; | |
constexpr static auto begin() noexcept { return std::cbegin(storage); } | |
constexpr static auto end() noexcept { return std::cend(storage); } | |
[[nodiscard]] constexpr static auto size() noexcept { | |
return std::size(storage); | |
} | |
template <size_type pos = 0, size_type count = npos> | |
[[nodiscard]] constexpr static auto | |
substr(std::integral_constant<size_type, pos>, | |
std::integral_constant<size_type, count> = {}) { | |
constexpr size_type sz = count == npos ? size() - pos : count; | |
return [&]<size_type... Is>(std::integer_sequence<size_type, Is...>) { | |
return string_constant<CharT, storage[pos + Is]...>{}; | |
}(std::make_integer_sequence<size_type, sz>{}); | |
} | |
template <typename F> constexpr auto apply(F &&f) const { | |
return std::forward<F>(f)(*this); | |
} | |
}; | |
template <class CharT, CharT... charsLhs, CharT... charsRhs> | |
[[nodiscard]] constexpr auto | |
operator==(string_constant<CharT, charsLhs...>, | |
string_constant<CharT, charsRhs...>) noexcept -> bool { | |
return false; | |
} | |
template <class CharT, CharT... chars> | |
[[nodiscard]] constexpr auto | |
operator==(string_constant<CharT, chars...>, | |
string_constant<CharT, chars...>) noexcept -> bool { | |
return true; | |
} | |
#if __cpp_lib_three_way_comparison < 201907L | |
template <class CharT, CharT... charsLhs, CharT... charsRhs> | |
[[nodiscard]] constexpr auto | |
operator!=(string_constant<CharT, charsLhs...> lhs, | |
string_constant<CharT, charsRhs...> rhs) noexcept -> bool { | |
return not(lhs == rhs); | |
} | |
template <class CharT, CharT... charsLhs, CharT... charsRhs> | |
[[nodiscard]] constexpr auto | |
operator<(string_constant<CharT, charsLhs...> lhs, | |
string_constant<CharT, charsRhs...> rhs) noexcept -> bool { | |
return lhs.value < rhs.value; | |
} | |
template <class CharT, CharT... charsLhs, CharT... charsRhs> | |
[[nodiscard]] constexpr auto | |
operator>(string_constant<CharT, charsLhs...> lhs, | |
string_constant<CharT, charsRhs...> rhs) noexcept -> bool { | |
return lhs.value > rhs.value; | |
} | |
template <class CharT, CharT... charsLhs, CharT... charsRhs> | |
[[nodiscard]] constexpr auto | |
operator<=(string_constant<CharT, charsLhs...> lhs, | |
string_constant<CharT, charsRhs...> rhs) noexcept -> bool { | |
return lhs.value <= rhs.value; | |
} | |
template <class CharT, CharT... charsLhs, CharT... charsRhs> | |
[[nodiscard]] constexpr auto | |
operator>=(string_constant<CharT, charsLhs...> lhs, | |
string_constant<CharT, charsRhs...> rhs) noexcept -> bool { | |
return lhs.value >= rhs.value; | |
} | |
#else | |
template <class CharT, CharT... charsLhs, CharT... charsRhs> | |
[[nodiscard]] constexpr auto | |
operator<=>(string_constant<CharT, charsLhs...> lhs, | |
string_constant<CharT, charsRhs...> rhs) noexcept { | |
return lhs.value <=> rhs.value; | |
} | |
#endif | |
template <class CharT, CharT... charsLhs, CharT... charsRhs> | |
[[nodiscard]] constexpr auto | |
operator+(string_constant<CharT, charsLhs...>, | |
string_constant<CharT, charsRhs...>) noexcept | |
-> string_constant<CharT, charsLhs..., charsRhs...> { | |
return {}; | |
} | |
} // namespace sc | |
namespace sc { | |
namespace detail { | |
template <typename T> | |
concept compile_time_field = requires { T::value; }; | |
template <compile_time_field T> [[nodiscard]] CONSTEVAL auto field_value(T) { | |
if constexpr (std::is_enum_v<decltype(T::value)>) { | |
return stdx::enum_as_string<T::value>(); | |
} else { | |
return T::value; | |
} | |
} | |
template <typename T> | |
[[nodiscard]] CONSTEVAL auto field_value(sc::type_name<T>) { | |
return stdx::type_as_string<T>(); | |
} | |
template <typename Fmt, typename Arg> constexpr auto format1(Fmt, Arg arg) { | |
constexpr auto str = [&] { | |
constexpr auto fmtstr = FMT_COMPILE(Fmt::value); | |
constexpr auto sz = fmt::formatted_size(fmtstr, field_value(arg)); | |
std::array<char, sz> buf{}; | |
fmt::format_to(std::begin(buf), fmtstr, field_value(arg)); | |
return buf; | |
}(); | |
return [&]<std::size_t... Is>(std::index_sequence<Is...>) { | |
return string_constant<char, str[Is]...>{}; | |
}(std::make_index_sequence<std::size(str)>{}); | |
} | |
template <typename Fmt> constexpr auto split_format_spec() { | |
constexpr Fmt fmt{}; | |
constexpr auto spec_start = std::adjacent_find( | |
std::begin(fmt), std::end(fmt), | |
[](auto c1, auto c2) { return c1 == '{' and c2 != '{'; }); | |
if constexpr (spec_start == std::end(fmt)) { | |
return std::pair{fmt, ""_sc}; | |
} else { | |
constexpr auto spec_end = std::find_if(spec_start, std::end(fmt), | |
[](auto c) { return c == '}'; }); | |
constexpr auto len = std::distance(std::begin(fmt), spec_end) + 1; | |
return std::pair{fmt.substr(int_<0>, int_<len>), fmt.substr(int_<len>)}; | |
} | |
} | |
template <typename Str, typename Fmt, typename RuntimeTuple, typename Arg> | |
constexpr auto process_arg(stdx::tuple<Str, Fmt, RuntimeTuple> t, Arg arg) { | |
using namespace stdx::literals; | |
constexpr auto p = split_format_spec<Fmt>(); | |
if constexpr (requires { field_value(arg); }) { | |
return stdx::make_tuple(t[0_idx] + format1(p.first, arg), p.second, | |
t[2_idx]); | |
} else if constexpr (requires { arg.args; }) { | |
return stdx::make_tuple(t[0_idx] + format1(p.first, arg.str), p.second, | |
stdx::tuple_cat(t[2_idx], arg.args)); | |
} else { | |
return stdx::make_tuple( | |
t[0_idx] + p.first, p.second, | |
stdx::tuple_cat(t[2_idx], stdx::make_tuple(arg))); | |
} | |
} | |
} // namespace detail | |
template <typename Fmt, typename... Args> | |
constexpr auto format(Fmt, Args... args) { | |
using namespace stdx::literals; | |
auto t = stdx::make_tuple(args...); | |
auto r = | |
t.fold_left(stdx::make_tuple(""_sc, Fmt{}, stdx::tuple{}), | |
[](auto x, auto y) { return detail::process_arg(x, y); }); | |
if constexpr (r[2_idx].size() == 0) { | |
return r[0_idx] + r[1_idx]; | |
} else { | |
return lazy_string_format{r[0_idx] + r[1_idx], r[2_idx]}; | |
} | |
} | |
template <typename T> struct formatter { | |
constexpr explicit formatter(T) {} | |
template <typename... Ts> constexpr auto operator()(Ts &&...args) { | |
return format(T{}, std::forward<Ts>(args)...); | |
} | |
}; | |
} // namespace sc | |
namespace logging { | |
namespace null { | |
struct config { | |
struct { | |
template <level L> | |
constexpr auto log(auto &&...) const noexcept -> void {} | |
} logger; | |
}; | |
} // namespace null | |
template <typename...> inline auto config = null::config{}; | |
template <typename T> | |
concept loggable = requires(T const &t) { | |
t.apply([]<typename StringType>(StringType, auto const &...) {}); | |
}; | |
template <level L, typename... Ts, typename... TArgs> | |
static auto log(TArgs &&...args) -> void { | |
auto &cfg = config<Ts...>; | |
cfg.logger.template log<L>(std::forward<TArgs>(args)...); | |
} | |
} // namespace logging | |
#define CIB_LOG(LEVEL, MSG, ...) \ | |
logging::log<LEVEL>(__FILE__, __LINE__, \ | |
sc::formatter{MSG##_sc}(__VA_ARGS__)) | |
#define CIB_TRACE(...) CIB_LOG(logging::level::TRACE, __VA_ARGS__) | |
#define CIB_INFO(...) CIB_LOG(logging::level::INFO, __VA_ARGS__) | |
#define CIB_WARN(...) CIB_LOG(logging::level::WARN, __VA_ARGS__) | |
#define CIB_ERROR(...) CIB_LOG(logging::level::ERROR, __VA_ARGS__) | |
#define CIB_FATAL(...) \ | |
(CIB_LOG(logging::level::FATAL, __VA_ARGS__), STDX_PANIC(__VA_ARGS__)) | |
#define CIB_ASSERT(expr) \ | |
((expr) ? void(0) : CIB_FATAL("Assertion failure: " #expr)) | |
namespace flow { | |
struct rt_node { | |
FunctionPtr run; | |
FunctionPtr log_name; | |
private: | |
friend constexpr auto operator==(rt_node const &, rt_node const &) | |
-> bool = default; | |
}; | |
template <stdx::ct_string Name> struct ct_node : rt_node { | |
using is_node = void; | |
using name_t = | |
decltype(stdx::ct_string_to_type<Name, sc::string_constant>()); | |
}; | |
namespace detail { | |
template <stdx::ct_string Name, stdx::ct_string Type, typename F> | |
[[nodiscard]] constexpr auto make_node() { | |
return ct_node<Name>{ | |
{.run = F{}, .log_name = [] { | |
CIB_TRACE("flow.{}({})", | |
stdx::ct_string_to_type<Type, sc::string_constant>(), | |
stdx::ct_string_to_type<Name, sc::string_constant>()); | |
}}}; | |
} | |
} // namespace detail | |
template <stdx::ct_string Name, typename F> | |
requires(stdx::is_function_object_v<F> and std::is_empty_v<F>) | |
[[nodiscard]] constexpr auto action(F const &) { | |
return detail::make_node<Name, "action", F>(); | |
} | |
template <stdx::ct_string Name> [[nodiscard]] constexpr auto milestone() { | |
return detail::make_node<Name, "milestone", decltype([] {})>(); | |
} | |
} // namespace flow | |
namespace flow { | |
// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor) | |
struct interface { | |
virtual auto operator()() const -> void {} | |
}; | |
/** | |
* flow::impl is a constant representation of a series of Milestones and actions | |
* to be executed in a specific order. | |
* | |
* flow::builder allows multiple independent components to collaboratively | |
* specify a flow::impl. Use flow::builder to create Flows. Independent | |
* components can then add their own actions and milestones to a flow::impl | |
* relative to other actions and milestones. | |
* | |
* @tparam Name | |
* Name of flow as a compile-time string. | |
* | |
* @tparam NumSteps | |
* The number of Milestones this flow::impl represents. | |
* | |
* @see flow::builder | |
*/ | |
template <stdx::ct_string Name, std::size_t NumSteps> | |
class impl : public interface { | |
private: | |
constexpr static bool loggingEnabled = not Name.empty(); | |
constexpr static auto capacity = [] { | |
if constexpr (loggingEnabled) { | |
return NumSteps * 2; | |
} else { | |
return NumSteps; | |
} | |
}(); | |
stdx::cx_vector<FunctionPtr, capacity> functionPtrs{}; | |
public: | |
using node_t = rt_node; | |
constexpr static bool active = capacity > 0; | |
/** | |
* Create a new flow::impl of Milestones. | |
* | |
* Do not call this constructor directly, use flow::builder instead. | |
* | |
* @param newMilestones | |
* Array of Milestones to execute in the flow. | |
* | |
* @see flow::builder | |
*/ | |
constexpr explicit(true) impl(std::span<node_t const> newMilestones) { | |
CIB_ASSERT(NumSteps >= std::size(newMilestones)); | |
if constexpr (loggingEnabled) { | |
for (auto const &milestone : newMilestones) { | |
functionPtrs.push_back(milestone.log_name); | |
functionPtrs.push_back(milestone.run); | |
} | |
} else { | |
std::transform(std::cbegin(newMilestones), std::cend(newMilestones), | |
std::back_inserter(functionPtrs), | |
[](auto const &milestone) { return milestone.run; }); | |
} | |
} | |
/** | |
* Execute the entire flow in order. | |
*/ | |
auto operator()() const -> void final { | |
constexpr auto name = | |
stdx::ct_string_to_type<Name, sc::string_constant>(); | |
if constexpr (loggingEnabled) { | |
CIB_TRACE("flow.start({})", name); | |
} | |
for (auto const func : functionPtrs) { | |
func(); | |
} | |
if constexpr (loggingEnabled) { | |
CIB_TRACE("flow.end({})", name); | |
} | |
} | |
}; | |
} // namespace flow | |
namespace flow { | |
[[nodiscard]] constexpr auto edge_size(auto const &nodes, auto const &edges) | |
-> std::size_t { | |
auto const edge_capacities = transform( | |
[&]<typename N>(N const &) { | |
return edges.fold_left( | |
std::size_t{}, []<typename E>(auto acc, E const &) { | |
if constexpr (std::is_same_v<typename E::source_t, N> or | |
std::is_same_v<typename E::dest_t, N>) { | |
return ++acc; | |
} else { | |
return acc; | |
} | |
}); | |
}, | |
nodes); | |
return edge_capacities.fold_left(std::size_t{1}, [](auto acc, auto next) { | |
return std::max(acc, next); | |
}); | |
} | |
template <template <stdx::ct_string, std::size_t> typename Impl> | |
struct graph_builder { | |
template <typename T> using name_for = typename T::name_t; | |
template <typename Node, std::size_t N, std::size_t E> | |
[[nodiscard]] constexpr static auto make_graph(auto const &nodes, | |
auto const &edges) { | |
using graph_t = stdx::cx_multimap<Node, Node, N, E>; | |
graph_t g{}; | |
for_each([&](auto const &node) { g.put(node); }, nodes); | |
auto const named_nodes = stdx::apply_indices<name_for>(nodes); | |
for_each( | |
[&]<typename Edge>(Edge const &) { | |
g.put(get<name_for<typename Edge::source_t>>(named_nodes), | |
get<name_for<typename Edge::dest_t>>(named_nodes)); | |
}, | |
edges); | |
return g; | |
} | |
template <typename Node, typename Graph> | |
[[nodiscard]] constexpr static auto is_source_of(Node const &node, | |
Graph const &g) -> bool { | |
return std::find_if(g.begin(), g.end(), [&](auto const &entry) { | |
return entry.value.contains(node); | |
}) == g.end(); | |
} | |
template <typename Graph> | |
[[nodiscard]] constexpr static auto get_sources(Graph const &g) | |
-> stdx::cx_set<typename Graph::key_type, Graph::capacity()> { | |
stdx::cx_set<typename Graph::key_type, Graph::capacity()> s; | |
for (auto const &entry : g) { | |
s.insert(entry.key); | |
} | |
for (auto const &entry : g) { | |
for (auto const &dst : entry.value) { | |
s.erase(dst); | |
} | |
} | |
return s; | |
} | |
template <typename Output, typename Graph> | |
[[nodiscard]] constexpr static auto topo_sort(Graph &g) | |
-> std::optional<Output> { | |
stdx::cx_vector<typename Graph::key_type, Graph::capacity()> | |
ordered_list{}; | |
auto sources = get_sources(g); | |
while (not sources.empty()) { | |
auto n = sources.pop_back(); | |
ordered_list.push_back(n); | |
if (g.contains(n)) { | |
auto ms = g.get(n); | |
if (ms.empty()) { | |
g.erase(n); | |
} else { | |
for (auto const &entry : ms) { | |
g.erase(n, entry); | |
if (is_source_of(entry, g)) { | |
sources.insert(entry); | |
} | |
} | |
} | |
} | |
} | |
if (not g.empty()) { | |
return {}; | |
} | |
return std::optional<Output>{ | |
std::in_place, | |
std::span{std::cbegin(ordered_list), std::size(ordered_list)}}; | |
} | |
template <typename Graph> | |
[[nodiscard]] constexpr static auto build(Graph const &input) { | |
auto nodes = flow::dsl::get_nodes(input); | |
auto edges = flow::dsl::get_edges(input); | |
constexpr auto node_capacity = stdx::tuple_size_v<decltype(nodes)>; | |
constexpr auto edge_capacity = edge_size(nodes, edges); | |
using output_t = Impl<Graph::name, node_capacity>; | |
using rt_node_t = typename output_t::node_t; | |
static_assert( | |
all_of( | |
[]<typename N>(N const &) { | |
return std::is_convertible_v<N, rt_node_t>; | |
}, | |
nodes), | |
"Output node type is not compatible with given input nodes"); | |
auto g = | |
make_graph<rt_node_t, node_capacity, edge_capacity>(nodes, edges); | |
return topo_sort<output_t>(g); | |
} | |
template <typename Initialized> class built_flow { | |
constexpr static auto built() { | |
constexpr auto v = Initialized::value; | |
constexpr auto built = build(v); | |
static_assert(built.has_value()); | |
return *built; | |
} | |
constexpr static auto run() { built()(); } | |
public: | |
// NOLINTNEXTLINE(google-explicit-constructor) | |
constexpr operator FunctionPtr() const { return run; } | |
constexpr auto operator()() const -> void { run(); } | |
constexpr static bool active = decltype(built())::active; | |
}; | |
template <typename Initialized> | |
[[nodiscard]] constexpr static auto render() -> built_flow<Initialized> { | |
return {}; | |
} | |
}; | |
template <stdx::ct_string Name = "", typename Renderer = graph_builder<impl>, | |
flow::dsl::node... Fragments> | |
class graph { | |
friend constexpr auto tag_invoke(flow::dsl::get_nodes_t, graph const &g) { | |
auto t = g.fragments.apply([](auto const &...frags) { | |
return stdx::tuple_cat(flow::dsl::get_nodes(frags)...); | |
}); | |
return stdx::to_unsorted_set(t); | |
} | |
friend constexpr auto tag_invoke(flow::dsl::get_edges_t, graph const &g) { | |
auto t = g.fragments.apply([](auto const &...frags) { | |
return stdx::tuple_cat(flow::dsl::get_edges(frags)...); | |
}); | |
return stdx::to_unsorted_set(t); | |
} | |
public: | |
template <flow::dsl::node... Ns> | |
[[nodiscard]] constexpr auto add(Ns &&...ns) { | |
return fragments.apply([&](auto &...frags) { | |
return graph<Name, Renderer, Fragments..., | |
stdx::remove_cvref_t<Ns>...>{ | |
{frags..., std::forward<Ns>(ns)...}}; | |
}); | |
} | |
template <typename BuilderValue> | |
[[nodiscard]] constexpr static auto build() { | |
return Renderer::template render<BuilderValue>(); | |
} | |
constexpr static auto name = Name; | |
stdx::tuple<Fragments...> fragments; | |
}; | |
} // namespace flow | |
namespace flow { | |
template <stdx::ct_string Name = ""> | |
using builder = graph<Name, graph_builder<impl>>; | |
template <stdx::ct_string Name = ""> | |
struct service : cib::builder_meta<builder<Name>, FunctionPtr> {}; | |
} // namespace flow | |
namespace flow::dsl { | |
template <node Lhs, node Rhs> struct par { | |
Lhs lhs; | |
Rhs rhs; | |
using is_node = void; | |
private: | |
friend constexpr auto tag_invoke(get_initials_t, par const &p) { | |
return stdx::tuple_cat(get_initials(p.lhs), get_initials(p.rhs)); | |
} | |
friend constexpr auto tag_invoke(get_finals_t, par const &p) { | |
return stdx::tuple_cat(get_finals(p.lhs), get_finals(p.rhs)); | |
} | |
friend constexpr auto tag_invoke(get_nodes_t, par const &p) { | |
return stdx::tuple_cat(get_nodes(p.lhs), get_nodes(p.rhs)); | |
} | |
friend constexpr auto tag_invoke(get_edges_t, par const &p) { | |
return stdx::tuple_cat(get_edges(p.lhs), get_edges(p.rhs)); | |
} | |
}; | |
template <node Lhs, node Rhs> par(Lhs, Rhs) -> par<Lhs, Rhs>; | |
} // namespace flow::dsl | |
template <flow::dsl::node Lhs, flow::dsl::node Rhs> | |
[[nodiscard]] constexpr auto operator&&(Lhs const &lhs, Rhs const &rhs) { | |
return flow::dsl::par{lhs, rhs}; | |
} | |
namespace flow::dsl { | |
template <node Lhs, node Rhs> struct seq { | |
Lhs lhs; | |
Rhs rhs; | |
using is_node = void; | |
private: | |
friend constexpr auto tag_invoke(get_initials_t, seq const &s) { | |
return get_initials(s.lhs); | |
} | |
friend constexpr auto tag_invoke(get_finals_t, seq const &s) { | |
return get_finals(s.rhs); | |
} | |
friend constexpr auto tag_invoke(get_nodes_t, seq const &s) { | |
return stdx::tuple_cat(get_nodes(s.lhs), get_nodes(s.rhs)); | |
} | |
friend constexpr auto tag_invoke(get_edges_t, seq const &s) { | |
auto is = get_initials(s.rhs); | |
auto fs = get_finals(s.lhs); | |
return stdx::tuple_cat( | |
get_edges(s.lhs), get_edges(s.rhs), | |
transform( | |
[]<typename P>(P const &) { | |
return edge<stdx::tuple_element_t<0, P>, | |
stdx::tuple_element_t<1, P>>{}; | |
}, | |
cartesian_product_copy(fs, is))); | |
} | |
}; | |
template <node Lhs, node Rhs> seq(Lhs, Rhs) -> seq<Lhs, Rhs>; | |
} // namespace flow::dsl | |
template <flow::dsl::node Lhs, flow::dsl::node Rhs> | |
[[nodiscard]] constexpr auto operator>>(Lhs const &lhs, Rhs const &rhs) { | |
return flow::dsl::seq{lhs, rhs}; | |
} | |
namespace flow { | |
/** | |
* Run the flow given by 'Tag'. | |
* | |
* @tparam Tag Type of the flow to be ran. This is the name of the flow::builder | |
* used to declare and build the flow. | |
*/ | |
template <typename Tag> FunctionPtr &run = cib::service<Tag>; | |
} // namespace flow | |
namespace cib { | |
/** | |
* Executed immediately after the C++ runtime is stable. This should be | |
* used to initialize essential services like logging and potentially | |
* configure the host system the project is running on. | |
*/ | |
class EarlyRuntimeInit : public flow::service<> {}; | |
/** | |
* Executed once after essential services like logging are initialized. | |
* This can be used for general component runtime initialization. | |
*/ | |
class RuntimeInit : public flow::service<> {}; | |
/** | |
* Executed after all runtime initialization is completed. This is where | |
* the project can start doing its job including enabling interrupts and/or | |
* starting threads if applicable and be ready to start accepting and | |
* processing external events. | |
*/ | |
class RuntimeStart : public flow::service<> {}; | |
/** | |
* Executed repeated in an infinite loop after initialization and | |
* RuntimeStart flows have completed. | |
*/ | |
class MainLoop : public flow::service<> {}; | |
/** | |
* The top object for cib framework. Call 'main' to execute the project. | |
*/ | |
template <typename ProjectConfig> class top { | |
private: | |
struct component { | |
constexpr static auto config = cib::config( | |
cib::exports<EarlyRuntimeInit, RuntimeInit, RuntimeStart, MainLoop>, | |
cib::components<ProjectConfig>); | |
}; | |
constexpr static cib::nexus<component> my_nexus{}; | |
public: | |
/** | |
* Main entry point to cib top. | |
*/ | |
[[noreturn]] inline void main() { | |
my_nexus.init(); | |
flow::run<EarlyRuntimeInit>(); | |
CIB_INFO("cib::top::init() - RuntimeInit"); | |
flow::run<RuntimeInit>(); | |
CIB_INFO("cib::top::init() - RuntimeStart"); | |
flow::run<RuntimeStart>(); | |
while (true) { | |
flow::run<MainLoop>(); | |
} | |
} | |
template <typename ServiceMeta> | |
constexpr static auto get_service() -> auto & { | |
return my_nexus.template service<ServiceMeta>; | |
} | |
}; | |
} // namespace cib | |
namespace interrupt { | |
enum struct irq_num_t : std::uint32_t {}; | |
using priority_t = std::size_t; | |
// NOLINTNEXTLINE(google-runtime-int) | |
CONSTEVAL auto operator""_irq(unsigned long long int v) -> irq_num_t { | |
return static_cast<irq_num_t>(v); | |
} | |
} // namespace interrupt | |
namespace interrupt { | |
template <typename T> | |
concept policy = requires { typename T::policy_type; }; | |
template <typename T> | |
concept status_policy = policy<T> and requires(void (*f)()) { | |
{ T::run(f, f) } -> stdx::same_as<void>; | |
}; | |
struct status_clear_policy; | |
struct clear_status_first { | |
using policy_type = status_clear_policy; | |
static void run(stdx::invocable auto const &clear_status, | |
stdx::invocable auto const &run) { | |
clear_status(); | |
run(); | |
} | |
static_assert(status_policy<clear_status_first>); | |
}; | |
struct clear_status_last { | |
using policy_type = status_clear_policy; | |
static void run(stdx::invocable auto const &clear_status, | |
stdx::invocable auto const &run) { | |
run(); | |
clear_status(); | |
} | |
static_assert(status_policy<clear_status_last>); | |
}; | |
struct dont_clear_status { | |
using policy_type = status_clear_policy; | |
static void run(stdx::invocable auto const &, | |
stdx::invocable auto const &run) { | |
run(); | |
} | |
static_assert(status_policy<dont_clear_status>); | |
}; | |
struct required_resources_policy; | |
template <typename... Resources> struct resource_list {}; | |
template <typename T> | |
concept resources_policy = | |
policy<T> and | |
stdx::is_specialization_of_v<typename T::resources, resource_list>; | |
template <typename... Resources> struct required_resources { | |
using policy_type = required_resources_policy; | |
using resources = resource_list<Resources...>; | |
}; | |
template <typename... Policies> struct policies { | |
template <typename PolicyType, typename Default> | |
constexpr static auto get() { | |
using M = stdx::type_map< | |
stdx::tt_pair<typename Policies::policy_type, Policies>...>; | |
return stdx::type_lookup_t<M, PolicyType, Default>{}; | |
} | |
template <typename PolicyType, typename Default> | |
using type = decltype(get<PolicyType, Default>()); | |
}; | |
} // namespace interrupt | |
namespace interrupt { | |
namespace detail { | |
template <typename T, template <typename...> typename X> | |
concept specializes = stdx::is_specialization_of_v<std::remove_cvref_t<T>, X>; | |
} | |
template <typename T> | |
concept root_config = requires { | |
{ T::children } -> detail::specializes<stdx::tuple>; | |
{ T::descendants } -> detail::specializes<stdx::tuple>; | |
typename T::template dynamic_controller_t<int>; | |
}; | |
template <typename T> | |
concept base_irq_config = | |
status_policy<typename T::status_policy_t> and | |
detail::specializes<typename T::resources_t, resource_list> and requires { | |
{ T::template enable<true>() } -> std::same_as<void>; | |
{ T::children } -> detail::specializes<stdx::tuple>; | |
{ T::descendants } -> detail::specializes<stdx::tuple>; | |
}; | |
template <typename T> | |
concept irq_config = base_irq_config<T> and requires { | |
{ T::irq_number } -> std::same_as<irq_num_t const &>; | |
}; | |
template <typename T> | |
concept sub_irq_config = base_irq_config<T> and requires { | |
T::enable_field; | |
T::status_field; | |
}; | |
template <typename T> | |
concept base_irq_interface = requires(T const &t) { | |
{ t.get_interrupt_enables() } -> detail::specializes<stdx::tuple>; | |
{ t.run() } -> std::same_as<void>; | |
}; | |
template <typename T> | |
concept irq_interface = base_irq_interface<T> and requires(T const &t) { | |
{ T::irq_number } -> std::same_as<irq_num_t const &>; | |
{ t.init_mcu_interrupts() } -> std::same_as<void>; | |
}; | |
template <typename T> | |
concept sub_irq_interface = base_irq_interface<T>; | |
template <typename T, typename Flow> | |
concept nexus_for = requires { | |
T::template service<Flow>(); | |
{ T::template service<Flow>.active } -> std::same_as<bool const &>; | |
}; | |
} // namespace interrupt | |
namespace interrupt { | |
enum class resource_status { OFF = 0, ON = 1 }; | |
template <typename Irq> | |
concept has_enable_field = requires { Irq::enable_field; }; | |
template <typename Irq> | |
concept has_resource = | |
has_enable_field<Irq> and | |
not boost::mp11::mp_empty<typename Irq::resources_t>::value; | |
template <typename Root> struct dynamic_controller { | |
private: | |
using all_resources_t = | |
boost::mp11::mp_unique<decltype(Root::descendants.apply( | |
[]<typename... Irqs>(Irqs const &...) { | |
return boost::mp11::mp_append<typename Irqs::resources_t...>{}; | |
}))>; | |
template <typename Register> | |
CONSTINIT static inline typename Register::DataType allowed_enables = | |
std::numeric_limits<typename Register::DataType>::max(); | |
template <typename Register> | |
CONSTINIT static inline typename Register::DataType dynamic_enables{}; | |
template <typename Resource> | |
CONSTINIT static inline bool is_resource_on = true; | |
template <typename Resource> struct doesnt_require_resource { | |
template <typename Irq> | |
using fn = | |
std::bool_constant<has_enable_field<Irq> and | |
not boost::mp11::mp_contains< | |
typename Irq::resources_t, Resource>::value>; | |
}; | |
template <typename Register> struct in_register { | |
template <typename Field> | |
using fn = std::is_same<Register, typename Field::RegisterType>; | |
}; | |
/** | |
* For each ResourceType, keep track of what interrupts can still be enabled | |
* when that resource goes down. | |
* | |
* Each bit in this mask corresponds to an interrupt enable field in | |
* RegType. If the bit is '1', that means the corresponding interrupt can be | |
* enabled when the resource is not available. If the bit is '0', that means | |
* the corresponding interrupt must be disabled when the resource is not | |
* available. | |
* | |
* @tparam ResourceType | |
* The resource we want to check. | |
* | |
* @tparam RegType | |
* The specific register mask we want to check. | |
*/ | |
template <typename ResourceType, typename RegType> | |
constexpr static typename RegType::DataType irqs_allowed = []() { | |
// get all interrupt enable fields that don't require the given resource | |
auto const matching_irqs = | |
stdx::filter<doesnt_require_resource<ResourceType>::template fn>( | |
Root::descendants); | |
auto const interrupt_enables_tuple = stdx::transform( | |
[](auto irq) { return irq.enable_field; }, matching_irqs); | |
// filter fields that aren't in RegType | |
auto const fields_in_reg = | |
stdx::filter<in_register<RegType>::template fn>( | |
interrupt_enables_tuple); | |
// set the bits in the mask for interrupts that don't require the | |
// resource | |
using DataType = typename RegType::DataType; | |
return fields_in_reg.fold_left( | |
DataType{}, [](DataType value, auto field) -> DataType { | |
return value | field.get_mask(); | |
}); | |
}(); | |
template <typename RegTypeTuple> | |
static inline void reprogram_interrupt_enables(RegTypeTuple regs) { | |
stdx::for_each( | |
[]<typename R>(R reg) { | |
// make sure we don't enable any interrupts that are not allowed | |
// according to resource availability | |
auto const final_enables = | |
allowed_enables<R> & dynamic_enables<R>; | |
// update the hardware registers | |
apply(write(reg.raw(final_enables))); | |
}, | |
regs); | |
} | |
/** | |
* tuple of every interrupt register affected by a resource | |
*/ | |
template <typename Irq> | |
using has_resource_t = std::bool_constant<has_resource<Irq>>; | |
constexpr static auto all_resource_affected_regs = | |
stdx::to_unsorted_set(stdx::transform( | |
[]<typename Irq>(Irq) { return Irq::enable_field.get_register(); }, | |
stdx::filter<has_resource_t>(Root::descendants))); | |
/** | |
* Reprogram interrupt enables based on updated resource availability. | |
*/ | |
static inline auto recalculate_allowed_enables() { | |
// set allowed_enables mask for each resource affected register | |
stdx::for_each( | |
[]<typename R>(R) { | |
using DataType = typename R::DataType; | |
allowed_enables<R> = std::numeric_limits<DataType>::max(); | |
}, | |
all_resource_affected_regs); | |
// for each resource, if it is not on, mask out unavailable interrupts | |
stdx::template_for_each<all_resources_t>([]<typename Rsrc>() { | |
if (not is_resource_on<Rsrc>) { | |
stdx::for_each( | |
[]<typename R>(R) { | |
allowed_enables<R> &= irqs_allowed<Rsrc, R>; | |
}, | |
all_resource_affected_regs); | |
} | |
}); | |
return all_resource_affected_regs; | |
} | |
/** | |
* Store the interrupt enable values that FW _wants_ at runtime, | |
* irrespective of any resource conflicts that would require specific | |
* interrupts to be disabled. | |
* | |
* @tparam RegType | |
* The croo::Register this value corresponds to. | |
*/ | |
template <typename... Flows> struct match_flow { | |
template <typename Irq> | |
using fn = | |
std::bool_constant<has_enable_field<Irq> and | |
(... or Irq::template triggers_flow<Flows>)>; | |
}; | |
template <bool Enable, typename... Flows> | |
static inline void enable_by_name() { | |
// NOTE: critical section is not needed here because shared state is | |
// only updated by the final call to enable_by_field | |
// TODO: add support to enable/disable top-level IRQs by name. | |
// this will require another way to manage them vs. mmio | |
// registers. once that goes in, then enable_by_field should be | |
// removed or made private. | |
auto const matching_irqs = | |
stdx::filter<match_flow<Flows...>::template fn>(Root::descendants); | |
auto const interrupt_enables_tuple = stdx::transform( | |
[](auto irq) { return irq.enable_field; }, matching_irqs); | |
interrupt_enables_tuple.apply([]<typename... Fields>(Fields...) { | |
enable_by_field<Enable, Fields...>(); | |
}); | |
} | |
template <typename ResourceType> | |
static inline void update_resource(resource_status status) { | |
conc::call_in_critical_section<dynamic_controller>([&] { | |
is_resource_on<ResourceType> = (status == resource_status::ON); | |
recalculate_allowed_enables(); | |
reprogram_interrupt_enables(all_resource_affected_regs); | |
}); | |
} | |
public: | |
template <typename ResourceType> static inline void turn_on_resource() { | |
update_resource<ResourceType>(resource_status::ON); | |
} | |
template <typename ResourceType> static inline void turn_off_resource() { | |
update_resource<ResourceType>(resource_status::OFF); | |
} | |
template <bool Enable, typename... Fields> | |
static inline void enable_by_field() { | |
conc::call_in_critical_section<dynamic_controller>([] { | |
[[maybe_unused]] const auto enable = []<typename F>() -> void { | |
using R = typename F::RegisterType; | |
if constexpr (Enable) { | |
dynamic_enables<R> |= F::get_mask(); | |
} else { | |
dynamic_enables<R> &= ~F::get_mask(); | |
} | |
}; | |
(enable.template operator()<Fields>(), ...); | |
auto const unique_regs = stdx::to_unsorted_set( | |
stdx::tuple<typename Fields::RegisterType...>{}); | |
reprogram_interrupt_enables(unique_regs); | |
}); | |
} | |
template <typename... Flows> static inline void enable() { | |
enable_by_name<true, Flows...>(); | |
} | |
template <typename... Flows> static inline void disable() { | |
enable_by_name<false, Flows...>(); | |
} | |
}; | |
} // namespace interrupt | |
namespace interrupt { | |
template <typename T> | |
concept hal_interface = requires(T const &t, void (*isr)()) { | |
{ t.init() } -> stdx::same_as<void>; | |
{ | |
t.template irq_init<true, irq_num_t{}, std::size_t{}>() | |
} -> stdx::same_as<void>; | |
{ | |
t.template run<dont_clear_status>(irq_num_t{}, isr) | |
} -> stdx::same_as<void>; | |
}; | |
template <typename...> struct null_hal { | |
static auto init() -> void { undefined(); } | |
template <bool Enable, irq_num_t IrqNumber, std::size_t PriorityLevel> | |
static auto irq_init() -> void { | |
undefined(); | |
} | |
template <status_policy> | |
static auto run(irq_num_t, stdx::invocable auto const &) -> void { | |
undefined(); | |
} | |
private: | |
static auto undefined() -> void { | |
static_assert(stdx::always_false_v<null_hal>, | |
"No interrupt HAL defined: inject one"); | |
} | |
}; | |
static_assert(hal_interface<null_hal<>>); | |
template <typename...> inline auto injected_hal = null_hal{}; | |
struct hal { | |
template <typename... Ts> | |
requires(sizeof...(Ts) == 0) | |
static auto init() -> void { | |
injected_hal<Ts...>.init(); | |
} | |
template <bool Enable, irq_num_t IrqNumber, int Priority, typename... Ts> | |
requires(sizeof...(Ts) == 0) | |
static auto irq_init() -> void { | |
injected_hal<Ts...>.template irq_init<Enable, IrqNumber, Priority>(); | |
} | |
template <status_policy P, typename... Ts> | |
requires(sizeof...(Ts) == 0) | |
static auto run(irq_num_t irq, stdx::invocable auto const &isr) -> void { | |
injected_hal<Ts...>.template run<P>(irq, isr); | |
} | |
}; | |
static_assert(hal_interface<hal>); | |
} // namespace interrupt | |
namespace interrupt { | |
namespace detail { | |
template <typename Dynamic, irq_interface... Impls> struct manager { | |
void init() const { | |
// TODO: log exact interrupt manager configuration | |
// (should be a single compile-time string with no arguments) | |
hal::init(); | |
init_mcu_interrupts(); | |
init_sub_interrupts(); | |
} | |
void init_mcu_interrupts() const { (Impls::init_mcu_interrupts(), ...); } | |
void init_sub_interrupts() const { | |
auto enables = stdx::tuple_cat(Impls::get_interrupt_enables()...); | |
enables.apply([]<typename... Enables>(Enables...) { | |
Dynamic::template enable_by_field<true, Enables...>(); | |
}); | |
} | |
template <irq_num_t Number> inline void run() const { | |
using M = stdx::type_map<stdx::vt_pair<Impls::irq_number, Impls>...>; | |
using irq_t = stdx::value_lookup_t<M, Number>; | |
if constexpr (not std::is_void_v<irq_t>) { | |
irq_t::run(); | |
} | |
} | |
[[nodiscard]] constexpr auto max_irq() const -> irq_num_t { | |
return static_cast<irq_num_t>( | |
std::max({stdx::to_underlying(Impls::irq_number)...})); | |
} | |
}; | |
template <typename Config> struct build_manager { | |
using dynamic_t = typename Config::template dynamic_controller_t<Config>; | |
template <typename... Built> using impl = manager<dynamic_t, Built...>; | |
}; | |
} // namespace detail | |
template <interrupt::root_config Config, typename... Nexi> | |
using manager = typename Config::template build< | |
detail::build_manager<Config>::template impl, Nexi...>; | |
} // namespace interrupt | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment