9#ifndef AVARA3D_UTIL_FLOW_H
10#define AVARA3D_UTIL_FLOW_H
18#include <source_location>
21#include <unordered_map>
26namespace a3d::util::flow::detail {
28 inline std::size_t hash_combine(std::size_t seed, std::size_t v) {
29 return seed ^ (v + 0x9e3779b97f4a7c15ull + (seed << 6) + (seed >> 2));
32 inline std::size_t callsite_key(std::source_location loc) {
34 std::string_view file = loc.file_name();
35 std::string_view func = loc.function_name();
36 h = hash_combine(h, std::hash<std::string_view> {}(file));
37 h = hash_combine(h, std::hash<std::string_view> {}(func));
38 h = hash_combine(h, std::hash<unsigned> {}(loc.line()));
39 h = hash_combine(h, std::hash<unsigned> {}(loc.column()));
44 template<
class State,
class... CtorArgs>
45 State& state_for(std::source_location loc, CtorArgs&&... ctorArgs) {
47 static std::unordered_map<std::size_t, std::unique_ptr<State>> states;
49 const std::size_t key = callsite_key(loc);
51 std::lock_guard<std::mutex> lock(m);
52 auto& ptr = states[key];
54 ptr = std::make_unique<State>(std::forward<CtorArgs>(ctorArgs)...);
60 bool every_tick(
typename Clock::time_point& last,
typename Clock::duration interval) {
61 const auto now = Clock::now();
62 if (now - last >= interval) {
69 inline long long clamp_step(
long long n) {
70 return (n < 1) ? 1 : n;
75namespace a3d::util::flow {
84 template<
class Cond,
class FailFn>
85 [[nodiscard]]
bool edge_guard(Cond&& cond,
86 FailFn&& on_fail_once,
87 std::source_location loc = std::source_location::current()) {
92 const bool ok =
static_cast<bool>(std::forward<Cond>(cond));
93 auto& s = ::a3d::util::flow::detail::state_for<State>(loc);
102 std::invoke(std::forward<FailFn>(on_fail_once));
109 decltype(
auto) once(Fn&& fn, std::source_location loc = std::source_location::current()) {
110 using R = std::invoke_result_t<Fn&>;
112 if constexpr (std::is_void_v<R>) {
117 auto& s = ::a3d::util::flow::detail::state_for<State>(loc);
118 std::call_once(s.once, [&] {
119 std::invoke(std::forward<Fn>(fn));
124 using T = std::decay_t<R>;
128 std::optional<T> value;
131 auto& s = ::a3d::util::flow::detail::state_for<State>(loc);
133 std::call_once(s.once, [&] {
134 s.value.emplace(std::invoke(std::forward<Fn>(fn)));
136 return static_cast<const T&
>(*s.value);
141 template<
class ThenFn,
class ElseFn>
142 decltype(
auto) once_else(ThenFn&& then_fn,
144 std::source_location loc = std::source_location::current()) {
145 using R1 = std::invoke_result_t<ThenFn&>;
146 using R2 = std::invoke_result_t<ElseFn&>;
148 static_assert(std::is_void_v<R1> == std::is_void_v<R2>,
149 "once_else: then/else must both return void or both return a value");
150 if constexpr (!std::is_void_v<R1>) {
151 static_assert(std::is_same_v<std::decay_t<R1>, std::decay_t<R2>>,
152 "once_else: then/else must return the same type");
159 auto& s = ::a3d::util::flow::detail::state_for<State>(loc);
161 bool i_ran_then =
false;
162 if constexpr (std::is_void_v<R1>) {
163 std::call_once(s.once, [&] {
165 std::invoke(std::forward<ThenFn>(then_fn));
168 std::invoke(std::forward<ElseFn>(else_fn));
173 using T = std::decay_t<R1>;
174 std::optional<T> first_value;
176 std::call_once(s.once, [&] {
178 first_value.emplace(std::invoke(std::forward<ThenFn>(then_fn)));
184 return std::invoke(std::forward<ElseFn>(else_fn));
195 decltype(
auto) on(
long long invocation,
197 std::source_location loc = std::source_location::current()) {
198 using R = std::invoke_result_t<Fn&>;
205 explicit State(
long long t):
206 target(t < 1 ? 1 : t) {}
209 auto& s = ::a3d::util::flow::detail::state_for<State>(loc, invocation);
211 if constexpr (std::is_void_v<R>) {
212 if (!s.done && (++s.count == s.target)) {
214 std::invoke(std::forward<Fn>(fn));
219 using T = std::decay_t<R>;
220 if (!s.done && (++s.count == s.target)) {
222 return std::optional<T> {std::invoke(std::forward<Fn>(fn))};
224 return std::optional<T> {};
233 template<
class ThenFn,
class ElseFn>
234 decltype(
auto) on_else(
long long invocation,
237 std::source_location loc = std::source_location::current()) {
238 using R1 = std::invoke_result_t<ThenFn&>;
239 using R2 = std::invoke_result_t<ElseFn&>;
241 static_assert(std::is_void_v<R1> == std::is_void_v<R2>,
242 "on_else: then/else must both return void or both return a value");
243 if constexpr (!std::is_void_v<R1>) {
244 static_assert(std::is_same_v<std::decay_t<R1>, std::decay_t<R2>>,
245 "on_else: then/else must return the same type");
253 explicit State(
long long t):
254 target(t < 1 ? 1 : t) {}
257 auto& s = ::a3d::util::flow::detail::state_for<State>(loc, invocation);
259 const bool fire = (!s.done && (++s.count == s.target));
264 if constexpr (std::is_void_v<R1>) {
266 std::invoke(std::forward<ThenFn>(then_fn));
269 std::invoke(std::forward<ElseFn>(else_fn));
274 return fire ? std::invoke(std::forward<ThenFn>(then_fn))
275 : std::invoke(std::forward<ElseFn>(else_fn));
286 decltype(
auto) after(
long long invocations,
288 std::source_location loc = std::source_location::current()) {
289 using R = std::invoke_result_t<Fn&>;
295 explicit State(
long long t):
296 target(t < 0 ? 0 : t) {}
299 auto& s = ::a3d::util::flow::detail::state_for<State>(loc, invocations);
301 const bool fire = (s.count++ >= s.target);
303 if constexpr (std::is_void_v<R>) {
305 std::invoke(std::forward<Fn>(fn));
310 using T = std::decay_t<R>;
312 return std::optional<T> {std::invoke(std::forward<Fn>(fn))};
314 return std::optional<T> {};
323 template<
class ThenFn,
class ElseFn>
324 decltype(
auto) after_else(
long long invocations,
327 std::source_location loc = std::source_location::current()) {
328 using R1 = std::invoke_result_t<ThenFn&>;
329 using R2 = std::invoke_result_t<ElseFn&>;
331 static_assert(std::is_void_v<R1> == std::is_void_v<R2>,
332 "after_else: then/else must both return void or both return a value");
333 if constexpr (!std::is_void_v<R1>) {
334 static_assert(std::is_same_v<std::decay_t<R1>, std::decay_t<R2>>,
335 "after_else: then/else must return the same type");
342 explicit State(
long long t):
343 target(t < 0 ? 0 : t) {}
346 auto& s = ::a3d::util::flow::detail::state_for<State>(loc, invocations);
348 const bool fire = (s.count++ >= s.target);
350 if constexpr (std::is_void_v<R1>) {
352 std::invoke(std::forward<ThenFn>(then_fn));
355 std::invoke(std::forward<ElseFn>(else_fn));
360 return fire ? std::invoke(std::forward<ThenFn>(then_fn))
361 : std::invoke(std::forward<ElseFn>(else_fn));