This was prompted by a test failing under Boost-1.65 (--> see #294) When reviewed now, the whole idea of testing Steam-Layer Commands for equivalence feels a bit sketchy. Just the comparison for the command ''identity'' alone seems sufficient, i.e. the test if a command-ID is associated with the same backend-handle and thus the same functor binding.
336 lines
11 KiB
C++
336 lines
11 KiB
C++
/*
|
|
COMMAND.hpp - Key abstraction for steam/edit operations and UNDO management
|
|
|
|
Copyright (C) Lumiera.org
|
|
2009, 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.hpp
|
|
** Steam-Layer command frontend.
|
|
** A *command* is a functor, which can be invoked according to a pre-defined HandlingPattern.
|
|
** Most notably, command invocation can be scheduled and logged with the serialiser, and the effect
|
|
** of any command invocation can be _undone_ later on by invoking the "undo operation" defined
|
|
** alongside with the command's operation. The command operation is defined through a C/C++ function
|
|
** and may receive an arbitrary number and type of arguments. After setting up such a CommandDef ,
|
|
** it can be referred for use through a symbolic ID. Before being able to invoke the command, concrete
|
|
** function arguments need to be provided (this is called "binding" or "closing the function arguments").
|
|
** These function arguments are stored within the command definition and remain opaque to the client code
|
|
** actually invoking the command. Behind the scenes, there is a CommandRegistry, holding an index of the
|
|
** registered commands and managing the storage for command definitions and arguments. The actual
|
|
** Command object used by client code is a small, copyable and ref-counting handle to this
|
|
** stored definition backend.
|
|
**
|
|
** # Command definition, argument types and UNDO operation
|
|
** For a command to be usable at all, a concrete [command definition](\ref command-def.hpp) need to be supplied
|
|
** somewhere in the code base. Typically this is done through static [command-setup bindings](\ref command-setup.hpp).
|
|
** Such a command definition links three functions with the name-ID of the command
|
|
** - the actual command operation
|
|
** - a function to capture state
|
|
** - a function to undo the effect of the operation
|
|
** These functions may take arbitrary arguments, but the signature of this operation is captured at
|
|
** compile time and embedded as invisible type information within the implementation storage. While this
|
|
** allows to verify the type of the actual command arguments, this type sanity check can happen only late,
|
|
** at invocation time (and result in raising an exception). In a similar vein, the state capturing and
|
|
** the undo function must match with the signature of the main operation, since these functions are
|
|
** provided with the same arguments as presented to the command function. The UNDO functionality
|
|
** in Lumiera is based on capturing a _state memento_, not on applying a reverse function.
|
|
** So it is the responsibility of the capture function to take a suitable state snapshot,
|
|
** such as to be able to revert to the situation before activating this command, but
|
|
** only as far as the effect of the actual command function is concerned.
|
|
**
|
|
** @see command-def.hpp
|
|
** @see command-use1-test.cpp
|
|
** @see command-use2-test.cpp
|
|
** @see command-use3-test.cpp
|
|
** @see SteamDispatcher
|
|
** @see Session
|
|
**
|
|
*/
|
|
|
|
|
|
|
|
#ifndef CONTROL_COMMAND_H
|
|
#define CONTROL_COMMAND_H
|
|
|
|
#include "lib/error.hpp"
|
|
#include "lib/symbol.hpp"
|
|
#include "steam/common.hpp"
|
|
#include "steam/control/argument-erasure.hpp"
|
|
#include "steam/control/argument-tuple-accept.hpp"
|
|
#include "steam/control/handling-pattern.hpp"
|
|
#include "lib/meta/tuple-helper.hpp"
|
|
#include "lib/diff/gen-node.hpp"
|
|
#include "lib/handle.hpp"
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
|
|
namespace steam {
|
|
namespace control {
|
|
|
|
using std::string;
|
|
using lib::Symbol;
|
|
using std::shared_ptr;
|
|
using lib::meta::Tuple;
|
|
using lib::meta::Types;
|
|
|
|
|
|
LUMIERA_ERROR_DECLARE (UNBOUND_ARGUMENTS); ///< Command functor not yet usable, because arguments aren't bound
|
|
LUMIERA_ERROR_DECLARE (INVALID_COMMAND); ///< Unknown or insufficiently defined command
|
|
LUMIERA_ERROR_DECLARE (DUPLICATE_COMMAND); ///< Attempt to redefine an already existing command definition
|
|
LUMIERA_ERROR_DECLARE (INVALID_ARGUMENTS); ///< Arguments provided for binding doesn't match stored command function parameters
|
|
|
|
|
|
|
|
typedef void* FuncPtr;
|
|
|
|
class CommandDef;
|
|
class CommandImpl;
|
|
|
|
|
|
/**
|
|
* Handle object representing a single Command instance to be used by client code.
|
|
* Commands are accessed \link #get through a symbolic ID \endlink; there needs to be
|
|
* a CommandDef somewhere to specify the actual operation and to define, how the
|
|
* effect of the command can be undone. Moreover, the command's definition
|
|
* refers to a HandlingPattern, which describes how the command is actually
|
|
* to be executed (the default is scheduling it within the SteamDispatcher)
|
|
*
|
|
* Client code usually just
|
|
* - creates a command instance by referring to a command ID
|
|
* - maybe binds to concrete arguments (e.g. a target object)
|
|
* - triggers command execution through operator()
|
|
* - maybe checks the return value for errors
|
|
*
|
|
* @remark lightweight implementation as smart-ptr
|
|
*/
|
|
class Command
|
|
: public AcceptAnyBinding<Command // accepts arbitrary bind(..) calls (with runtime check)
|
|
, Command& // (return value of bind() functions is *this)
|
|
, lib::Handle<CommandImpl> // actually implemented as ref counting Handle
|
|
> //
|
|
{
|
|
typedef lib::Handle<CommandImpl> _Handle;
|
|
|
|
public:
|
|
/* === command registry === */
|
|
static Command get (Symbol cmdID);
|
|
static bool remove (Symbol cmdID);
|
|
|
|
/** create a clone definition */
|
|
Command storeDef (Symbol newCmdID) const;
|
|
Command newInstance () const;
|
|
|
|
static Command maybeGetNewInstance (Symbol cmdID);
|
|
|
|
|
|
Command (Symbol cmdID) { *this = get (cmdID); }
|
|
Command() { } ///< undefined command
|
|
~Command();
|
|
|
|
// default copy acceptable
|
|
Command (Command &&) = default;
|
|
Command (Command const&) = default;
|
|
Command& operator= (Command &&) = default;
|
|
Command& operator= (Command const&) = default;
|
|
|
|
|
|
|
|
/* === command lifecycle === */
|
|
|
|
template<typename...TYPES>
|
|
Command& bindArg (std::tuple<TYPES...> const&);
|
|
|
|
Command& bindArg (lib::diff::Rec const&);
|
|
|
|
Command& unbind();
|
|
|
|
|
|
ExecResult operator() () ;
|
|
ExecResult exec () ;
|
|
ExecResult undo () ;
|
|
|
|
|
|
/** core operation: invoke the command
|
|
* @param execPattern describes the individual steps
|
|
* necessary to get this command invoked properly
|
|
*/
|
|
ExecResult exec (HandlingPattern const& execPattern);
|
|
ExecResult exec (HandlingPattern::ID);
|
|
|
|
ExecResult undo (HandlingPattern const& execPattern);
|
|
ExecResult undo (HandlingPattern::ID);
|
|
|
|
/** invoke using a default "synchronous" execution pattern */
|
|
ExecResult execSync ();
|
|
|
|
/** @return ID of the execution pattern used by operator() */
|
|
HandlingPattern::ID getDefaultHandlingPattern() const;
|
|
|
|
/** define a handling pattern to be used by default
|
|
* @return ID of the currently defined default pattern */
|
|
HandlingPattern::ID setHandlingPattern (HandlingPattern::ID);
|
|
|
|
|
|
|
|
/* === diagnostics === */
|
|
|
|
static size_t definition_count();
|
|
static size_t instance_count();
|
|
|
|
bool canExec() const;
|
|
bool canUndo() const;
|
|
|
|
static bool defined (Symbol cmdID);
|
|
static bool canExec (Symbol cmdID);
|
|
static bool canUndo (Symbol cmdID);
|
|
|
|
void duplicate_detected (Symbol) const;
|
|
|
|
Symbol getID() const noexcept;
|
|
bool isAnonymous() const;
|
|
|
|
operator string() const;
|
|
friend bool operator== (Command const&, Command const&);
|
|
friend bool operator< (Command const&, Command const&);
|
|
|
|
|
|
protected:
|
|
static Command fetchDef (Symbol cmdID);
|
|
void activate (shared_ptr<CommandImpl> &&, Symbol cmdID =0);
|
|
|
|
friend class CommandDef; //...invoking those two functions during definition stage
|
|
|
|
|
|
private:
|
|
void setArguments (Arguments&);
|
|
void setArguments (lib::diff::Rec const&);
|
|
static bool equivalentImpl (Command const&, Command const&);
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
inline ExecResult
|
|
Command::operator() ()
|
|
{
|
|
return exec (getDefaultHandlingPattern());
|
|
}
|
|
|
|
inline ExecResult
|
|
Command::exec ()
|
|
{
|
|
return exec (getDefaultHandlingPattern());
|
|
}
|
|
|
|
inline ExecResult
|
|
Command::undo ()
|
|
{
|
|
return undo (getDefaultHandlingPattern());
|
|
}
|
|
|
|
|
|
template<typename...TYPES>
|
|
inline Command&
|
|
Command::bindArg (std::tuple<TYPES...> const& tuple)
|
|
{
|
|
TypedArguments<std::tuple<TYPES...>> args(tuple);
|
|
this->setArguments (args);
|
|
return *this;
|
|
}
|
|
|
|
|
|
inline Command&
|
|
Command::bindArg (lib::diff::Rec const& paramData)
|
|
{
|
|
this->setArguments (paramData);
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* == state predicate shortcuts == */
|
|
|
|
inline bool
|
|
Command::defined (Symbol cmdID)
|
|
{
|
|
return fetchDef(cmdID).isValid();
|
|
}
|
|
|
|
|
|
#define _FAILSAFE_COMMAND_QUERY(_ID_, _QUERY_) \
|
|
try \
|
|
{ \
|
|
return Command::get(_ID_)._QUERY_; \
|
|
} \
|
|
catch(lumiera::error::Invalid&) \
|
|
{ \
|
|
lumiera_error(); /* ignore errorstate */ \
|
|
return false; \
|
|
}
|
|
|
|
|
|
inline bool
|
|
Command::canExec (Symbol cmdID)
|
|
{
|
|
_FAILSAFE_COMMAND_QUERY (cmdID, canExec() );
|
|
}
|
|
|
|
|
|
inline bool
|
|
Command::canUndo (Symbol cmdID)
|
|
{
|
|
_FAILSAFE_COMMAND_QUERY (cmdID, canUndo() );
|
|
}
|
|
|
|
#undef _FAILSAFE_COMMAND_QUERY
|
|
|
|
|
|
|
|
|
|
inline bool
|
|
operator== (Command const& c1, Command const& c2)
|
|
{
|
|
return (!c1 && !c2)
|
|
|| ( c1 && c2 && (&c1.impl() == &c2.impl()));
|
|
}
|
|
|
|
inline bool
|
|
operator!= (Command const& c1, Command const& c2)
|
|
{
|
|
return not (c1 == c2);
|
|
}
|
|
|
|
/** allow for sets and associative containers */
|
|
inline bool
|
|
operator< (Command const& c1, Command const& c2)
|
|
{
|
|
return (!c1 && c2)
|
|
|| ( c1 && c2 && (&c1.impl() < &c2.impl()));
|
|
}
|
|
|
|
|
|
|
|
|
|
}} // namespace steam::control
|
|
#endif
|