// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#ifndef R2_ACTION_HISTORIC_H
#define R2_ACTION_HISTORIC_H
#include "nel/misc/historic.h"
#include "nel/misc/smart_ptr.h"
#include "nel/misc/ucstring.h"
//
#include "game_share/object.h"
#include "game_share/scenario.h"
namespace R2
{
class IDynamicMapClient;
// An undo / redo historic
class CActionHistoric
{
public:
CActionHistoric();
~CActionHistoric();
void setDMC(IDynamicMapClient *dmc) { _DMC = dmc; }
/** Signal the begining of a new action. Calling this twice in a row will not create
* an empty action.
*/
void newSingleAction(const ucstring &name);
/** Begin a new action composed of several subactions. 'numSubActions' calls to 'newAction' will be expected
* before the merge of the subactions is pushed
*/
void newMultiAction(const ucstring &name, uint actionCount);
/** Begin a new action composed of several subactions. 'numSubActions' calls to 'newAction' will be expected
* before the merge of the subactions is pushed
*/
void newPendingMultiAction(const ucstring &name, uint actionCount);
/** Begin a new 'pending' action
* Unlike 'newAction', requests of the actions are sent at each call
* to the 'request' commands, not when the action is finished.
* Use this if you can't issue an action in a single row (may happen
* if some requests in the action depends on notifications sent by previous requests)
*/
void newPendingAction(const ucstring &name);
// force to end a multi action, event if the action count hasnt been reached
void forceEndMultiAction();
// End last action / pending action
void endAction();
// cancel any action, including multi action
void cancelAction();
// If there's a pending action for this frame, flush its content
void flushPendingAction();
// Test if a pending action has been begun
bool isPendingActionInProgress() const { return _NewActionIsPending; }
// get number of completed actions (do not include the new action being built)
uint getNumActions() const { return _Actions.getSize(); }
bool isNewActionBeingRecorded() const { return _NewAction != NULL; }
uint getMaxNumActions() const { return _Actions.getMaxSize(); }
void setMaxNumActions(uint count);
// clear
void clear(CObject *newScenario = NULL);
// get name of next action that can be redone
const ucstring *getNextActionName() const;
// get name of previous action that can be undone
const ucstring *getPreviousActionName() const;
// Test if an action can be undone
bool canUndo() const;
// Test if an action can be redo
bool canRedo() const;
// undo last action, if possible (return true on success)
bool undo();
// redo last action, if possible (return true on success)
bool redo();
// push requests into current action
void requestInsertNode(const std::string& instanceId, const std::string& name, sint32 position, const std::string& key, CObject* value);
void requestSetNode(const std::string& instanceId, const std::string& attrName, CObject* value);
void requestEraseNode(const std::string& instanceId, const std::string& attrName, sint32 position);
void requestMoveNode(const std::string& instanceId, const std::string& attrName, sint32 position, const std::string& destInstanceId, const std::string& destAttrName, sint32 destPosition);
//Uundo supported only if 'clear' was called with a scenario pointer
//Only 'redo' supported else
bool isUndoSupported() const { return _Scenario.getHighLevel() != NULL; }
private:
// base for all requests
class CRequestBase : public NLMISC::CRefCount
{
public:
typedef NLMISC::CSmartPtr TSmartPtr;
virtual ~CRequestBase() {}
virtual void redo(IDynamicMapClient *dmc, CScenario &scenario) = 0;
virtual void undo(IDynamicMapClient *dmc, CScenario &scenario) = 0;
protected:
// clone an object, and disable its refids objects (these object would send
// creation notifications otherwise)
public:
static CObject *cloneObject(const CObject *src);
};
/** An atomic list of requests.
* Once redo / undo have been called, no other requests can be pushed into this action,
* so calls to 'pushRequest' will assert.
*/
class CAction : public NLMISC::CRefCount
{
public:
typedef NLMISC::CSmartPtr TSmartPtr;
CAction(const ucstring &name);
~CAction();
void setCompleted() { _Completed = true; }
void pushRequest(CRequestBase *req);
// For pending action : flush the content that has already been pushed using 'pushRequest'
// This should be called before 'setCompleted'
void flush(IDynamicMapClient *dmc, CScenario &scenario);
// If content has been flushed, rollback the modifications
void rollback(IDynamicMapClient *dmc, CScenario &scenario);
//
void redo(IDynamicMapClient *dmc, CScenario &scenario);
void undo(IDynamicMapClient *dmc, CScenario &scenario);
const ucstring &getName() const { return _Name; }
private:
ucstring _Name;
std::vector _Requests;
uint _FlushedCount;
bool _Completed;
bool _Flushing;
};
//////////////
// REQUESTS //
//////////////
class CRequestSetNode : public CRequestBase
{
public:
CRequestSetNode(const std::string& instanceId, const std::string& attrName, CObject* value);
// from CRequestBase
virtual void redo(IDynamicMapClient *dmc, CScenario &scenario);
virtual void undo(IDynamicMapClient *dmc, CScenario &scenario);
private:
std::string _InstanceId;
std::string _AttrName;
CObject::TSmartPtr _NewValue;
CObject::TSmartPtr _OldValue;
};
//
class CRequestEraseNode : public CRequestBase
{
public:
CRequestEraseNode(const std::string& instanceId, const std::string& attrName, sint32 position);
// from CRequestBase
virtual void redo(IDynamicMapClient *dmc, CScenario &scenario);
virtual void undo(IDynamicMapClient *dmc, CScenario &scenario);
private:
// point of deletion
std::string _InstanceId;
std::string _AttrName;
sint32 _Position;
CObject::TSmartPtr _OldValue;
// name in parent
std::string _ParentInstanceId;
std::string _AttrNameInParent;
sint32 _PositionInParent;
};
//
class CRequestInsertNode : public CRequestBase
{
public:
CRequestInsertNode(const std::string& instanceId, const std::string& attrName, sint32 position, const std::string& key, CObject* value);
// from CRequestBase
virtual void redo(IDynamicMapClient *dmc, CScenario &scenario);
virtual void undo(IDynamicMapClient *dmc, CScenario &scenario);
private:
std::string _InstanceId;
std::string _AttrName;
sint32 _Position;
std::string _Key;
CObject::TSmartPtr _Value;
};
//
class CRequestMoveNode : public CRequestBase
{
public:
CRequestMoveNode(const std::string& srcInstanceId,
const std::string& srcAttrName,
sint32 srcPosition,
const std::string& destInstanceId,
const std::string& destAttrName,
sint32 destPosition);
// from CRequestBase
virtual void redo(IDynamicMapClient *dmc, CScenario &scenario);
virtual void undo(IDynamicMapClient *dmc, CScenario &scenario);
private:
std::string _SrcInstanceId;
std::string _SrcAttrName;
sint32 _SrcPosition;
// name of source in parent for reciprocal move
std::string _SrcInstanceIdInParent;
std::string _SrcAttrNameInParent;
sint32 _SrcPositionInParent;
//
std::string _DestInstanceId;
std::string _DestAttrName;
sint32 _DestPosition;
// name of dest
std::string _DestInstanceIdAfterMove;
std::string _DestAttrNameAfterMove;
sint32 _DestPositionAfterMove;
};
private:
IDynamicMapClient *_DMC;
NLMISC::CHistoric _Actions;
/** Index of current action relative to the end of the stack (-1 for the last action)
* Undo applies to this action, redo applies to the next action
*/
sint _CurrActionIndex;
CAction::TSmartPtr _NewAction;
ucstring _NewActionName;
uint _SubActionCount;
bool _NewActionIsPending;
// anticipated scenario state,
CScenario _Scenario;
//
private:
// return previous action absolute index, or -1 if there's no previous action that can be undone.
sint getPreviousActionIndex() const;
// return next action absolute index, or -1 if there's no next action that can be undone.
sint getNextActionIndex() const;
};
} // R2
#endif