Page MenuHomePhorge

No OneTemporary

Size
26 KB
Referenced Files
None
Subscribers
None
diff --git a/src/base/promise-interface.hpp b/src/base/promise-interface.hpp
index 2be31c4..5bcbd17 100644
--- a/src/base/promise-interface.hpp
+++ b/src/base/promise-interface.hpp
@@ -1,349 +1,352 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <type_traits>
#include <functional>
namespace Kazv
{
class TypelessPromise
{
};
template<template<class> class DeriveT, class T>
class AbstractPromise;
namespace detail
{
template<class T, class = void>
struct PromiseParameterT
{
using type = T;
};
template<class PromiseT>
struct PromiseParameterT<PromiseT,
std::void_t<std::enable_if_t<std::is_base_of_v<TypelessPromise, PromiseT>, int>>>
{
using type = typename PromiseT::DataT;
};
template<class T>
using PromiseParameter = typename PromiseParameterT<T>::type;
template<class T, class = void>
struct PromiseResult
{
};
template<class T>
struct PromiseResult<T, std::void_t<std::invoke_result_t<T>>>
{
using Res = std::invoke_result_t<T>;
using type = PromiseParameter<Res>;
};
template<class T>
using PromiseResultT = typename PromiseResult<T>::type;
template<class T, class P>
struct PromiseThenResultT
{
using Res = std::invoke_result_t<T, PromiseParameter<P>>;
using type = PromiseParameter<Res>;
};
}
template<class T, class P>
using PromiseThenResult = typename detail::PromiseThenResultT<T, P>::type;
template<class T>
constexpr auto isPromise = std::is_base_of_v<TypelessPromise, T>;
template<template<class> class DeriveT, class T>
class AbstractPromise : TypelessPromise
{
public:
using DataT = T;
static_assert(!isPromise<DataT>, "Cannot create a promise of promise");
AbstractPromise(DeriveT<T> *obj) : m_derived(obj) {}
template<class FuncT>
auto then(FuncT &&func)
-> DeriveT<PromiseThenResult<FuncT, DataT>> {
return m_derived->then(std::forward<FuncT>(func));
}
private:
DeriveT<T> *m_derived;
};
namespace PromiseCombination
{
constexpr bool createDefaultForPromiseThen(bool) { return true; }
constexpr bool dataCombine(bool a, bool b) { return a && b; }
constexpr bool dataCombineNone(bool) { return true; }
template<class T, class = void>
struct DefaultForPromiseT
{
constexpr T operator()() const { return T(); }
};
template<class T>
struct DefaultForPromiseT<
T,
std::void_t<decltype(createDefaultForPromiseThen(std::declval<T>()))>>
{
constexpr T operator()() const { return createDefaultForPromiseThen(T()); }
};
template<class T>
constexpr T defaultForPromiseThen(T)
{
return DefaultForPromiseT<T>{}();
}
}
template<class T>
class SingleTypePromise : TypelessPromise
{
public:
using DataT = T;
template<class DeriveT>
SingleTypePromise(DeriveT obj)
: m_d(std::unique_ptr<Concept>(new Model<DeriveT>(std::move(obj)))) {}
SingleTypePromise(const SingleTypePromise &that)
: m_d(that.m_d->clone())
{}
SingleTypePromise(SingleTypePromise &&that)
: m_d(std::move(that.m_d))
{}
SingleTypePromise &operator=(const SingleTypePromise &that) {
m_d = that.m_d->clone();
return *this;
}
SingleTypePromise &operator=(SingleTypePromise &&that) {
m_d = std::move(that.m_d);
return *this;
}
template<class F>
SingleTypePromise then(F &&f) {
if constexpr (std::is_same_v<std::invoke_result_t<F, DataT>, void>) {
return m_d->thenVoid(f);
} else if constexpr(isPromise<std::invoke_result_t<F, DataT>>) {
return m_d->thenPromise(f);
} else {
return m_d->thenData(f);
}
}
bool ready() const { return m_d->ready(); }
DataT get() const { return m_d->get(); }
private:
struct Concept
{
virtual ~Concept() = default;
virtual SingleTypePromise thenVoid(std::function<void(DataT)> f) = 0;
virtual SingleTypePromise thenData(std::function<DataT(DataT)> f) = 0;
virtual SingleTypePromise thenPromise(std::function<SingleTypePromise(DataT)> f) = 0;
virtual std::unique_ptr<Concept> clone() const = 0;
virtual bool ready() const = 0;
virtual DataT get() const = 0;
};
template<class DeriveT>
struct Model : public Concept
{
Model(DeriveT obj) : instance(std::move(obj)) {}
~Model() override = default;
SingleTypePromise thenVoid(std::function<void(DataT)> f) override {
return instance.then([=](DataT v) {
f(v);
return PromiseCombination::defaultForPromiseThen(DataT());
});
}
SingleTypePromise thenData(std::function<DataT(DataT)> f) override {
return instance.then([=](DataT v) { return f(v); });
}
SingleTypePromise thenPromise(std::function<SingleTypePromise(DataT)> f) override {
return instance.then([=](DataT v) { return f(v); });
}
std::unique_ptr<Concept> clone() const override {
return std::unique_ptr<Concept>(new Model<DeriveT>(instance));
}
bool ready() const override {
return instance.ready();
}
DataT get() const override {
return instance.get();
}
DeriveT instance;
};
std::unique_ptr<Concept> m_d;
};
using BoolPromise = SingleTypePromise<bool>;
template<class DeriveT, template<class> class PromiseT>
class PromiseInterface
{
public:
PromiseInterface(DeriveT *obj) : m_derived(obj) {}
template<class T, class FuncT>
auto create(FuncT &&func) -> PromiseT<T>;
template<class T>
auto createResolved(T &&val) -> PromiseT<T>;
private:
DeriveT *m_derived;
};
template<class DeriveT, template<class> class PromiseT>
template<class T, class FuncT>
auto PromiseInterface<DeriveT, PromiseT>::create(FuncT &&func) -> PromiseT<T> {
return m_derived->create(std::forward<FuncT>(func));
}
template<class DeriveT, template<class> class PromiseT>
template<class T>
auto PromiseInterface<DeriveT, PromiseT>::createResolved(T &&val) -> PromiseT<T> {
return m_derived->createResolved(std::forward<T>(val));
}
template<class T>
class SingleTypePromiseInterface
{
public:
using DataT = T;
using PromiseT = SingleTypePromise<DataT>;
using ResolveT = std::function<void(DataT)>;
using ResolveToPromiseT = std::function<void(PromiseT)>;
template<class DeriveT>
SingleTypePromiseInterface(DeriveT obj)
: m_d(std::unique_ptr<Concept>(new Model<std::decay_t<DeriveT>>(std::move(obj)))) {
if (! m_d) {
throw std::logic_error("promise handler is empty");
}
}
SingleTypePromiseInterface(const SingleTypePromiseInterface &that)
: m_d(that.m_d) {
if (! m_d) {
throw std::logic_error("promise handler is empty");
}
}
SingleTypePromiseInterface(SingleTypePromiseInterface &&that)
: m_d(std::move(that.m_d)) {
if (! m_d) {
throw std::logic_error("promise handler is empty");
}
}
SingleTypePromiseInterface &operator=(const SingleTypePromiseInterface &that) {
m_d = that.m_d;
return *this;
}
SingleTypePromiseInterface &operator=(SingleTypePromiseInterface &&that) {
m_d = std::move(that.m_d);
return *this;
}
PromiseT create(std::function<void(ResolveT)> f) const {
return m_d->create(f);
}
PromiseT createResolveToPromise(std::function<void(ResolveToPromiseT)> f) const {
return m_d->createResolveToPromise(f);
}
PromiseT createResolved(DataT v) const {
return m_d->createResolved(v);
}
template<class RangeT>
PromiseT all(RangeT promises) const {
using PromiseCombination::dataCombine;
using PromiseCombination::dataCombineNone;
if (promises.empty()) {
return createResolved(dataCombineNone(DataT{}));
}
auto p1 = *(promises.begin());
promises.erase(promises.begin());
+ if (promises.empty()) {
+ return p1;
+ }
return p1.then([*this, promises=std::move(promises)](DataT val) mutable {
return all(std::move(promises))
.then([=](DataT val2) {
return dataCombine(val, val2);
});
});
}
private:
struct Concept
{
virtual ~Concept() = default;
virtual PromiseT create(std::function<void(ResolveT)> f) = 0;
virtual PromiseT createResolveToPromise(std::function<void(ResolveToPromiseT)> f) = 0;
virtual PromiseT createResolved(DataT v) = 0;
};
template<class DeriveT>
struct Model : public Concept
{
static_assert(std::is_same_v<std::decay_t<DeriveT>, DeriveT>, "DeriveT must not be a reference");
Model(DeriveT obj) : instance(std::move(obj)) {}
~Model() override = default;
PromiseT create(std::function<void(ResolveT)> f) override {
return instance.template create<DataT>(f);
}
PromiseT createResolveToPromise(std::function<void(ResolveToPromiseT)> f) override {
return instance.template create<DataT>(f);
}
PromiseT createResolved(DataT v) override {
return instance.createResolved(v);
}
DeriveT instance;
};
std::shared_ptr<Concept> m_d;
};
using BoolPromiseInterface = SingleTypePromiseInterface<bool>;
}
diff --git a/src/store/context.hpp b/src/store/context.hpp
index 44aa403..6046671 100644
--- a/src/store/context.hpp
+++ b/src/store/context.hpp
@@ -1,278 +1,278 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <lager/deps.hpp>
#include <boost/hana.hpp>
#include <jsonwrap.hpp>
#include <promise-interface.hpp>
namespace Kazv
{
class EffectStatus
{
public:
inline EffectStatus() : m_succ(true) {}
inline EffectStatus(bool succ) : m_succ(succ) {}
inline EffectStatus(bool succ, JsonWrap d) : m_succ(succ), m_data(d) {}
/**
* Get whether this is successful.
*
* @return Whether this is successful.
*/
inline bool success() const { return m_succ; }
/**
* Conversion to bool.
*
* @return `success()`.
*/
inline explicit operator bool() const { return success(); }
/**
* Get the attached data.
*
* @return A JsonWrap containing the attached data.
*/
inline const JsonWrap &data() const { return m_data; }
/**
* Get the data at `key`.
*
* This requires `data()` to contain a json object.
*
* @param key The key.
* @return A json with the data at `key`.
*/
inline const json &dataJson(std::string key) const {
return data().get().at(key);
}
/**
* Get the data at `index` at `key`.
*
* This requires `data()` to contain a json array of json objects.
*
* @param index The index.
* @param key The key.
* @return A json with the data at `index` at `key`.
*/
inline const json &dataJson(int index, std::string key) const {
return data().get().at(index).at(key);
}
/**
* Get the data string at `key`.
*
* This requires `data()` to contain a json object, and
* `data().get().at(key)` to be a json string.
*
* @param key The key.
* @return A std::string for the data at `key`.
*/
inline std::string dataStr(std::string key) const {
return dataJson(key).template get<std::string>();
}
/**
* Get the data at `index` at `key`.
*
* This requires `data()` to contain a json array of json objects,
* and `data().get().at(index).at(key)` to be a json string.
*
* @param index The index.
* @param key The key.
* @return A std::string for the data at `index` at `key`.
*/
inline std::string dataStr(int index, std::string key) const {
return dataJson(index, key).template get<std::string>();
}
private:
bool m_succ;
JsonWrap m_data;
};
inline EffectStatus createDefaultForPromiseThen(EffectStatus)
{
- return EffectStatus{true};
+ return EffectStatus{true, json::array()};
}
inline EffectStatus dataCombine(EffectStatus a, EffectStatus b)
{
auto succ = a.success() && b.success();
auto data = a.data().get().is_array()
? a.data().get()
: json::array({a.data().get()});
if (b.data().get().is_array()) {
for (const auto &i: b.data().get()) {
data.push_back(i);
}
} else {
data.push_back(b.data().get());
}
return {succ, data};
}
inline EffectStatus dataCombineNone(EffectStatus)
{
return EffectStatus(true);
}
using DefaultRetType = EffectStatus;
template<class T, class Action, class Deps = lager::deps<>>
class ContextBase : public Deps
{
public:
using RetType = T;
using PromiseInterfaceT = SingleTypePromiseInterface<RetType>;
using PromiseT = SingleTypePromise<RetType>;
template<class Func>
ContextBase(Func &&dispatcher, PromiseInterfaceT ph, Deps deps)
: Deps(std::move(deps))
, m_ph(ph)
, m_dispatcher(std::forward<Func>(dispatcher))
{
}
template<class AnotherAction, class AnotherDeps,
std::enable_if_t<std::is_convertible_v<Action, AnotherAction>
&& std::is_convertible_v<AnotherDeps, Deps>, int> = 0>
ContextBase(const ContextBase<RetType, AnotherAction, AnotherDeps> &that)
: Deps(that)
, m_ph(that.m_ph)
, m_dispatcher(that.m_dispatcher)
{
}
template<class AnotherAction, class AnotherDeps, class Conv,
std::enable_if_t<std::is_convertible_v<std::invoke_result_t<Conv, Action>, AnotherAction>
&& std::is_convertible_v<AnotherDeps, Deps>, int> = 0>
ContextBase(const ContextBase<RetType, AnotherAction, AnotherDeps> &that, Conv &&conv)
: Deps(that)
, m_ph(that.m_ph)
, m_dispatcher([=,
thatDispatcher=that.m_dispatcher,
conv=std::forward<Conv>(conv)](Action a) {
thatDispatcher(conv(std::move(a)));
})
{
}
PromiseT dispatch(Action a) const {
return m_dispatcher(std::move(a));
}
decltype(auto) deps() {
return static_cast<Deps &>(*this);
}
template<class Func>
PromiseT createPromise(Func func) const {
return m_ph.create(std::move(func));
}
template<class Func>
PromiseT createWaitingPromise(Func func) const {
return m_ph.createResolveToPromise(std::move(func));
}
PromiseT createResolvedPromise(RetType v) const {
return m_ph.createResolved(v);
}
PromiseInterfaceT promiseInterface() const {
return m_ph;
}
private:
template<class R2, class A2, typename D2>
friend class ContextBase;
PromiseInterfaceT m_ph;
std::function<PromiseT(Action)> m_dispatcher;
};
template<class A, class D = lager::deps<>>
using Context = ContextBase<DefaultRetType, A, D>;
template<class T, class Action, class Deps = lager::deps<>>
class EffectBase
{
public:
using RetType = T;
using PromiseT = SingleTypePromise<RetType>;
using ContextT = ContextBase<RetType, Action, Deps>;
/**
* Constructor.
*
* @return An effect that runs `func` upon invocation.
* The effect will return a Promise that
* \arg resolves after the effect is invoked, to
* `PromiseCombination::defaultForPromiseThen(RetType())`,
* if `func(ctx)` returns void;
* \arg resolves after `func(ctx)` is resolved, to
* what `func(ctx)` resolves to, if `func(ctx)`
* returns a Promise;
* \arg resolves after the effect is invoked, to
* `func(ctx)` (converted to `RetType` if it is not already one),
* otherwise.
*/
template<class Func>
EffectBase(Func func) {
if constexpr (std::is_same_v<std::invoke_result_t<Func, ContextT>, void>) {
m_d = [func=std::move(func)](const auto &ctx) {
return ctx.createPromise(
[ctx,
func](auto resolve) {
func(ctx);
resolve(PromiseCombination::defaultForPromiseThen(RetType()));
});
};
} else {
m_d = [func=std::move(func)](const auto &ctx) {
return ctx.createResolvedPromise(PromiseCombination::defaultForPromiseThen(RetType()))
.then([ctx,
func](auto) {
return func(ctx);
});
};
}
}
PromiseT operator()(const ContextT &ctx) const {
return m_d(ctx);
}
private:
std::function<PromiseT(const ContextT &)> m_d;
};
template<class A, class D = lager::deps<>>
using Effect = EffectBase<DefaultRetType, A, D>;
template<class Reducer, class RetType, class Model, class Action, class Deps>
constexpr bool hasEffect = boost::hana::is_valid(
[]() -> decltype((void)EffectBase<RetType, Action, Deps>(
std::get<1>(std::declval<std::invoke_result_t<Reducer, Model, Action>>()))) {}
)();
}
diff --git a/src/tests/promise-test.cpp b/src/tests/promise-test.cpp
index 74509ae..c2236ff 100644
--- a/src/tests/promise-test.cpp
+++ b/src/tests/promise-test.cpp
@@ -1,112 +1,151 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <catch2/catch.hpp>
#include <asio-promise-handler.hpp>
+#include <context.hpp>
+
using namespace Kazv;
struct MockDataStruct
{
int i{};
};
TEST_CASE("Promise should behave properly", "[promise]")
{
boost::asio::io_context ioContext;
auto ph = AsioPromiseHandler(ioContext.get_executor());
std::vector<int> v;
auto p1 = ph.create<int>([&v](auto resolve) {
v.push_back(1);
resolve(2);
});
auto p2 = p1.then([&v, &ph](int val) {
v.push_back(val);
return ph.createResolved(3);
});
auto p3 = p2.then([&v, &ph](int val) {
v.push_back(val);
return ph.createResolved(-1);
});
auto p4 = p3.then([](int val) {
REQUIRE(val == -1);
return 5;
});
auto p5 = p4.then([](int val) {
REQUIRE(val == 5);
return MockDataStruct{6};
});
auto p6 = p5.then([](MockDataStruct m) {
REQUIRE(m.i == 6);
return 0;
});
auto pTimer = ph.create<int>([&ioContext](auto resolve) {
auto timer = std::make_shared<boost::asio::steady_timer>(ioContext);
timer->expires_after(std::chrono::milliseconds(300));
timer->async_wait(
[timer, resolve](const boost::system::error_code& error) {
if (! error) {
resolve(20);
}
});
});
auto pTimer2 = pTimer.then([](int val) {
REQUIRE(val == 20);
return 0;
});
ioContext.run();
REQUIRE(v == std::vector<int>{ 1, 2, 3 });
}
TEST_CASE("BoolPromise should behave properly", "[promise]")
{
boost::asio::io_context ioContext;
auto ph = BoolPromiseInterface(AsioPromiseHandler(ioContext.get_executor()));
auto p1 = ph.create([](auto resolve) {
resolve(true);
});
auto p2 = p1.then([](bool v) {
REQUIRE(v == true);
});
auto p3 = p2.then([&](bool v) {
REQUIRE(v == true);
return ph.createResolved(false);
});
auto p4 = p3.then([](bool v) {
REQUIRE(v == false);
});
ioContext.run();
}
+
+TEST_CASE("EffectStatus should combine properly with .all()", "[promise][store]")
+{
+ boost::asio::io_context ioContext;
+ using PH = SingleTypePromiseInterface<EffectStatus>;
+ using PromiseT = typename PH::PromiseT;
+ auto ph = PH(AsioPromiseHandler(ioContext.get_executor()));
+
+ auto e1 = EffectStatus(true, json{{"foo", "bar"}});
+ auto e2 = EffectStatus(true, json{{"foo2", "bar2"}});
+ auto e3 = EffectStatus(false, json{{"foo3", "bar3"}});
+
+ auto p1 = ph.createResolved(e1);
+ auto p2 = ph.createResolved(e2);
+ auto p3 = ph.createResolved(e3);
+
+ SECTION(".all(singlePromise) should give the Promise as-is") {
+ ph.all(std::vector<PromiseT>{p1})
+ .then([](auto e) {
+ REQUIRE(e.success());
+ REQUIRE(e.dataStr("foo") == "bar");
+ });
+ }
+
+ SECTION(".all(multiplePromises) should give out the data"
+ " as an array and compute successfulness using &&") {
+ ph.all(std::vector<PromiseT>{p2, p3, p1})
+ .then([](auto e) {
+ REQUIRE(! e.success());
+ REQUIRE(e.dataStr(0, "foo2") == "bar2");
+ REQUIRE(e.dataStr(1, "foo3") == "bar3");
+ REQUIRE(e.dataStr(2, "foo") == "bar");
+ });
+ }
+
+ ioContext.run();
+}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 9, 10:07 AM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1784686
Default Alt Text
(26 KB)

Event Timeline