diff --git a/src/proc/control/command-impl-clone-builder.hpp b/src/proc/control/command-impl-clone-builder.hpp new file mode 100644 index 000000000..6d185db22 --- /dev/null +++ b/src/proc/control/command-impl-clone-builder.hpp @@ -0,0 +1,220 @@ +/* + COMMAND-IMPL-CLONE-BUILDER.hpp - Cloning command implementation without disclosing concrete type + + Copyright (C) Lumiera.org + 2009, 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 command-impl-clone-builder.hpp + ** Helper for creating an implementation clone, based on the visitor pattern. + ** This file deals with the problem of creating a clone from top level without + ** any specific type information. While generally this means passing down the + ** allocation interface, the specific problem here is that multiple parts of the + ** command implementation need to be cloned and re-wired with the cloned partners, + ** which requires re-creating the specifically typed context used at initial setup. + ** + ** Ticket #301 : it may well be that the need for such a facility is a symptom of + ** misaligned design, but I rather doubt so -- because both the memento holder and + ** the command closure need a specifically typed context, and there is no reason + ** for combining them into a single facility. + ** + ** @see CommandRegistry#createCloneImpl + ** @see CommandImpl + ** @see ArgumentHolder#createClone + ** @see command-clone-builder-test.cpp + ** + */ + + + +#ifndef CONTROL_COMMAND_IMPL_CLONE_BUILDER_H +#define CONTROL_COMMAND_IMPL_CLONE_BUILDER_H + +//#include "proc/control/command.hpp" +//#include "proc/control/command-closure.hpp" +#include "proc/control/command-mutation.hpp" +#include "lib/typed-allocation-manager.hpp" +//#include "lib/bool-checkable.hpp" + +#include +//#include + +#include +//#include + + +namespace control { + + using lib::TypedAllocationManager; +// using std::tr1::function; + using std::tr1::shared_ptr; + + + + /** + * Visitor to support creating a CommandImpl clone. + * Created and managed by CommandRegistry, on clone creation + * an instance of this builder object is passed down to re-gain + * a fully typed context, necessary for re-wiring the undo functors + * and the memento storage within the cloned parts. + */ + class CommandImplCloneBuilder +// : public lib::BoolCheckable + { + Mutation do_; + UndoMutation undo_; + + shared_ptr pClo_; + + HandlingPattern::ID defaultPatt_; + + + template + struct _Type + { + typedef typename ARG::SIG_op SIG_op; + typedef typename ARG::SIG_cap SIG_cap; + typedef typename ARG::SIG_undo SIG_undo; + + typedef function Func_op; + typedef function Func_cap; + typedef function Func_undo; + }; +#define _TY(_ID_) typename _Type::_ID_ + + public: + /** build a new implementation frame, and do the initial wiring. + * On the interface the specific type is discarded afterwards. + * This information is still kept though, as encoded into the vtable + * of the embedded FunErasure objects holding the command operation + * and undo functors, and the vtable of the embedded CmdClosure */ + template + CommandImpl (shared_ptr pArgHolder + ,_TY (Func_op) const& operFunctor + ,_TY (Func_cap) const& captFunctor + ,_TY (Func_undo) const& undoFunctor + ) + : do_(operFunctor) + , undo_(pArgHolder->tie (undoFunctor, captFunctor)) + , pClo_(pArgHolder) + , defaultPatt_(HandlingPattern::defaultID()) + { } + +#undef _TY + + + ~CommandImpl(); + + + /** cloning service for the CommandRegistry: + * effectively this is a copy ctor, but as we rely + * on a argument holder (without knowing the exact type), + * we need to delegate the cloning of the arguments down + * while providing a means of allocating storage for the clone */ + CommandImpl (CommandImpl const& orig, TypedAllocationManager& storageManager) + : do_(orig.do_) + , undo_(orig.undo_) + , pClo_(orig.pClo_->createClone(storageManager)) + , defaultPatt_(orig.defaultPatt_) + { } + + + void + setArguments (Arguments& args) + { + pClo_->bindArguments(args); + } + + void invokeOperation() { do_(*pClo_); } + void invokeCapture() { undo_.captureState(*pClo_); } + void invokeUndo() { undo_(*pClo_); } + + + + typedef HandlingPattern::ID PattID; + + PattID + getDefaultHandlingPattern() const + { + return defaultPatt_; + } + + /** define a handling pattern to be used by default + * @return ID of the currently defined default pattern */ + PattID + setHandlingPattern (PattID newID) + { + PattID currID = defaultPatt_; + defaultPatt_ = newID; + return currID; + } + + + + /* === diagnostics === */ + + bool + isValid() const ///< validity self-check: is basically usable. + { + return bool(pClo_) + && HandlingPattern::get(defaultPatt_).isValid(); + } + + bool + canExec() const ///< state check: sufficiently defined to be invoked + { + return isValid() + && pClo_->isValid(); + } + + bool + canUndo() const ///< state check: has undo state been captured? + { + return isValid() && pClo_->isCaptured(); + } + + + + friend bool + operator== (CommandImpl const& ci1, CommandImpl const& ci2) + { + return (ci1.do_ == ci2.do_) +// && (ci1.undo_ == ci2.undo_) // causes failure regularly, due to the missing equality on boost::function. See Ticket #294 + && (ci1.defaultPatt_ == ci2.defaultPatt_) + && (ci1.canExec() == ci2.canExec()) + && (ci1.canUndo() == ci2.canUndo()) + && (ci1.pClo_->equals(*ci2.pClo_)) + ; + } + + friend bool + operator!= (CommandImpl const& ci1, CommandImpl const& ci2) + { + return !(ci1==ci2); + } + }; + + + + + +} // namespace control +#endif diff --git a/tests/45controller.tests b/tests/45controller.tests index 86b76261e..0732dae4c 100644 --- a/tests/45controller.tests +++ b/tests/45controller.tests @@ -55,6 +55,11 @@ return: 0 END +PLANNED "Opaque cloning of implementation" CommandCloneBuilder_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. + +* *****************************************************/ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +//#include "proc/control/command-def.hpp" +#include "proc/control/command-registry.hpp" +#include "proc/control/command-impl-clone-builder.hpp" +#include "lib/symbol.hpp" +#include "lib/util.hpp" + +#include "proc/control/test-dummy-commands.hpp" + +#include + + +namespace control { +namespace test { + + + using std::tr1::function; + using util::isSameObject; + using lib::Symbol; + + + namespace { // test data and helpers... + + Symbol TEST_CMD = "test.command1.1"; + Symbol TEST_CMD2 = "test.command1.2"; + } + + + + /******************************************************************************** + * @test check creation of a command implementation clone from top level, + * without disclosing specific type information about the involved closure. + * This includes verifying sane allocation management. + * @note this test covers a very specific low-level perspective, but on an + * integration level, including TypedAllocationManager, CommandRegistry, + * CommandImpl, CmdClosure, ArgumentHolder, UndoMutation, MementoTie. + * Closes: Ticket #298 + * + * @see Command + * @see CommandRegistry + * @see command.cpp + * @see command-use1-test.cpp + */ + class CommandCloneBuilder_test : public Test + { + + uint cnt_defs; + uint cnt_inst; + + + virtual void + run (Arg) + { + CommandRegistry& registry = CommandRegistry::instance(); + ASSERT (®istry); + + cnt_defs = registry.index_size(); + cnt_inst = registry.instance_count(); + + // prepare a command definition (prototype) + CommandDef (TEST_CMD) + .operation (command1::operate) + .captureUndo (command1::capture) + .undoOperation (command1::undoIt) + .bind(123); + + // this command definition is + // represented internally by a prototype instance + ASSERT (++cnt_inst == registry.instance_count()); + ASSERT (++cnt_defs == registry.index_size()); + + checkRegistration (registry); + checkAllocation(registry); + + ASSERT (cnt_inst == registry.instance_count()); + ASSERT (cnt_defs == registry.index_size()); + + Command::remove (TEST_CMD); + ASSERT (--cnt_inst == registry.instance_count()); + } + + + + /** @test verify the index operation. + * Add, search, remove, store copy. + */ + void + checkRegistration (CommandRegistry& registry) + { + ASSERT (cnt_inst == registry.instance_count()); + + Command cmd1 = registry.queryIndex (TEST_CMD); + ASSERT (cmd1); + ASSERT (TEST_CMD == registry.findDefinition(cmd1)); + + Command nonexistant = registry.queryIndex("miraculous"); + ASSERT (!nonexistant); + + // now create a clone, registered under a different ID + Command cmd2 = cmd1.storeDef(TEST_CMD2); + ASSERT (cmd2 == cmd1); + cmd2.bind(54321); + ASSERT (cmd2 != cmd1); + + // this created exactly one additional instance allocation: + ASSERT (1+cnt_inst == registry.instance_count()); + ASSERT (1+cnt_defs == registry.index_size()); + // ...and another index entry + + + Command cmdX = registry.queryIndex(TEST_CMD2); + ASSERT (cmdX == cmd2); + ASSERT (cmdX != cmd1); + + ASSERT (registry.remove(TEST_CMD2)); + ASSERT (!registry.queryIndex(TEST_CMD2)); + ASSERT (cnt_defs == registry.index_size()); // removed from index + ASSERT (1+cnt_inst == registry.instance_count()); //...but still alive + + // create a new registration.. + registry.track(TEST_CMD2, cmd2); + ASSERT (registry.queryIndex(TEST_CMD2)); + ASSERT (1+cnt_defs == registry.index_size()); // again holding two distinct entries + ASSERT (cmdX == cmd2); + ASSERT (cmdX != cmd1); + + ASSERT (TEST_CMD == registry.findDefinition(cmd1)); + ASSERT (TEST_CMD2 == registry.findDefinition(cmd2)); + ASSERT (TEST_CMD2 == registry.findDefinition(cmdX)); + + ASSERT ( registry.remove(TEST_CMD2)); + ASSERT (!registry.remove("miraculous")); + + ASSERT (!registry.queryIndex(TEST_CMD2)); + ASSERT ( registry.queryIndex(TEST_CMD)); + ASSERT (cnt_defs == registry.index_size()); // the index entry is gone, + + ASSERT (1+cnt_inst == registry.instance_count()); // but the allocation still lives + cmdX.close(); + ASSERT (1+cnt_inst == registry.instance_count()); + cmd2.close(); + ASSERT (0+cnt_inst == registry.instance_count()); // ...as long as it's still referred + } + + + + /** @test verify the allocation/de-allocation handling as + * embedded into the CommandRegistry operation. + * Simulates on low level what normally happens + * during command lifecycle. + */ + void + checkAllocation (CommandRegistry& registry) + { + // simulate what normally happens within a CommandDef + typedef void Sig_oper(int); + typedef long Sig_capt(int); + typedef void Sig_undo(int,long); + + function o_Fun (command1::operate); + function c_Fun (command1::capture); + function u_Fun (command1::undoIt); + + ASSERT (o_Fun && c_Fun && u_Fun); + ASSERT (cnt_inst == registry.instance_count()); + + // when the CommandDef is complete, it issues the + // allocation call to the registry behind the scenes.... + + typedef shared_ptr PImpl; + + PImpl pImpl = registry.newCommandImpl(o_Fun,c_Fun,u_Fun); + ASSERT (1+cnt_inst == registry.instance_count()); + + ASSERT (pImpl); + ASSERT (pImpl->isValid()); + ASSERT (!pImpl->canExec()); + ASSERT (1 == pImpl.use_count()); // no magic involved, we hold the only instance + + PImpl clone = registry.createCloneImpl(*pImpl); + ASSERT (clone->isValid()); + ASSERT (!clone->canExec()); + ASSERT (1 == clone.use_count()); + ASSERT (1 == pImpl.use_count()); + ASSERT (2+cnt_inst == registry.instance_count()); + + ASSERT (!isSameObject (*pImpl, *clone)); + ASSERT (*pImpl == *clone); + + ASSERT (!pImpl->canExec()); + typedef Types ArgType; + TypedArguments > arg (Tuple(98765)); + pImpl->setArguments(arg); + ASSERT (pImpl->canExec()); + + ASSERT (!clone->canExec()); // this proves the clone has indeed a separate identity + ASSERT (*pImpl != *clone); + + // discard the first clone and overwrite with a new one + clone = registry.createCloneImpl(*pImpl); + ASSERT (2+cnt_inst == registry.instance_count()); + ASSERT (*pImpl == *clone); + ASSERT (clone->canExec()); + + clone.reset(); + pImpl.reset(); + // corresponding allocation slots cleared automatically + ASSERT (cnt_inst == registry.instance_count()); + } + + }; + + + /** Register this test class... */ + LAUNCHER (CommandCloneBuilder_test, "function controller"); + + +}} // namespace control::test