refactor CommandDef, get rid of the possibility of re-defining. YAGNI!
This commit is contained in:
parent
37cd451367
commit
17c7160f02
6 changed files with 127 additions and 109 deletions
|
|
@ -26,14 +26,20 @@
|
|||
** While the header command.hpp contains everything needed for executing and
|
||||
** commands and referring to them, this more heavy-weight header is needed when
|
||||
** \em defining the concrete operations to be encapsulated into a command. To
|
||||
** create a command, you need to provide three functors (for the actual operation,
|
||||
** create a command, you need to provide three functions (for the actual operation,
|
||||
** the undo operation and for capturing undo state prior to invoking the operation).
|
||||
** Besides, you provide a \em binding, thus creating a closure out of these three
|
||||
** function objects and a set of actual parameters. This closure effectively is
|
||||
** the command, which in a last step can be either dispatched, stored or
|
||||
** invoked immediately.
|
||||
** //TODO
|
||||
**
|
||||
**
|
||||
** For actually providing these operations, the client is expected to call the
|
||||
** definition functions in a chained manner ("fluent interface"). When finally all the
|
||||
** required informations are available, a \em prototype object is built and registered
|
||||
** with the CommandRegistry. From this point on, the corresponding Command (frontend object)
|
||||
** can be accessed directly by ID (and only relying on the header command.hpp).
|
||||
**
|
||||
** In addition to the bare definition, it is possible to provide a binding for the command's
|
||||
** parameters immediately during the command definition. Of course it's also possible (and
|
||||
** indeed this is the standard case) to provide these concrete arguments just immediately
|
||||
** prior to invoking the command.
|
||||
**
|
||||
** @see Command
|
||||
** @see Mutation
|
||||
** @see CommandClosure
|
||||
|
|
@ -75,6 +81,8 @@ namespace control {
|
|||
|
||||
using std::tr1::shared_ptr;
|
||||
using std::tr1::function;
|
||||
using std::tr1::bind;
|
||||
using std::tr1::placeholders::_1;
|
||||
using lib::Symbol;
|
||||
using util::cStr;
|
||||
|
||||
|
|
@ -84,6 +92,10 @@ namespace control {
|
|||
using lumiera::typelist::NullType;
|
||||
using lumiera::typelist::Tuple;
|
||||
|
||||
typedef shared_ptr<CommandImpl> ImplInstance;
|
||||
typedef function<Command&(ImplInstance const&)> Activation;
|
||||
|
||||
|
||||
|
||||
namespace stage { ///< helpers for building up a command definition
|
||||
|
||||
|
|
@ -102,6 +114,7 @@ namespace control {
|
|||
CompletedDefinition (Command& definedCommand)
|
||||
: prototype_(definedCommand)
|
||||
{
|
||||
REQUIRE (prototype_);
|
||||
TRACE (command_dbg, "Completed definition of %s.", cStr(prototype_));
|
||||
}
|
||||
|
||||
|
|
@ -134,16 +147,16 @@ namespace control {
|
|||
typedef function<UndoOperationSig> UndoFunc;
|
||||
typedef function<UndoCaptureSig> CaptFunc;
|
||||
|
||||
Command& prototype_;
|
||||
Activation activatePrototype_;
|
||||
OperFunc operFunctor_;
|
||||
CaptFunc captFunctor_;
|
||||
UndoFunc undoFunctor_;
|
||||
|
||||
|
||||
UndoDefinition (Command& underConstruction,
|
||||
UndoDefinition (Activation const& whenComplete,
|
||||
OperFunc const& commandOperation,
|
||||
CaptFunc const& undoCapOperation)
|
||||
: prototype_(underConstruction)
|
||||
: activatePrototype_(whenComplete)
|
||||
, operFunctor_(commandOperation)
|
||||
, captFunctor_(undoCapOperation)
|
||||
, undoFunctor_()
|
||||
|
|
@ -158,15 +171,11 @@ namespace control {
|
|||
REQUIRE (undoFunctor_);
|
||||
REQUIRE (captFunctor_);
|
||||
|
||||
typedef shared_ptr<CommandImpl> ImplInstance;
|
||||
CommandRegistry& registry = CommandRegistry::instance();
|
||||
ImplInstance completedDef = registry.newCommandImpl(operFunctor_
|
||||
,captFunctor_
|
||||
,undoFunctor_);
|
||||
prototype_.activate(completedDef);
|
||||
// activated the instance in the registry
|
||||
ENSURE (prototype_);
|
||||
return CompletedDefinition<SIG> (prototype_);
|
||||
return CompletedDefinition<SIG> (activatePrototype_(completedDef));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -189,11 +198,11 @@ namespace control {
|
|||
template<typename SIG>
|
||||
struct BasicDefinition
|
||||
{
|
||||
Command& prototype_;
|
||||
Activation callback_;
|
||||
function<SIG> operation_;
|
||||
|
||||
BasicDefinition(Command& underConstruction, function<SIG> const& operation)
|
||||
: prototype_(underConstruction)
|
||||
BasicDefinition(Activation const& whenComplete, function<SIG> const& operation)
|
||||
: callback_(whenComplete)
|
||||
, operation_(operation)
|
||||
{ }
|
||||
|
||||
|
|
@ -206,7 +215,7 @@ namespace control {
|
|||
typedef typename BuildUndoDefType<UndoSignature<SIG2> >::Type SpecificUndoDefinition;
|
||||
|
||||
function<UndoCapSig> captureOperation (how_to_capture_UndoState);
|
||||
return SpecificUndoDefinition (prototype_, operation_, captureOperation);
|
||||
return SpecificUndoDefinition (callback_, operation_, captureOperation);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -234,7 +243,7 @@ namespace control {
|
|||
: public lib::BoolCheckable<CommandDef>
|
||||
{
|
||||
Symbol id_;
|
||||
Command& prototype_;
|
||||
Command prototype_;
|
||||
|
||||
public:
|
||||
CommandDef (Symbol cmdID)
|
||||
|
|
@ -247,16 +256,31 @@ namespace control {
|
|||
~CommandDef();
|
||||
|
||||
|
||||
|
||||
template<typename SIG>
|
||||
stage::BasicDefinition<SIG>
|
||||
operation (SIG& operation_to_define)
|
||||
{
|
||||
function<SIG> opera1 (operation_to_define);
|
||||
return stage::BasicDefinition<SIG>(prototype_, opera1);
|
||||
Activation callback_when_defined = bind (&CommandDef::activate, this, _1);
|
||||
|
||||
return stage::BasicDefinition<SIG>(callback_when_defined, opera1);
|
||||
}
|
||||
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
|
||||
private:
|
||||
/** callback from completed command definition stage:
|
||||
* "arm up" the command handle object and register it
|
||||
* with the CommandRegistry. */
|
||||
Command& activate (ImplInstance const& completedDef)
|
||||
{
|
||||
prototype_.activate (id_,completedDef);
|
||||
ENSURE (prototype_);
|
||||
return prototype_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -143,21 +143,24 @@ namespace control {
|
|||
|
||||
|
||||
|
||||
/** register a command (Frontend) under the given ID
|
||||
* @return either the new command, or an already existing
|
||||
* command registered under the given ID */
|
||||
Command&
|
||||
/** register a command (Frontend) under the given ID.
|
||||
* Any previously existing registration is detached from the index
|
||||
*/
|
||||
void
|
||||
track (Symbol cmdID, Command const& commandHandle)
|
||||
{
|
||||
Lock sync(this);
|
||||
|
||||
Command& indexSlot (index_[cmdID]);
|
||||
if (!indexSlot)
|
||||
{
|
||||
indexSlot = commandHandle;
|
||||
ridx_[&indexSlot] = cmdID;
|
||||
}
|
||||
return indexSlot;
|
||||
REQUIRE (commandHandle);
|
||||
if (contains (index_,cmdID) || contains(ridx_, &commandHandle))
|
||||
commandHandle.duplicate_detected(cmdID);
|
||||
|
||||
Command& indexSlot = index_[cmdID];
|
||||
indexSlot = commandHandle;
|
||||
ridx_[&indexSlot] = cmdID;
|
||||
|
||||
ENSURE (contains(ridx_, &indexSlot));
|
||||
ENSURE (contains(index_, cmdID));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -109,41 +109,59 @@ namespace control {
|
|||
}
|
||||
|
||||
|
||||
/** @internal to be invoked by CommandDef when starting a new definition.
|
||||
* @note we hand out a direct reference to the registered object. When this
|
||||
* command is "activated" later on, this deliberately has the side effect
|
||||
* of detaching any further references which may be held outside.
|
||||
* They will continue to live on as "anonymous" commands, until
|
||||
* going out of scope.
|
||||
*/
|
||||
Command&
|
||||
/** @internal just query an existing instance, if any. */
|
||||
Command
|
||||
Command::fetchDef (Symbol cmdID)
|
||||
{
|
||||
return CommandRegistry::instance().queryIndex (cmdID);
|
||||
}
|
||||
|
||||
|
||||
/** @internal to be invoked through CommandDef to make a command ready for use
|
||||
* @throw error::Logic when this is already activated. */
|
||||
void
|
||||
Command::activate (Symbol cmdID, shared_ptr<CommandImpl> const& implFrame)
|
||||
{
|
||||
REQUIRE (implFrame);
|
||||
static format fmt("Command \"%s\" already defined");
|
||||
|
||||
if (this->isValid())
|
||||
duplicate_detected (cmdID);
|
||||
|
||||
_Handle::activate (implFrame);
|
||||
CommandRegistry::instance().track (cmdID, *this);
|
||||
|
||||
INFO (command, "Command \"%s\" defined OK", cStr(*this));
|
||||
}
|
||||
|
||||
|
||||
Command
|
||||
Command::storeDef (Symbol newCmdID) const
|
||||
{
|
||||
CommandRegistry& registry = CommandRegistry::instance();
|
||||
|
||||
Command newDefinition = registry.queryIndex (cmdID);
|
||||
// will be empty on first invocation
|
||||
if (registry.queryIndex (newCmdID))
|
||||
duplicate_detected (newCmdID);
|
||||
|
||||
return registry.track (cmdID, newDefinition);
|
||||
} // return direct ref to new or currently registered cmd...
|
||||
Command cloneDefinition;
|
||||
cloneDefinition.activate (newCmdID, registry.createCloneImpl(impl()));
|
||||
ENSURE (cloneDefinition);
|
||||
return cloneDefinition;
|
||||
}
|
||||
|
||||
|
||||
/** @internal to be invoked through CommandDef to make a command ready
|
||||
* @throw std::bad_alloc, in which case
|
||||
* CommandRegistry::killCommandImpl is invoked.
|
||||
* @throw error::Logic when this is already activated. */
|
||||
void
|
||||
Command::activate (shared_ptr<CommandImpl> const& implFrame)
|
||||
Command::duplicate_detected (Symbol newCmdID) const
|
||||
{
|
||||
static format fmt("Command \"%s\" already defined");
|
||||
REQUIRE (implFrame);
|
||||
|
||||
if (this->isValid())
|
||||
throw error::Logic (str (fmt % *this), LUMIERA_ERROR_DUPLICATE_COMMAND);
|
||||
|
||||
_Handle::activate (implFrame);
|
||||
|
||||
INFO (command, "Command \"%s\" defined OK", cStr(*this));
|
||||
static format fmt("Unable to store %s as new command. ID \"%s\" is already in use");
|
||||
throw error::Logic (str (fmt % *this % newCmdID), LUMIERA_ERROR_DUPLICATE_COMMAND);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Command::remove (Symbol cmdID)
|
||||
{
|
||||
return CommandRegistry::instance().remove (cmdID);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -157,33 +175,6 @@ namespace control {
|
|||
}
|
||||
|
||||
|
||||
Command
|
||||
Command::storeDef (Symbol newCmdID) const
|
||||
{
|
||||
CommandRegistry& registry = CommandRegistry::instance();
|
||||
|
||||
if (!registry.queryIndex (newCmdID))
|
||||
{
|
||||
Command cloneDefinition;
|
||||
cloneDefinition.activate (registry.createCloneImpl(impl()));
|
||||
|
||||
Command& registeredCloneDef = registry.track (newCmdID, cloneDefinition);
|
||||
if (cloneDefinition == registeredCloneDef)
|
||||
return registeredCloneDef;
|
||||
}
|
||||
|
||||
static format fmt("Unable to store %s as new command. ID \"%s\" is already in use");
|
||||
throw error::Logic (str (fmt % *this % newCmdID), LUMIERA_ERROR_DUPLICATE_COMMAND);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Command::remove (Symbol cmdID)
|
||||
{
|
||||
return CommandRegistry::instance().remove (cmdID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @return the number of command \em definitions currently registered */
|
||||
size_t
|
||||
|
|
|
|||
|
|
@ -127,8 +127,6 @@ namespace control {
|
|||
|
||||
/* === command lifecycle === */
|
||||
|
||||
void activate (shared_ptr<CommandImpl> const&);
|
||||
|
||||
template<typename TYPES>
|
||||
Command& bindArg (Tuple<TYPES> const&);
|
||||
|
||||
|
|
@ -168,15 +166,18 @@ namespace control {
|
|||
static bool canExec (Symbol cmdID);
|
||||
static bool canUndo (Symbol cmdID);
|
||||
|
||||
void duplicate_detected (Symbol) const;
|
||||
|
||||
operator string() const;
|
||||
friend bool operator== (Command const&, Command const&);
|
||||
friend bool operator< (Command const&, Command const&);
|
||||
|
||||
|
||||
protected:
|
||||
static Command& fetchDef (Symbol cmdID);
|
||||
static Command fetchDef (Symbol cmdID);
|
||||
void activate (Symbol cmdID, shared_ptr<CommandImpl> const&);
|
||||
|
||||
friend class CommandDef;
|
||||
friend class CommandDef; //...invoking those two functions during definition stage
|
||||
|
||||
|
||||
private:
|
||||
|
|
@ -208,6 +209,13 @@ namespace control {
|
|||
|
||||
/* == state predicate shortcuts == */
|
||||
|
||||
inline bool
|
||||
Command::defined (Symbol cmdID)
|
||||
{
|
||||
return fetchDef(cmdID).isValid();
|
||||
}
|
||||
|
||||
|
||||
#define _FAILSAFE_COMMAND_QUERY(_ID_, _QUERY_) \
|
||||
try \
|
||||
{ \
|
||||
|
|
@ -220,13 +228,6 @@ namespace control {
|
|||
}
|
||||
|
||||
|
||||
inline bool
|
||||
Command::defined (Symbol cmdID)
|
||||
{
|
||||
_FAILSAFE_COMMAND_QUERY (cmdID, isValid() );
|
||||
}
|
||||
|
||||
|
||||
inline bool
|
||||
Command::canExec (Symbol cmdID)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -135,20 +135,18 @@ namespace test {
|
|||
|
||||
ASSERT (registry.remove(TEST_CMD2));
|
||||
ASSERT (!registry.queryIndex(TEST_CMD2));
|
||||
ASSERT (cnt_defs == registry.index_size()); // removed from index
|
||||
ASSERT (cnt_defs == registry.index_size()); // removed from index
|
||||
ASSERT (1+cnt_inst == registry.instance_count()); //...but still alive
|
||||
|
||||
// create a new registration..
|
||||
cmdX = registry.track(TEST_CMD, cmd2); // but "accidentally" use an existing ID
|
||||
ASSERT (cmdX == cmd1); // Oops, we got the existing registration...
|
||||
|
||||
cmdX = registry.track(TEST_CMD2, cmd2);
|
||||
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 (1+cnt_inst == registry.instance_count());
|
||||
ASSERT (1+cnt_defs == registry.index_size());
|
||||
|
||||
ASSERT (TEST_CMD == registry.findDefinition(cmd1));
|
||||
ASSERT (TEST_CMD2 == registry.findDefinition(cmd2));
|
||||
ASSERT (TEST_CMD2 == registry.findDefinition(cmdX));
|
||||
|
||||
ASSERT ( registry.remove(TEST_CMD2));
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
</pre>
|
||||
</div>
|
||||
<div title="CommandHandling" modifier="Ichthyostega" modified="200910072005" created="200906072048" tags="SessionLogic spec draft decision design img" changecount="30">
|
||||
<div title="CommandHandling" modifier="Ichthyostega" modified="200910090044" created="200906072048" tags="SessionLogic spec draft decision design img" changecount="31">
|
||||
<pre>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.
|
||||
|
||||
|
||||
|
|
@ -1011,7 +1011,7 @@ While obviously the first solution is much simpler to implement on behalf of the
|
|||
While the usual »Memento« implementation might automatically capture the whole model (resulting in a lot of data to be stored and some uncertainty about the scope of the model to be captured), in Lumiera we rely instead on the client code to provide a ''capture function''&nbsp;and a ''playback function'' alongside with the actual operation. To help with this task, we provide a set of standard handlers for common situations. This way, operations might capture very specific information, might provide an "intelligent undo" to restore a given semantic instead of just a fixed value &mdash; and moreover the client code is free actually to employ the "inverse operation" model in special cases where it just makes more sense than capturing state.
|
||||
|
||||
!Handling of commands
|
||||
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.
|
||||
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. {{red{10/09 WIP: not clear if we acutally implement this concept}}}
|
||||
|
||||
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; command [[definition|CommandDefinition]] and [[-lifecycle|CommandLifecycle]]
|
||||
|
|
@ -1034,16 +1034,17 @@ To support this handling scheme, some infrastructure is in place:
|
|||
* performing the actual execution is delegated to a handling pattern object, accessed by name.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="CommandLifecycle" modifier="Ichthyostega" modified="200910081800" created="200907210135" tags="SessionLogic spec draft design img" changecount="16">
|
||||
<div title="CommandLifecycle" modifier="Ichthyostega" modified="200910090043" created="200907210135" tags="SessionLogic spec draft design img" changecount="19">
|
||||
<pre>[<img[Structure of Commands|uml/fig135173.png]]
|
||||
While generally the command framework was designed to be flexible and allow a lot of different use cases, execution paths and to serve various goals, there is an ''intended lifecycle'' &mdash; commands are expected to go through several distinct states.
|
||||
|
||||
The handling of a command starts out with a ''command ID'' provided by the client code. Command IDs are unique (human readable) identifiers and should be organised in a hierarchical fashion. When provided with an ID, the CommandRegistry tries to fetch an existing command definition. In case this fails, we enter the [[command definition stage|CommandDefinition]], which includes specifying functions to implement the operation, state capturing and UNDO. When all these informations are available, the entity is called a ''command definition''. Conceptually, it is comparable to a //class// or //meta object.//
|
||||
The handling of a command starts out with a ''command ID'' provided by the client code. Command ~IDs are unique (human readable) identifiers and should be organised in a hierarchical fashion. When provided with an ID, the CommandRegistry tries to fetch an existing command definition. In case this fails, we enter the [[command definition stage|CommandDefinition]], which includes specifying functions to implement the operation, state capturing and UNDO. When all these informations are available, the entity is called a ''command definition''. Conceptually, it is comparable to a //class// or //meta object.//
|
||||
|
||||
By ''binding'' to specific operation arguments, the definition is //armed up//&nbsp; and becomes a real ''command''. This is similar to creating an instance from a class. Behind the scenes, storage is allocated to hold the argument values and any state captured to create the ability to UNDO the command's effect later on.
|
||||
|
||||
A command is operated or executed by passing it to an ''execution pattern'' &mdash; there is a multitude of possible execution patterns to choose from, depending on the situation.
|
||||
{{red{WIP}}}
|
||||
{{red{WIP... details of ~ProcDispatcher not specified yet}}}
|
||||
|
||||
When a command has been executed (and maybe undone), it's best to leave it alone, because the UndoManager might hold a reference. Anyway, a ''clone of the command'' could be created, maybe bound with different arguments and treated separately from the original command.
|
||||
|
||||
!State predicates
|
||||
|
|
|
|||
Loading…
Reference in a new issue