diff --git a/src/lib/handle.hpp b/src/lib/handle.hpp index bf9aabca8..222faa1a7 100644 --- a/src/lib/handle.hpp +++ b/src/lib/handle.hpp @@ -82,9 +82,15 @@ namespace lib { * Typically this is followed by activating * the handle by the managing service. */ - Handle ( ) - : smPtr_() - { } + Handle() = default; + + /** directly establish handle from an implementation, + * which typically way just heap allocated beforehand. + */ + explicit + Handle (IMP* imp) + : smPtr_{imp} + { } Handle (Handle const& r) = default; Handle (Handle && rr) = default; diff --git a/src/lib/test/testdummy.hpp b/src/lib/test/testdummy.hpp index cbbe32cd4..1f9e7df01 100644 --- a/src/lib/test/testdummy.hpp +++ b/src/lib/test/testdummy.hpp @@ -48,7 +48,7 @@ namespace test{ * i.e. to verify each created instance was properly destroyed after use. */ class Dummy - : util::NonCopyable + : util::MoveAssign { int val_; @@ -57,6 +57,11 @@ namespace test{ static bool _throw_in_ctor; public: + virtual ~Dummy() ///< can act as interface + { + checksum() -= val_; + } + Dummy () : val_(1 + (rand() % 100000000)) { init(); } @@ -65,14 +70,24 @@ namespace test{ : val_(v) { init(); } - virtual ~Dummy() + Dummy (Dummy && oDummy) + : Dummy(0) { - checksum() -= val_; + swap (*this, oDummy); } - virtual long + Dummy& + operator= (Dummy && oDummy) + { + if (&oDummy != this) + swap (*this, oDummy); + return *this; + } - acc (int i) ///< dummy API operation + + /** a dummy API operation */ + virtual long + acc (int i) { return val_+i; } @@ -93,7 +108,7 @@ namespace test{ friend void swap (Dummy& dum1, Dummy& dum2) ///< checksum neutral { - std::swap(dum1.val_, dum2.val_); + std::swap (dum1.val_, dum2.val_); } static long& diff --git a/src/vault/gear/nop-job-functor.hpp b/src/vault/gear/nop-job-functor.hpp index 98ac7c752..38bb525eb 100644 --- a/src/vault/gear/nop-job-functor.hpp +++ b/src/vault/gear/nop-job-functor.hpp @@ -63,7 +63,7 @@ namespace gear { JobKind getJobKind() const { - return TEST_JOB; + return META_JOB; } std::string diff --git a/src/vault/gear/special-job-fun.hpp b/src/vault/gear/special-job-fun.hpp new file mode 100644 index 000000000..a453ad16e --- /dev/null +++ b/src/vault/gear/special-job-fun.hpp @@ -0,0 +1,197 @@ +/* + SPECIAL-JOB-FUN.hpp - a one-time render job to do something special + + Copyright (C) Lumiera.org + 2023, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file special-job-fun.hpp + ** A configurable one-time job to invoke some _special_ function. + ** The actual operation is configured as λ-function and the instance + ** manages itself into heap storage and automatically destroys itself + ** after the predetermined invocation. The intended usage is to supply + ** a specifically wired one-time »fire-and-forget« action to the Scheduler + ** as answer so some special processing situation. The front-end handle + ** SpecialJobFun itself is disposable and only serves as builder; even + ** after the front-end is gone, the actual JobFunctor will maintain + ** one self-reference — unless it is invoked... + ** @warning obviously this is a dangerous tool; the user *must ensure* + ** that it is at most *invoked once* — after that, the instance + ** will self-destroy, leaving a dangling reference. + ** @todo WIP 12/2023 invented to help with Scheduler load testing as + ** part of the »Playback Vertical Slice« — this idea however + ** might be generally useful to handle one-time adjustments + ** from within a play- or planning process. + ** @see SpecialJobFun_test + ** @see TestChainLoad::ScheduleCtx::continuation() usage example + */ + + +#ifndef VAULT_GEAR_SPECIAL_JOB_FUN_H +#define VAULT_GEAR_SPECIAL_JOB_FUN_H + + + +#include "lib/handle.hpp" +#include "vault/gear/nop-job-functor.hpp" +#include "lib/time/timevalue.hpp" +#include "lib/format-string.hpp" +#include "lib/format-obj.hpp" +#include "lib/meta/util.hpp" + +#include +#include +#include + + +namespace vault{ +namespace gear { + namespace err = lumiera::error; + + using util::_Fmt; + using lib::meta::disable_if_self; + using std::move; + using std::forward; + using std::make_shared; + + + /** + * Interface: JobFunctor configured to invoke a function a limited number of times. + */ + class SpecialFunPrototype + : public NopJobFunctor + { + public: + virtual uint remainingInvocations() const =0; + }; + + + + + /** + * Front-end to configure a special job functor for one-time use. + */ + class SpecialJobFun + : protected lib::Handle + { + using _Handle = lib::Handle; + + template + class SpecialExecutor + : SpecialFunPrototype + { + FUN fun_; + uint lives_{1}; + _Handle selfHook_; + + public: + explicit + SpecialExecutor (FUN theFun) + : fun_{move (theFun)} + , selfHook_(this) + { } + + /** @internal allow the front-end to attach + * itself to the self-managing hook */ + friend _Handle& + selfAttached (SpecialExecutor* instance) + { + REQUIRE (instance->selfHook_); + REQUIRE (instance->remainingInvocations()); + // expose the self-managing handle + return instance->selfHook_; + } + + + uint + remainingInvocations() const + { + return lives_; + } + + + /* === JobFunctor Interface === */ + + void + invokeJobOperation (JobParameter param) override + { + if (not remainingInvocations()) + throw err::Logic{"invoking deceased SpecialJobFun" + ,err::LUMIERA_ERROR_LIFECYCLE}; + fun_(param); + + if (not --lives_) + selfHook_.close(); + } // Suicide. + + std::string + diagnostic() const override + { + return _Fmt{"SpecialJob(%d)-%s"} + % lives_ + % util::showHash(size_t(this), 2); + } + }; + + + + public: + SpecialJobFun() = default; + + /** + * Establish a new SpecialJobFun variation + * directly by wrapping a given functor. + * The JobFunctor instance itself will be heap allocated. + * @warning while a direct reference to this JobFunctor can be + * obtained through conversion, this reference must not be + * used after invoking the job one single time; after that, + * this reference is dangling and using it further will + * lead to SEGFAULT or memory corruption. + */ + template> + explicit + SpecialJobFun (FUN&& someFun) + : _Handle{selfAttached (new SpecialExecutor(forward (someFun)))} + { } + + // standard copy operations acceptable + + explicit + operator bool() const + { + return _Handle::isValid() + and 0 < _Handle::impl().remainingInvocations(); + } + + operator JobClosure&() const + { + REQUIRE (operator bool()); + return _Handle::impl(); + } + + long + use_count() const + { + return _Handle::smPtr_.use_count(); + } + }; + + +}} // namespace vault::gear +#endif /*VAULT_GEAR_SPECIAL_JOB_FUN_H*/ diff --git a/tests/32scheduler.tests b/tests/32scheduler.tests index 0c2a16089..c183fd181 100644 --- a/tests/32scheduler.tests +++ b/tests/32scheduler.tests @@ -44,6 +44,12 @@ END +TEST "Self-managed one-time Job" SpecialJobFun_test < + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file special-job-fun-test.cpp + ** unit test \ref SpecialJobFun_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +//#include "vault/real-clock.hpp" +//#include "lib/time/timevalue.hpp" +#include "vault/gear/special-job-fun.hpp" +#include "lib/format-cout.hpp" ////////////////////////////////////TODO Moo-oh +#include "lib/test/diagnostic-output.hpp"//////////////////////////TODO TOD-oh +#include "lib/test/testdummy.hpp" +//#include "lib/util.hpp" + +//#include +//#include + +//using lib::time::Time; +//using lib::time::FSecs; + +//using util::isnil; +//using util::isSameObject; +//using lib::test::randStr; +//using lib::test::randTime; +using lib::test::Dummy; +//using std::array; + + +namespace vault{ +namespace gear { +namespace test { + + namespace { // shorthands and parameters for test... + + + }//(End)test definitions + + + + + /*****************************************************************//** + * @test verify a self-managing one-time render job functor. + * @see TestChainLoad_test::usageExample + * @see TestChainLoad::ScheduleCtx::continuation() + * @see special-job-fun.hpp + */ + class SpecialJobFun_test : public Test + { + + virtual void + run (Arg) + { + simpleUsage(); + verifyLifecycle(); + } + + + /** @test demonstrate simple usage by λ-binding + * @todo WIP 12/23 🔁 define ⟶ ✔ implement + */ + void + simpleUsage() + { + bool hit{false}; // create directly from λ + SpecialJobFun specialFun{[&](JobParameter){ hit=true; }}; + + CHECK (specialFun); + Job funJob{specialFun + ,InvocationInstanceID() + ,Time::ANYTIME + }; + + funJob.triggerJob(); + CHECK (hit); + CHECK (not specialFun); + } + + + + /** @test verify storage and lifecycle management + * - use a instance-tracking marker implanted into the functor + * - verify no memory is leaked and the tracker instance is deallocated + * - verify the single tracker instance indeed lives in the JobFunctor + * - investigate the usage count of the front-end handle + * - verify the front-end can be copied without impact on the JobFunctor + * - verify the heap allocated functor keeps itself alive + * even when the front-end handle is already gone. + * - verify the functor de-allocates itself after latst invocation + * @todo WIP 12/23 ✔ define ⟶ ✔ implement + */ + void + verifyLifecycle() + { + CHECK (0 == Dummy::checksum()); + { // Note: ▽▽▽ tracker in λ-closure + SpecialJobFun funTrack{[tracker=Dummy(23)] + (JobParameter param) mutable + { + int mark = param.invoKey.part.a; + tracker.setVal (mark); + }}; // △△△ invocation should alter checksum + + // one Dummy instance was implanted + CHECK (23 == Dummy::checksum()); + InvocationInstanceID hiddenMessage; + hiddenMessage.part.a = 55; + Job funJob{funTrack + ,hiddenMessage + ,Time::ANYTIME + }; + + CHECK (23 == Dummy::checksum()); + funJob.triggerJob(); + CHECK (55 == Dummy::checksum()); // the `funJob` front-end handle still keeps it alive + } // but when this front-end goes out of scope... + CHECK (0 == Dummy::checksum()); // the implanted tracker is also gone + + { // another investigation with the same technique... + auto trackingLambda = [tracker=Dummy(23)] + (JobParameter param) mutable + { + int mark = param.invoKey.part.a; + tracker.setVal (mark); + }; + CHECK (23 == Dummy::checksum()); + + SpecialJobFun frontEnd{move(trackingLambda)}; // this time the λ is moved in.. + CHECK (23 == Dummy::checksum()); // the embedded tracker was copied into the Functor in heap memory + CHECK (2 == frontEnd.use_count()); // Note: both the front-end and the Functor in heap hold a use-reference + + auto otherHandle = frontEnd; // copy of front-end... + CHECK (3 == frontEnd.use_count()); // ...so there are three usages of the front-end handle now + CHECK (23 == Dummy::checksum()); // ...but only one tracker instance (in heap) + + frontEnd = SpecialJobFun(); // re-assign one front-end handle with an empty instance + CHECK (0 == frontEnd.use_count()); // thus `frontEnd` is no longer attached to the active instance + CHECK (2 == otherHandle.use_count()); // but the other copy still is + CHECK (not frontEnd); + CHECK (otherHandle); + + InvocationInstanceID hiddenMessage; + hiddenMessage.part.a = 55; + Job funJob{otherHandle // Note: don't pass the handle here, rather a JobFunctor& is extracted + ,hiddenMessage + ,Time::ANYTIME + }; + + CHECK (2 == otherHandle.use_count()); + CHECK (23 == Dummy::checksum()); + + otherHandle = SpecialJobFun(); // now kill even the last front-end handle we had + CHECK (0 == otherHandle.use_count()); // thus _we_ have no way to reach the Functor in heap + CHECK (23 == Dummy::checksum()); // yet it stays alive, since it was not invoked yet + + funJob.triggerJob(); // after invocation, the Functor in heap memory self-destructs + CHECK (0 == Dummy::checksum()); // since it did hold the last reference + } + CHECK (0 == Dummy::checksum()); + } + }; + + + /** Register this test class... */ + LAUNCHER (SpecialJobFun_test, "unit engine"); + + + +}}} // namespace vault::gear::test diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index b9a2b19a5..3efad4c0b 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -101304,8 +101304,49 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + +

+ soll per Schedule als Letztes laufen +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -101389,15 +101430,16 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + + - + @@ -101428,7 +101470,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -101453,7 +101495,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -101479,14 +101522,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

- +
- - - + + + +