rectify-design(#301): disentangle CmdClosure hierarchy

Completely removed the nested hierarchy, where
the top-level implementation forwarded to yet another
sub-implementation of the same interface. Rather, this
sub-implementation (OpClosure) is now a mere implementation
detail class without VTable, and without half-baked
re-implementation of the CmdClosure interface. And the
state-switch from unbound to bound arguments is now
implemented as a plain-flat boolean flag, instead of
hiding it in the VTable.

To make this possible, without having to rewrite lots of
tests, I've created a clone of StorageHolder as a
"proof-of-concept" dummy implementation, for the sole
purpose of writing test fixtures. This one behaves
similar to the real-world thing, but cares only
for closing the command operation and omits all
the gory details of memento capturing and undo.
This commit is contained in:
Fischlurch 2016-02-07 01:41:40 +01:00
parent 9515e45723
commit e0f866092d
10 changed files with 300 additions and 115 deletions

View file

@ -468,7 +468,10 @@ namespace func{
/**
* Closure-creating template.
* @note we take functor objects \em and parameters by reference
* The instance is linked (reference) to a given concrete argument tuple.
* A functor with a matching signature may then either be _closed_ over
* this argument values, or even be invoked right away with theses arguments.
* @warning we take functor objects _and parameters_ by reference
*/
template<typename SIG>
class TupleApplicator

View file

@ -101,9 +101,6 @@ namespace control {
class CommandImplCloneBuilder;
class CmdClosure;
typedef std::shared_ptr<CmdClosure> PClo;
/** Interface */
@ -124,17 +121,7 @@ namespace control {
};
class AbstractClosure
: public CmdClosure
{
bool isValid() const override { return false; }
bool isCaptured() const override { return false; }
void accept (CommandImplCloneBuilder&) const override {}
};
using PClo = std::shared_ptr<CmdClosure>;

View file

@ -110,14 +110,14 @@ namespace control {
* of the embedded FunErasure objects holding the command operation
* and undo functors, and the vtable of the embedded CmdClosure */
template<typename ARG>
CommandImpl (shared_ptr<ARG> pArgHolder
CommandImpl (shared_ptr<ARG> pStorageHolder
,_TY (Func_op) const& operFunctor
,_TY (Func_cap) const& captFunctor
,_TY (Func_undo) const& undoFunctor
)
: do_(operFunctor)
, undo_(pArgHolder->tie (undoFunctor, captFunctor))
, pClo_(pArgHolder)
, undo_(pStorageHolder->tie (undoFunctor, captFunctor))
, pClo_(pStorageHolder)
, defaultPatt_(HandlingPattern::defaultID())
{ }

View file

@ -126,6 +126,7 @@ namespace control {
ParamAccessor& operator= (ParamAccessor const&) =delete;
ParamAccessor& operator= (ParamAccessor&&) =delete;
ParamAccessor (ParamAccessor const&) =default;
ParamAccessor (ParamAccessor&&) =default;
////////////////////TODO the recursion-end of the access operations goes here
@ -151,7 +152,6 @@ namespace control {
*/
template<typename SIG>
class OpClosure
: public AbstractClosure
{
using Args = typename FunctionSignature< function<SIG> >::Args;
using Builder = BuildTupleAccessor<ParamAccessor, Args>;
@ -159,45 +159,36 @@ namespace control {
using ParamStorageTuple =typename Builder::Product;
ParamStorageTuple params_;
protected:
OpClosure()
: params_(Tuple<Args>())
{ }
bool activated_;
public:
using ArgTuple = Tuple<Args>;
OpClosure()
: params_(Tuple<Args>())
, activated_(false)
{ }
explicit
OpClosure (ArgTuple const& args)
: params_(args)
, activated_(true)
{ }
/** create a clone copy of this, without disclosing the exact type */
PClo
createClone (TypedAllocationManager& storageManager)
/** we deliberately support immutable types as command arguments */
OpClosure& operator= (OpClosure const&) =delete;
OpClosure& operator= (OpClosure&&) =delete;
OpClosure (OpClosure const&) =default;
OpClosure (OpClosure&&) =default;
bool
isValid () const
{
return storageManager.create<OpClosure> (*this);
return activated_;
}
/** assign a new parameter tuple to this */
void
bindArguments (Arguments& args) override
{
//params_ = args.get<ArgTuple>();
}
/** assign a new set of parameter values to this.
* @note the values are passed packaged into a sequence
* of GenNode elements. This is the usual way
* arguments are passed from the UI-Bus
*/
void
bindArguments (lib::diff::Rec const& paramData) override
{
//params_ = buildTuple<Args> (paramData);
}
/** Core operation: use the embedded argument tuple for invoking a functor
* @param unboundFunctor an function object, whose function arguments are
@ -208,7 +199,7 @@ namespace control {
* Thus this function can't be const.
*/
void
invoke (CmdFunctor const& unboundFunctor) override
invoke (CmdFunctor const& unboundFunctor)
{
TupleApplicator<SIG> apply_this_arguments(params_);
apply_this_arguments (unboundFunctor.getFun<SIG>());
@ -216,7 +207,7 @@ namespace control {
operator string() const override
operator string() const
{
std::ostringstream buff;
params_.dump (buff << "OpClosure(" );
@ -230,19 +221,9 @@ namespace control {
}
bool isValid () const override { return true; }
/// Supporting equality comparisons...
friend bool operator== (OpClosure const& c1, OpClosure const& c2) { return compare (c1.params_, c2.params_); }
friend bool operator!= (OpClosure const& c1, OpClosure const& c2) { return not (c1 == c2); }
bool
equals (CmdClosure const& other) const override
{
const OpClosure* toCompare = dynamic_cast<const OpClosure*> (&other);
return (toCompare)
&& (*this == *toCompare);
}
};

View file

@ -0,0 +1,223 @@
/*
COMMAND-SIMPLE-CLOSURE.hpp - demo implementation of command closure
Copyright (C) Lumiera.org
2016, Hermann Vosseler <Ichthyostega@web.de>
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 command-simple-closure.hpp
** Proof-of-concept implementation of CmdClosure.
** This is used for test only, to invoke an arbitrary matching functor with
** arguments stored embedded within this closure. In the real system, a more
** [elaborate version](\ref StorageHolder) of the same concept is used, with
** the additional complication of managing the UNDO operation as well.
**
** \par historical note
** This proof-of-concept variation was split off in an attempt to improve the
** overall design of the command / closure system. The original design had the
** embedded argument holder also implement the CmdClosure interface, which is
** a clever implementation and code-reuse trick, but otherwise caused confusion.
**
** @see Ticket #301
** @see CommandMutation_test
** @see StorageHolder
** @see OpClosure
**
*/
#ifndef CONTROL_COMMAND_SIMPLE_CLOSURE_H
#define CONTROL_COMMAND_SIMPLE_CLOSURE_H
#include "lib/typed-allocation-manager.hpp"
#include "proc/control/command-op-closure.hpp"
#include "lib/opaque-holder.hpp"
#include <string>
namespace proc {
namespace control {
using lib::InPlaceBuffer;
using std::string;
/**
* Dummy / proof-of-concept implementation of CmdClosure.
* It is a specifically typed subclass, which serves to hold
* storage for the concrete invocation arguments within an
* inline buffer.
* @note for demonstration and unit testing
* @see StorageHolder real world implementation
*/
template<typename SIG>
class SimpleClosure
: public CmdClosure
{
using ArgHolder = OpClosure<SIG>;
using ArgumentBuff = InPlaceBuffer<ArgHolder>;
using ArgTuple = typename ArgHolder::ArgTuple;
using Args = typename Types<ArgTuple>::Seq;
/* ====== in-place argument storage ====== */
ArgumentBuff arguments_;
/* ==== proxied CmdClosure interface ==== */
public:
virtual bool
isValid () const override
{
return arguments_->isValid();
}
virtual bool
isCaptured() const override
{
return false;
}
/** assign a new parameter tuple to this */
virtual void
bindArguments (Arguments& args) override
{
storeTuple (args.get<ArgTuple>());
}
/** assign a new set of parameter values to this.
* @note the values are passed packaged into a sequence
* of GenNode elements. This is the usual way
* arguments are passed from the UI-Bus
*/
virtual void
bindArguments (lib::diff::Rec const& paramData) override
{
storeTuple (buildTuple<Args> (paramData));
}
virtual void
invoke (CmdFunctor const& func) override
{
if (!isValid())
throw lumiera::error::State ("Lifecycle error: can't bind functor, "
"command arguments not yet provided",
LUMIERA_ERROR_UNBOUND_ARGUMENTS);
arguments_->invoke(func);
}
virtual
operator string() const override
{
return "Command-Closure{ arguments="
+ (arguments_->isValid()? string(*arguments_) : "unbound")
+ " }"
;
}
/** per default, all data within StorageHolder
* is set up in \em empty state. Later on, the
* command arguments are to be provided by #bind ,
* whereas the undo functions will be wired by #tie
*/
SimpleClosure ()
: arguments_()
{ }
explicit
SimpleClosure (ArgTuple const& args)
: arguments_()
{
storeTuple (args);
}
SimpleClosure (SimpleClosure const& oAh)
: arguments_()
{
if (oAh.arguments_->isValid()) // don't clone garbage from invalid arguments
arguments_.template create<ArgHolder> (*oAh.arguments_);
}
void
accept (CommandImplCloneBuilder&) const override
{
NOTREACHED();
}
/** has undo state capturing been invoked? */
bool canUndo () const { return false; }
bool empty () const { return !arguments_->isValid(); }
/** store a new argument tuple within this StorageHolder,
* discarding any previously stored arguments */
void
storeTuple (ArgTuple const& argTup)
{
arguments_.template create<ArgHolder> (argTup);
}
bool
equals (CmdClosure const& other) const override
{
const SimpleClosure* toCompare = dynamic_cast<const SimpleClosure*> (&other);
return (toCompare)
and (*this == *toCompare);
}
/// Supporting equality comparisons...
friend bool
operator== (SimpleClosure const& a1, SimpleClosure const& a2)
{
return (a1.arguments_->isValid() == a2.arguments_->isValid())
and (*a1.arguments_ == *a2.arguments_)
;
}
friend bool
operator!= (SimpleClosure const& a1, SimpleClosure const& a2)
{
return not (a1 == a2);
}
};
}} // namespace proc::control
#endif /*CONTROL_COMMAND_SIMPLE_CLOSURE_H*/

View file

@ -61,23 +61,6 @@ namespace control {
using std::string;
namespace { // empty state marker objects for StorageHolder
template<typename SIG>
struct MissingArguments
: OpClosure<SIG>
{
virtual bool isValid () const override { return false; }
};
template<typename SIG, typename MEM>
struct UntiedMemento
: MementoTie<SIG,MEM>
{ };
} // (END) impl details / empty state marker objects
@ -94,17 +77,13 @@ namespace control {
*/
template<typename SIG, typename MEM>
class StorageHolder
: public AbstractClosure
: public CmdClosure
{
/** copy construction allowed(but no assignment)*/
StorageHolder& operator= (StorageHolder const&);
using ArgHolder = OpClosure<SIG>;
using MemHolder = MementoTie<SIG,MEM>;
using ArgumentBuff = InPlaceBuffer<ArgHolder, sizeof(ArgHolder), MissingArguments<SIG>>;
using MementoBuff = InPlaceBuffer<MemHolder, sizeof(MemHolder), UntiedMemento<SIG,MEM>>;
using ArgumentBuff = InPlaceBuffer<ArgHolder>;
using MementoBuff = InPlaceBuffer<MemHolder>;
using ArgTuple = typename ArgHolder::ArgTuple;
using Args = typename Types<ArgTuple>::Seq;
@ -169,7 +148,7 @@ namespace control {
operator string() const override
{
return "Command-State{ arguments="
+ (*arguments_? string(*arguments_) : "unbound")
+ (arguments_->isValid()? string(*arguments_) : "unbound")
+ ", "+string(*memento_)+"}"
;
}
@ -186,7 +165,10 @@ namespace control {
, memento_()
{ }
/** copy construction allowed(but no assignment) */
/** copy construction allowed(but no assignment).
* @remarks rationale is to support immutable argument values,
* which means default/copy construction is OK
*/
StorageHolder (StorageHolder const& oAh)
: arguments_()
, memento_()
@ -198,6 +180,12 @@ namespace control {
memento_.template create<MemHolder> (*oAh.memento_);
}
/** copy construction allowed(but no assignment)*/
StorageHolder& operator= (StorageHolder const&) = delete;
/** assist with creating a clone copy;
* this results in invocation of the copy ctor */
void
@ -259,7 +247,7 @@ namespace control {
{
const StorageHolder* toCompare = dynamic_cast<const StorageHolder*> (&other);
return (toCompare)
&& (*this == *toCompare);
and (*this == *toCompare);
}
/// Supporting equality comparisons...

View file

@ -110,13 +110,11 @@ namespace control {
}
protected:
public:
MementoTie()
: MementoTie (function<SIG_undo>(), function<SIG_cap>())
{ }
public:
/** creates an execution context tying together the provided functions.
* Bound copies of these functors may be pulled from the MementoTie,
* in order to build the closures (with the concrete operation arguments)

View file

@ -46,9 +46,9 @@ END
TEST "Command functor and UNDO functor" CommandMutation_test <<END
out: empty placeholder closure: OpClosure\(0\)
out: param values: OpClosure\(23\)
out: saved state: 11
out-lit: empty placeholder closure: Command-Closure{ arguments=unbound }
out-lit: param values: Command-Closure{ arguments=OpClosure(23) }
out-lit: saved state: 11
return: 0
END

View file

@ -27,6 +27,7 @@
#include "proc/control/command-mutation.hpp"
#include "proc/control/argument-erasure.hpp"
#include "proc/control/command-storage-holder.hpp"
#include "proc/control/command-simple-closure.hpp"
#include "proc/control/memento-tie.hpp"
#include "lib/meta/tuple-helper.hpp"
#include "lib/format-cout.hpp"
@ -86,16 +87,18 @@ namespace test {
using ArgTuple = Tuple<Types<char>>;
using ArgHolder = OpClosure<Sig_oper>;
using MemHolder = MementoTie<Sig_oper, string>;
using Closure = SimpleClosure<Sig_oper>;
}
/*************************************************************************************//**
* @test cover command equality detection. Two commands are deemed equivalent, if they
* - build on the same Mutation functors
* - are either both incomplete or
* - are bound to equivalent arguments
* - hold equivalent undo state (memento)
* @test cover command equality detection.
* Two commands are deemed equivalent, if they
* - build on the same Mutation functors
* - are either both incomplete or
* - are bound to equivalent arguments
* - hold equivalent undo state (memento)
* To conduct this test, we set up two sets of functions, and then build both complete
* command objects and command implementation facilities based on them.
*
@ -171,20 +174,22 @@ namespace test {
verifyClosureEquality()
{
ArgHolder a1 (make_tuple ('a'));
ArgHolder a2 (make_tuple ('z'));
ArgHolder a2 (make_tuple ('a'));
ArgHolder a3 (make_tuple ('z'));
CHECK (a1 == a1);
CHECK (a1 != a2);
CHECK (a2 != a1);
TypedArguments<ArgTuple> newArgs (make_tuple ('z'));
a1.bindArguments(newArgs);
CHECK (a1 == a2);
CHECK (a2 == a1);
CHECK (a1 != a3);
CHECK (a3 != a1);
CHECK (a2 != a3);
CHECK (a3 != a2);
typedef StorageHolder<Sig_oper,string> Storage;
Storage abuff1;
Storage abuff2;
CHECK (abuff1 == abuff2);
TypedArguments<ArgTuple> newArgs (make_tuple ('z'));
abuff1.bindArguments(newArgs);
CHECK (abuff1 != abuff2);
abuff2.bindArguments(newArgs);
@ -194,17 +199,18 @@ namespace test {
UndoMutation umu2 (abuff2.tie (undo_1, capt_1));
CHECK (abuff1 == abuff2); // same capture function, no memento state!
umu1.captureState(a1);
Closure args {make_tuple ('u')};
umu1.captureState(args);
CHECK (abuff1 != abuff2);
umu2.captureState(a1);
umu2.captureState(args);
CHECK (abuff1 == abuff2); // same functions, same memento state
check_ += "fake"; // manipulate the "state" to be captured
umu2.captureState(a1); // capture again...
umu2.captureState(args); // capture again...
CHECK (abuff1 != abuff2); // captured memento differs!
UndoMutation umu3 (abuff2.tie (undo_1, capt_2));
umu3.captureState(a1);
umu3.captureState(args);
CHECK (abuff1 != abuff2); // differing functions detected
}

View file

@ -24,7 +24,7 @@
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "proc/control/command-mutation.hpp"
#include "proc/control/command-storage-holder.hpp"
#include "proc/control/command-simple-closure.hpp"
#include "proc/control/memento-tie.hpp"
#include "lib/meta/tuple-helper.hpp"
#include "lib/meta/typelist.hpp"
@ -90,8 +90,7 @@ namespace test {
}
/** @test check the Mutation functor w#include "lib/meta/typelist.hpp"
hich is bound to our \c testFunc(int) .
/** @test check the Mutation functor which is bound to our `testFunc(int)`.
* Then create a argument closure and use this to invoke the Mutation
* and verify actually \c testFunc(param) is executed.
*/
@ -103,16 +102,16 @@ hich is bound to our \c testFunc(int) .
Mutation functor (funky);
MissingArguments<SIG_fun> nullClosure;
CHECK (!nullClosure);
SimpleClosure<SIG_fun> nullClosure;
CHECK (not nullClosure.isValid());
cout << "empty placeholder closure: " << nullClosure << endl;
VERIFY_ERROR (UNBOUND_ARGUMENTS, functor(nullClosure) );
// now create a real closure....
Tuple<Types<int> > param = std::make_tuple (23);
OpClosure<void(int)> close_over (param);
SimpleClosure<void(int)> closed_over{param};
CmdClosure& closure (close_over);
CmdClosure& closure (closed_over);
CHECK (closure);
cout << "param values: " << closure << endl;
@ -152,12 +151,12 @@ hich is bound to our \c testFunc(int) .
UndoMutation undoFunctor (mementoHolder);
CHECK (!mementoHolder);
MissingArguments<void(void)> nullClosure;
SimpleClosure<void(void)> nullClosure;
VERIFY_ERROR (UNBOUND_ARGUMENTS, undoFunctor(nullClosure) );
VERIFY_ERROR (UNBOUND_ARGUMENTS, undoFunctor.captureState(nullClosure) );
Tuple<Types<> > param;
OpClosure<void()> clo (param);
SimpleClosure<void()> clo{param};
CHECK (!mementoHolder);
VERIFY_ERROR (MISSING_MEMENTO, undoFunctor (clo) );