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
-
+
-
-
-
+
+
+
+