From e9b95e47cf0dca34a2e13bf9808ee99f99334374 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 29 Sep 2009 18:09:34 +0200 Subject: [PATCH] Command handling pattern? test-driven brainstorming --- src/proc/control/handling-pattern.hpp | 1 + tests/45controller.tests | 24 ++- tests/components/Makefile.am | 2 + .../control/handling-pattern-basics-test.cpp | 194 ++++++++++++++++++ .../handling-pattern-standard-impl-test.cpp | 126 ++++++++++++ wiki/renderengine.html | 21 +- 6 files changed, 359 insertions(+), 9 deletions(-) create mode 100644 tests/components/proc/control/handling-pattern-basics-test.cpp create mode 100644 tests/components/proc/control/handling-pattern-standard-impl-test.cpp diff --git a/src/proc/control/handling-pattern.hpp b/src/proc/control/handling-pattern.hpp index 5422bfb31..7ec8befdf 100644 --- a/src/proc/control/handling-pattern.hpp +++ b/src/proc/control/handling-pattern.hpp @@ -96,6 +96,7 @@ namespace control { { SYNC , SYNC_THROW , ASYNC + , DUMMY , NUM_IDS }; diff --git a/tests/45controller.tests b/tests/45controller.tests index 83aa89365..f59d50a1f 100644 --- a/tests/45controller.tests +++ b/tests/45controller.tests @@ -35,13 +35,6 @@ out: RESET...undoIt\(time=00:..:....00\)----memento-:START...doIt\( Time=00:..:. END -TEST "Command functor and UNDO functor" CommandMutation_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.hpp" +#include "proc/control/command-impl.hpp" +#include "proc/control/command-registry.hpp" +#include "proc/control/argument-erasure.hpp" +#include "proc/control/handling-pattern.hpp" +//#include "lib/lumitime.hpp" +#include "lib/symbol.hpp" +//#include "lib/util.hpp" + +#include "proc/control/test-dummy-commands.hpp" + +//#include +//#include +//#include +//#include +#include +//#include + + +namespace control { +namespace test { + + +// using boost::format; +// using boost::str; + //using lumiera::Time; + //using util::contains; + using std::tr1::function; +// using std::tr1::bind; +// using std::string; + using std::rand; + //using std::cout; + //using std::endl; +// using lib::test::showSizeof; +// using util::isSameObject; +// using util::contains; + using lib::Symbol; + + + + //using lumiera::typelist::BuildTupleAccessor; +// using lumiera::error::LUMIERA_ERROR_EXTERNAL; + + namespace { // test data and helpers... + + Symbol TEST_CMD = "test.command1.handling"; + HandlingPattern::ID TEST_PATTERN = HandlingPattern::DUMMY; + } + + typedef shared_ptr PCommandImpl; + typedef HandlingPattern const& HaPatt; + + + + /********************************************************************************** + * @test operate and verify a simple dummy command handling pattern. + * interface. Add/remove a command instance to the index, allocate an + * CommandImpl frame and verify it is removed properly on ref count zero. + * @note this test covers mainly the behaviour of a handling pattern as a concept, + * not so much the behaviour of the (standard) handling pattern implementations. + * + * @see HandlingPattern + * @see CommandRegistry + * @see command.hpp + * @see command-basic-test.cpp + */ + class HandlingPatternBasics_test : public Test + { + + uint cnt_inst; + + + virtual void + run (Arg) + { + CommandRegistry& registry = CommandRegistry::instance(); + ASSERT (®istry); + + cnt_inst = registry.instance_count(); + + { + PCommandImpl pCom = buildTestCommand(registry); + checkExec (pCom); + checkUndo (pCom); + } + + ASSERT (cnt_inst == registry.instance_count()); + } + + + /** create a command implementation frame usable for tests. + * This simulates what normally happens within a CommandDef. + * The created CommandImpl isn't registered, and thus will + * just go away when the smart-ptr leaves scope. + */ + PCommandImpl + buildTestCommand (CommandRegistry& registry) + { + + 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); + + // when the CommandDef is complete, it issues the + // allocation call to the registry behind the scenes.... + + PCommandImpl pImpl = registry.newCommandImpl(o_Fun,c_Fun,u_Fun); + ASSERT (pImpl); + ASSERT (*pImpl); + return pImpl; + } + + + void + checkExec (PCommandImpl com) + { + ASSERT (com); + ASSERT (!com->canExec()); + + typedef Types ArgType; + const int ARGU (1 + rand() % 1000); + Tuple tuple(ARGU); + TypedArguments > arg(tuple); + com->setArguments(arg); + + ASSERT (com->canExec()); + ASSERT (!com->canUndo()); + command1::check_ = 0; + + HaPatt patt = HandlingPattern::get(TEST_PATTERN); + ExecResult res = patt.invoke(*com, TEST_CMD); + + ASSERT (res); + ASSERT (ARGU == command1::check_); + ASSERT (com->canUndo()); + } + + + void + checkUndo (PCommandImpl com) + { + ASSERT (com); + ASSERT (com->canExec()); + ASSERT (com->canUndo()); + + ASSERT (command1::check_ > 0); + + HaPatt ePatt = HandlingPattern::get(TEST_PATTERN); + HaPatt uPatt = ePatt.howtoUNDO(); + ExecResult res = uPatt.invoke(*com, TEST_CMD); + + ASSERT (res); + ASSERT (command1::check_ == 0); + } + + }; + + + /** Register this test class... */ + LAUNCHER (HandlingPatternBasics_test, "function controller"); + + +}} // namespace control::test diff --git a/tests/components/proc/control/handling-pattern-standard-impl-test.cpp b/tests/components/proc/control/handling-pattern-standard-impl-test.cpp new file mode 100644 index 000000000..f9fdf0058 --- /dev/null +++ b/tests/components/proc/control/handling-pattern-standard-impl-test.cpp @@ -0,0 +1,126 @@ +/* + HandlingPatternStandardImpl(Test) - cover the provided standard command handling patterns + + 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. + +* *****************************************************/ + + +#include "lib/test/run.hpp" +//#include "lib/test/test-helper.hpp" +//#include "proc/asset/media.hpp" +//#include "proc/mobject/session.hpp" +//#include "proc/mobject/session/edl.hpp" +//#include "proc/mobject/session/testclip.hpp" +//#include "proc/mobject/test-dummy-mobject.hpp" +//#include "lib/p.hpp" +//#include "proc/mobject/placement.hpp" +//#include "proc/mobject/placement-index.hpp" +//#include "proc/mobject/explicitplacement.hpp" +#include "proc/control/command.hpp" +#include "proc/control/command-impl.hpp" +#include "proc/control/command-registry.hpp" +//#include "proc/control/command-def.hpp" +//#include "lib/lumitime.hpp" +//#include "lib/symbol.hpp" +//#include "lib/util.hpp" + +#include "proc/control/test-dummy-commands.hpp" + +//#include +//#include +//#include +//#include +//#include +//#include + + +namespace control { +namespace test { + + +// using boost::format; +// using boost::str; + //using lumiera::Time; + //using util::contains; + using std::tr1::function; +// using std::tr1::bind; +// using std::string; + //using std::rand; + //using std::cout; + //using std::endl; +// using lib::test::showSizeof; +// using util::isSameObject; +// using util::contains; + +// using session::test::TestClip; +// using lib::Symbol; +// using lumiera::P; + + + //using lumiera::typelist::BuildTupleAccessor; +// using lumiera::error::LUMIERA_ERROR_EXTERNAL; + + namespace { // test data and helpers... + +// Symbol TEST_CMD = "test.command1.1"; +// Symbol TEST_CMD2 = "test.command1.2"; + } + + + + /*********************************************************************** + * @test verify correct behaviour of all the command handling patterns + * provided by the default configuration of a lumiera session. + * - executing quasi synchronous + * - the throw-on-error variant + * - background execution + * - ...? + * @todo define and code those handling patterns; Ticket #210 + * + * @see HandlingPattern + * @see CommandRegistry + * @see command.hpp + * @see command-basic-test.cpp + */ + class HandlingPatternStandardImpl_test : public Test + { + + uint cnt_inst; + + + virtual void + run (Arg) + { + CommandRegistry& registry = CommandRegistry::instance(); + ASSERT (®istry); + + cnt_inst = registry.instance_count(); + + UNIMPLEMENTED ("unit test to cover the standard command handling patterns"); + + ASSERT (cnt_inst == registry.instance_count()); + } + }; + + + /** Register this test class... */ + LAUNCHER (HandlingPatternStandardImpl_test, "function controller"); + + +}} // namespace control::test diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 9a2e98214..2fc4e4a54 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -995,7 +995,7 @@ While generally there is //no limitation// on the number and type of parameters, Usually, parameters should be passed //by value// &mdash; with the exception of target object(s), which are typically bound as MObjectRef, causing them to be resolved at commad execution time (late binding). -
+
Organising any ''mutating'' operations executable by the user (via GUI) by means of the [[command pattern|http://en.wikipedia.org/wiki/Command_pattern]] can be considered //state of the art//&nbsp; today. First of all, it allows to discern the specific implementation operations to be called on one or several objects within the HighLevelModel from the operation requested by the user, the latter being rather a concept. A command can be labeled clearly, executed under controlled circumstances, allowing transactional behaviour.
 
 
@@ -1014,7 +1014,24 @@ While the usual »Memento« implementation might automatically capture the whole
 A command may be [[defined|CommandDefinition]] completely from scratch, or it might just serve as a CommandPrototype with specific targets and parameters. The command could then be serialised and later be recovered and re-bound with the parameters, but usually it will be handed over to the ProcDispatcher, pending execution. When ''invoking'', the handling sequence is to [[log the command|SessionStorage]], then call the ''undo capture function'', followed from calling the actual ''operation function''. After success, the logging and [[undo registration|UndoManager]] is completed. In any case, finally the ''result signal'' (a functor previously stored within the command) is emitted.
 
 By design, commands are single-serving value objects; executing an operation repeatedly requires creating a collection of command objects, one for each invocation. While nothing prevents you from invoking the command operation functor several times, each invocation will overwrite the undo state captrued by the previous invocation. Thus, each command instance should bes seen as the promise (or later the trace) of a single operation execution. In a similar vein, the undo capturing should be defined as to be self sufficient, so that invoking just the undo functor of a single command performes any necessary steps to restore the situation found before invoking the corresponding mutation functor &mdash; of course only //with respect to the topic covered by this command.// So, while commands provide a lot of flexibility and allow to do a multitude of things, certainly there is an intended CommandLifecycle.
-&rarr; more on possible [[command usage scenarios|CommandUsage]]
+&rarr; more on possible [[command usage scenarios|CommandUsage]] +&rarr; more details regarding [[command implementation|CommandImpl]] + +
+
+
Commands are separated in a handle (the {{{control::Command}}}-object), to be used by the client code, and an implementation level, which is managed transparently behind the stages. Client code is assumed to build a CommandDefinition at some point, and from then on to access the command ''by ID'', yielding the command handle.
+Binding of arguments, invocation and UNDO all are accessible through this frontend.
+
+!Infrastructure
+To support this handling scheme, some infrastructure is in place:
+* a command registry maintains the ID &harr; Command relation.
+* indirectly, through a custom alloctaor, the registry is also involved into allocation of the command implementation frame
+* this implementation frame combines
+** an operation mutation and an undo mutation
+** a closure, implemented through an argument holder
+** an undo state capturing mechanism, based on a capturing function provided on definition
+* performing the actual execution is delegated to a handling pattern object, accessed by name.
+
[<img[Structure of Commands|uml/fig135173.png]]