Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
16 KB
Referenced Files
None
Subscribers
None
diff --git a/src/client/random-generator.hpp b/src/client/random-generator.hpp
index 9a1aa8e..7f676e5 100644
--- a/src/client/random-generator.hpp
+++ b/src/client/random-generator.hpp
@@ -1,81 +1,124 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@kazv.moe>
*
* 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 <libkazv-config.hpp>
#include <memory>
namespace Kazv
{
- class RandomInterface : public std::function<unsigned int()>
+ class RandomInterface
{
- private:
- using BaseT = std::function<unsigned int()>;
-
public:
+ using DataT = unsigned int;
+
/**
* Construct a RandomInterface using an implementation.
*
* @param obj The random generator implementation.
*
- * The implementation obj should be such that the following
- * call is valid, and after that v contains a random number:
+ * The implementation obj must be of a movable type and
+ * must be such that the following
+ * call is valid, and after that v should contain a random number:
*
* `unsigned int v = obj();`
*/
template<class DeriveT>
RandomInterface(DeriveT obj)
- : BaseT(std::move(obj))
+ : m_d(std::make_unique<Model<DeriveT>>(std::move(obj)))
{}
+ /**
+ * Generate a random number of type DataT.
+ *
+ * @return A randomly generated DataT.
+ */
+ DataT operator()() {
+ return (*m_d)();
+ }
+
/**
* Generate a range containing `size` random elements.
*
* RangeT must have a constructor such that
* `RangeT(size, ValueT())` constructs a range of `size`
* elements valued `ValueT()`, where `ValueT` is the value type
* of `RangeT`.
*
* @param size The size of the range to create.
*
* @return A RangeT containing `size` random elements.
*/
template<class RangeT>
RangeT generateRange(std::size_t size) {
using ValueT = std::decay_t<decltype(* (std::declval<RangeT>().begin()))>;
RangeT range(size, ValueT());
return fillRange(std::move(range));
}
/**
* Fill the given range.
*
* @param range The range to fill.
*
* @return A range filled with random elements. Its size will be
* the same as `range`.
*/
template<class RangeT>
RangeT fillRange(RangeT range) {
- std::generate(range.begin(), range.end(), *this);
+ std::generate(range.begin(), range.end(), [this] { return (*this)(); });
return range;
}
+
+ private:
+ struct Concept
+ {
+ virtual ~Concept() = default;
+ virtual DataT operator()() = 0;
+ };
+
+ template<class DeriveT>
+ struct Model : public Concept
+ {
+ Model(DeriveT obj) : instance(std::move(obj)) {}
+ ~Model() override = default;
+ DataT operator()() override {
+ return instance();
+ }
+ DeriveT instance;
+ };
+
+ std::unique_ptr<Concept> m_d;
+ };
+
+ /**
+ * A movable wrapper around std::random_device.
+ */
+ class RandomDeviceGenerator
+ {
+ public:
+ RandomDeviceGenerator() : m_d(std::make_unique<std::random_device>()) {}
+
+ auto operator()() { return (*m_d)(); }
+
+ private:
+ std::unique_ptr<std::random_device> m_d;
};
}
diff --git a/src/client/sdk.hpp b/src/client/sdk.hpp
index 9982fc9..6dd9ec3 100644
--- a/src/client/sdk.hpp
+++ b/src/client/sdk.hpp
@@ -1,220 +1,229 @@
/*
- * Copyright (C) 2020 Tusooa Zhu
+ * Copyright (C) 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
*
* 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 <libkazv-config.hpp>
#include <lager/store.hpp>
+#include <random>
+
#include <store.hpp>
#include "sdk-model.hpp"
#include "sdk-model-cursor-tag.hpp"
#include "client.hpp"
#include "thread-safety-helper.hpp"
+#include "random-generator.hpp"
+
+
namespace Kazv
{
/**
* Contain the single source of truth of a matrix sdk.
*/
template<class EventLoop, class Xform, class ...Enhancers>
class Sdk
{
using ModelT = ::Kazv::SdkModel;
using ClientT = ::Kazv::ClientModel;
using ActionT = typename ModelT::Action;
using CursorT = lager::reader<ModelT>;
using CursorTSP = std::shared_ptr<CursorT>;
using StoreT = decltype(
makeStore<ActionT>(
std::declval<ModelT>(),
&ModelT::update,
std::declval<EventLoop>(),
lager::with_deps(
std::ref(detail::declref<JobInterface>()),
std::ref(detail::declref<EventInterface>()),
- lager::dep::as<SdkModelCursorKey>(std::declval<std::function<CursorTSP()>>())
+ lager::dep::as<SdkModelCursorKey>(std::declval<std::function<CursorTSP()>>()),
+ std::ref(detail::declref<RandomInterface>())
#ifdef KAZV_USE_THREAD_SAFETY_HELPER
, std::ref(detail::declref<EventLoopThreadIdKeeper>())
#endif
),
std::declval<Enhancers>()...)
);
- using DepsT = lager::deps<JobInterface &, EventInterface &, SdkModelCursorKey
+ using DepsT = lager::deps<JobInterface &, EventInterface &, SdkModelCursorKey, RandomInterface &
#ifdef KAZV_USE_THREAD_SAFETY_HELPER
, EventLoopThreadIdKeeper &
#endif
>;
using ContextT = Context<ActionT, DepsT>;
public:
Sdk(ModelT model,
JobInterface &jobHandler,
EventInterface &eventEmitter,
EventLoop &&eventLoop,
Xform &&xform,
Enhancers &&...enhancers)
: m_d(std::make_unique<Private>(
std::move(model), jobHandler, eventEmitter,
std::forward<EventLoop>(eventLoop), std::forward<Xform>(xform),
std::forward<Enhancers>(enhancers)...)) {}
/**
* Get the context associated with this.
*
* The returned context is thread-safe if every thread calls with
* different instances.
*/
ContextT context() const {
return m_d->store;
}
/**
* Get a Client representing this.
*
* The returned Client belongs to the thread where the promise handler runs.
*/
Client client() const {
return {Client::InEventLoopTag{}, ContextT(m_d->store)};
}
/**
* Create a secondary root for this Sdk.
*
* @param eventLoop An event loop passed to `lager::make_store`.
* The resulting secondary root will belong to the thread of this event loop.
*
* @return A lager::store that belongs to the thread of `eventLoop`. The
* store will be kept update with this sdk.
*/
template<class EL>
auto createSecondaryRoot(EL &&eventLoop) const {
auto secondaryStore = lager::make_store<ModelT>(
ModelT{},
std::forward<EL>(eventLoop),
lager::with_reducer([](auto &&, auto next) { return next; }));
lager::context<ModelT> secondaryCtx = secondaryStore;
context().createResolvedPromise({})
.then([secondaryCtx, d=m_d.get()](auto &&) {
lager::watch(*(d->sdk),
[secondaryCtx](auto next) { secondaryCtx.dispatch(std::move(next)); });
});
return secondaryStore;
}
/**
* Get a Client representing this.
*
* The returned Client belongs to the same thread as `sr`.
*
* This function is thread-safe, but it must be called from the thread
* where `sr` belongs.
*
* @param sr The secondary root cursor that represents this sdk.
*
* @return A Client representing this in the same thread as `sr`.
*/
Client clientFromSecondaryRoot(lager::reader<ModelT> sr) const {
return Client(sr, ContextT(m_d->store));
}
private:
struct Private
{
Private(ModelT model,
JobInterface &jobHandler,
EventInterface &eventEmitter,
EventLoop &&eventLoop,
Xform &&xform,
Enhancers &&...enhancers)
- : store(makeStore<ActionT>(
+ : rg(RandomInterface{RandomDeviceGenerator{}})
+ , store(makeStore<ActionT>(
std::move(model),
&ModelT::update,
std::forward<EventLoop>(eventLoop),
lager::with_deps(
std::ref(jobHandler),
std::ref(eventEmitter),
lager::dep::as<SdkModelCursorKey>(
- std::function<CursorTSP()>([this] { return sdk; }))
+ std::function<CursorTSP()>([this] { return sdk; })),
+ std::ref(rg.value())
#ifdef KAZV_USE_THREAD_SAFETY_HELPER
, std::ref(keeper)
#endif
),
std::forward<Enhancers>(enhancers)...))
, sdk(std::make_shared<lager::reader<ModelT>>(store.reader().xform(std::forward<Xform>(xform))))
{
#ifdef KAZV_USE_THREAD_SAFETY_HELPER
store.context().createResolvedPromise(EffectStatus{})
.then([this](auto &&) {
keeper.set(std::this_thread::get_id());
});
#endif
}
#ifdef KAZV_USE_THREAD_SAFETY_HELPER
EventLoopThreadIdKeeper keeper;
#endif
+ std::optional<RandomInterface> rg;
StoreT store;
CursorTSP sdk;
};
std::unique_ptr<Private> m_d;
};
template<class EventLoop, class Xform, class ...Enhancers>
inline auto makeSdk(SdkModel sdk,
JobInterface &jobHandler,
EventInterface &eventEmitter,
EventLoop &&eventLoop,
Xform &&xform,
Enhancers &&...enhancers)
-> Sdk<EventLoop, Xform, Enhancers...>
{
return { std::move(sdk),
jobHandler,
eventEmitter,
std::forward<EventLoop>(eventLoop),
std::forward<Xform>(xform),
std::forward<Enhancers>(enhancers)... };
}
template<class EventLoop, class Xform, class ...Enhancers>
inline auto makeDefaultEncryptedSdk(
JobInterface &jobHandler,
EventInterface &eventEmitter,
EventLoop &&eventLoop,
Xform &&xform,
Enhancers &&...enhancers)
-> Sdk<EventLoop, Xform, Enhancers...>
{
auto m = SdkModel{};
m.client.crypto = Crypto();
return makeSdk(std::move(m),
jobHandler,
eventEmitter,
std::forward<EventLoop>(eventLoop),
std::forward<Xform>(xform),
std::forward<Enhancers>(enhancers)...);
}
}
diff --git a/src/tests/client/random-generator-test.cpp b/src/tests/client/random-generator-test.cpp
index 649780b..511db1d 100644
--- a/src/tests/client/random-generator-test.cpp
+++ b/src/tests/client/random-generator-test.cpp
@@ -1,69 +1,102 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@kazv.moe>
*
* 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 <libkazv-config.hpp>
#include <catch2/catch.hpp>
#include <string>
#include <vector>
#include <random-generator.hpp>
using namespace Kazv;
TEST_CASE("RandomInterface with a const generator", "[client][random]")
{
auto generator = [] { return 1; };
auto iface = RandomInterface{generator};
REQUIRE(iface() == 1);
auto range = iface.generateRange<std::vector<unsigned int>>(5);
REQUIRE(range == std::vector<unsigned int>(5, 1));
auto range2 = iface.fillRange(std::vector<unsigned int>(5));
REQUIRE(range2 == std::vector<unsigned int>(5, 1));
}
TEST_CASE("RandomInterface with a non-const generator", "[client][random]")
{
unsigned int cur = 0;
- auto generator = [&cur] { ++cur; return cur; };
+ auto generator = [cur]() mutable { ++cur; return cur; };
auto iface = RandomInterface{generator};
REQUIRE(iface() == 1);
auto range = iface.generateRange<std::vector<unsigned int>>(5);
REQUIRE(range == std::vector<unsigned int>{2, 3, 4, 5, 6});
auto range2 = iface.fillRange(std::vector<unsigned int>(5));
REQUIRE(range2 == std::vector<unsigned int>{7, 8, 9, 10, 11});
}
TEST_CASE("RandomInterface generating ranges with element type narrower than unsigned int", "[client][random]")
{
auto ucharMax = static_cast<unsigned int>(static_cast<unsigned char>(-1));
auto generator = [=] { return (ucharMax + 1) + 1; };
auto iface = RandomInterface{generator};
auto range = iface.generateRange<std::string>(5);
REQUIRE(range == std::string(5, 1));
}
+
+namespace
+{
+ struct NonCopyableCallable
+ {
+ NonCopyableCallable() {}
+ NonCopyableCallable(const NonCopyableCallable &that) = delete;
+ NonCopyableCallable(NonCopyableCallable &&that) = default;
+ NonCopyableCallable &operator=(const NonCopyableCallable &that) = delete;
+ NonCopyableCallable &operator=(NonCopyableCallable &&that) = default;
+
+ unsigned int operator()() { return 1; }
+ };
+}
+
+TEST_CASE("RandomInterface should work with non-copyable types", "[client][random]")
+{
+ auto iface = RandomInterface{NonCopyableCallable()};
+
+ REQUIRE(iface() == 1);
+
+ auto range = iface.generateRange<std::vector<unsigned int>>(5);
+ REQUIRE(range == std::vector<unsigned int>(5, 1));
+
+ auto range2 = iface.fillRange(std::vector<unsigned int>(5));
+ REQUIRE(range2 == std::vector<unsigned int>(5, 1));
+}
+
+TEST_CASE("RandomDeviceGenerator should be movable", "[client][random]")
+{
+ auto rdg = RandomDeviceGenerator{};
+ auto rdg2 = std::move(rdg);
+}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 2, 9:14 AM (6 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1804125
Default Alt Text
(16 KB)

Event Timeline