LUMIERA.clone/src/steam/control/command.hpp
Ichthyostega d57770ca89 Commands: disable equivalence-test on command equality
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.
2019-06-23 17:35:21 +02:00

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