diff --git a/indra/newview/rlvcommon.h b/indra/newview/rlvcommon.h index 7b7ca22b0b7e8dafad416b5a812e68d12f8460ae..1c55c685d0eece0d63a8e2bb822b4f145c879213 100644 --- a/indra/newview/rlvcommon.h +++ b/indra/newview/rlvcommon.h @@ -56,6 +56,7 @@ class RlvObject; struct RlvException; typedef boost::variant<std::string, LLUUID, S32, ERlvBehaviour> RlvExceptionOption; +typedef boost::variant<int, float> RlvBehaviourModifierValue; class RlvGCTimer; diff --git a/indra/newview/rlvdefines.h b/indra/newview/rlvdefines.h index 3358a4f6ecd2d210a6af90e930c323942c748c5b..7b21e8bb519347747fca4ffff83a2cc59a767738 100644 --- a/indra/newview/rlvdefines.h +++ b/indra/newview/rlvdefines.h @@ -194,11 +194,19 @@ enum ERlvBehaviour { RLV_BHVR_UNKNOWN }; -enum RlvBehaviourOptionType +enum ERlvBehaviourModifier { - RLV_OPTION_NONE, - RLV_OPTION_EXCEPTION, - RLV_OPTION_NONE_OR_EXCEPTION + RLV_MODIFIER_COUNT, + RLV_MODIFIER_UNKNOWN +}; + +enum ERlvBehaviourOptionType +{ + RLV_OPTION_NONE, // Behaviour takes no parameters + RLV_OPTION_EXCEPTION, // Behaviour requires an exception as a parameter + RLV_OPTION_NONE_OR_EXCEPTION, // Behaviour takes either no parameters or an exception + RLV_OPTION_MODIFIER, // Behaviour requires a modifier as a parameter + RLV_OPTION_NONE_OR_MODIFIER // Behaviour takes either no parameters or a modifier }; enum ERlvParamType { diff --git a/indra/newview/rlvhandler.cpp b/indra/newview/rlvhandler.cpp index c748a4de27b1601000b05cbd651e69d27836eeb3..368f3115954853f9d1f3cb486f9d6b893b836749 100644 --- a/indra/newview/rlvhandler.cpp +++ b/indra/newview/rlvhandler.cpp @@ -121,7 +121,7 @@ RlvHandler::RlvHandler() : m_fCanCancelTp(true), m_posSitSource(), m_pGCTimer(NU { gAgent.addListener(this, "new group"); - // Array auto-initialization to 0 is non-standard? (Compiler warning in VC-8.0) + // Array auto-initialization to 0 is still not supported in VS2013 memset(m_Behaviours, 0, sizeof(S16) * RLV_BHVR_COUNT); } @@ -1362,6 +1362,45 @@ ERlvCmdRet RlvBehaviourGenericHandler<RLV_OPTION_NONE_OR_EXCEPTION>::onCommand(c return RlvBehaviourGenericHandler<RLV_OPTION_NONE>::onCommand(rlvCmd, fRefCount); } +// Handles: @bhvr:<modifier>=n|y +ERlvCmdRet RlvBehaviourGenericHandler<RLV_OPTION_MODIFIER>::onCommand(const RlvCommand& rlvCmd, bool& fRefCount) +{ + // There should be an option and it should specify a valid modifier (RlvBehaviourModifier performs the appropriate type checks) + RlvBehaviourModifier* pBhvrModifier = RlvBehaviourDictionary::instance().getModifierFromBehaviour(rlvCmd.getBehaviourType()); + RlvBehaviourModifierValue modValue; + if ( (!rlvCmd.hasOption()) || (!pBhvrModifier) || (!pBhvrModifier->convertOptionValue(rlvCmd.getOption(), modValue)) ) + return RLV_RET_FAILED_OPTION; + + if (RLV_TYPE_ADD == rlvCmd.getParamType()) + pBhvrModifier->addValue(modValue, rlvCmd.getObjectID()); + else + pBhvrModifier->removeValue(modValue, rlvCmd.getObjectID()); + + fRefCount = true; + return RLV_RET_SUCCESS; +} + +// Handles: @bhvr[:<modifier>]=n|y +ERlvCmdRet RlvBehaviourGenericHandler<RLV_OPTION_NONE_OR_MODIFIER>::onCommand(const RlvCommand& rlvCmd, bool& fRefCount) +{ + // If there is an option then it should specify a valid modifier (and reference count) + if (rlvCmd.hasOption()) + return RlvBehaviourGenericHandler<RLV_OPTION_MODIFIER>::onCommand(rlvCmd, fRefCount); + + // Add the default option on an empty modifier if needed + RlvBehaviourModifier* pBhvrModifier = RlvBehaviourDictionary::instance().getModifierFromBehaviour(rlvCmd.getBehaviourType()); + if ( (pBhvrModifier) && (pBhvrModifier->getAddDefault()) ) + { + if (RLV_TYPE_ADD == rlvCmd.getParamType()) + pBhvrModifier->addValue(pBhvrModifier->getDefaultValue(), rlvCmd.getObjectID()); + else + pBhvrModifier->removeValue(pBhvrModifier->getDefaultValue(), rlvCmd.getObjectID()); + } + + fRefCount = true; + return RLV_RET_SUCCESS; +} + // Handles: @addattach[:<attachpt>]=n|y and @remattach[:<attachpt>]=n|y template<> template<> ERlvCmdRet RlvBehaviourAddRemAttachHandler::onCommand(const RlvCommand& rlvCmd, bool& fRefCount) diff --git a/indra/newview/rlvhelper.cpp b/indra/newview/rlvhelper.cpp index f87485c56fb06daea593b70111514fefdf4f708c..7e78b05ec24e5b81d30579b0acc045a77c4c5e3e 100644 --- a/indra/newview/rlvhelper.cpp +++ b/indra/newview/rlvhelper.cpp @@ -32,8 +32,51 @@ // RlvBehaviourDictionary // +static RlvBehaviourModifier_CompMin s_RlvBehaviourModifier_CompMin; +static RlvBehaviourModifier_CompMax s_RlvBehaviourModifier_CompMax; + +/* + * Processing of RLVa commands used to be a big switch/case loop with one function for each command type(addrem, reply + * and force). This is slowly being replaced with templated command handling which might be more confusing intially + * (also due to my poor naming schemes) but is actually far simpler and less error-prone than the old way. + * + * In the general case you just add a definition for the command below and then write the function body in rlvhandler.cpp + * and you're done! Told you this was easy. + * + * Reply command + * ============= + * Definition: RlvReplyProcessor<RLV_BHVR_COMMANDNAME>("commandname"[, <options>])); + * Implement : ERlvCmdRet RlvReplyHandler<RLV_BHVR_COMMANDNAME>::onCommand(const RlvCommand& rlvCmd, std::string& strReply) + * + * Force command + * ============= + * Definition: new RlvForceProcessor<RLV_BHVR_COMMANDNAME>("commandname"[, <options>])); + * Implement : ERlvCmdRet RlvForceProcessor<RLV_BHVR_COMMANDNAME>::onCommand(const RlvCommand& rlvCmd) + * + * Behaviours + * ========== + * Behaviours come in many forms but the added complexity is only in the variety of choices. The implementation is as + * easy as reply or force commands. + * + * For simple behaviours that only require recordkeeping and don't run code when set/unset (see ERlvBehaviourOptionType): + * Definition: RlvBehaviourGenericProcessor<RLV_OPTION_TYPE>("commandname", RLV_BHVR_COMMANDNAME) + * Implement : nothing! (it automagically works) + * For simple behaviours that only require recordkeeping and only run code when they toggle: + * Definition: RlvBehaviourToggleProcessor<RLV_BHVR_COMMANDNAME, RLV_OPTION_TYPE>("commandname")) + * Implement : void RlvBehaviourToggleHandler<RLV_BHVR_COMMANDNAME>::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) + * For behaviours that require manual processing: + * Definition: RlvBehaviourProcessor<RLV_BHVR_COMMANDNAME>("commandname")) + * Implement : ERlvCmdRet RlvBehaviourHandler<RLV_BHVR_COMMANDNAME>::onCommand(const RlvCommand& rlvCmd, bool& fRefCount) + * For behaviours that run code when their modifier changes: + * Definition: addModifier(RLV_BHVR_COMMANDNAME, RLV_MODIFIER_COMMANDNAME, new RlvBehaviourModifierHandler<RLV_MODIFIER_COMMANDNAME>(<default>, <auto-add>, <comparator>)); + * Implement : void RlvBehaviourModifierHandler::onValueChanged() + * + */ RlvBehaviourDictionary::RlvBehaviourDictionary() { + // Array auto-initialization to 0 is still not supported in VS2013 + memset(m_BehaviourModifiers, 0, sizeof(RlvBehaviourModifier*) * RLV_MODIFIER_COUNT); + // // Restrictions // @@ -221,6 +264,12 @@ RlvBehaviourDictionary::~RlvBehaviourDictionary() for (const RlvBehaviourInfo* pBhvrInfo : m_BhvrInfoList) delete pBhvrInfo; m_BhvrInfoList.clear(); + + for (int idxBhvrMod = 0; idxBhvrMod < RLV_MODIFIER_COUNT; idxBhvrMod++) + { + delete m_BehaviourModifiers[idxBhvrMod]; + m_BehaviourModifiers[idxBhvrMod] = nullptr; + } } void RlvBehaviourDictionary::addEntry(const RlvBehaviourInfo* pEntry) @@ -243,6 +292,15 @@ void RlvBehaviourDictionary::addEntry(const RlvBehaviourInfo* pEntry) m_BhvrInfoList.push_back(pEntry); } +void RlvBehaviourDictionary::addModifier(ERlvBehaviour eBhvr, ERlvBehaviourModifier eModifier, RlvBehaviourModifier* pModifierEntry) +{ + if (eModifier < RLV_MODIFIER_COUNT) + { + m_BehaviourModifiers[eModifier] = pModifierEntry; + m_Bhvr2ModifierMap.insert(std::make_pair(eBhvr, eModifier)); + } +} + const RlvBehaviourInfo* RlvBehaviourDictionary::getBehaviourInfo(const std::string& strBhvr, ERlvParamType eParamType, bool* pfStrict) const { bool fStrict = boost::algorithm::ends_with(strBhvr, "_sec"); @@ -289,6 +347,13 @@ bool RlvBehaviourDictionary::getHasStrict(ERlvBehaviour eBhvr) const return false; } +RlvBehaviourModifier* RlvBehaviourDictionary::getModifierFromBehaviour(ERlvBehaviour eBhvr) const +{ + rlv_bhvr2mod_map_t::const_iterator itMod = m_Bhvr2ModifierMap.find(eBhvr); + ERlvBehaviourModifier eBhvrMod = (m_Bhvr2ModifierMap.end() != itMod) ? itMod->second : RLV_MODIFIER_UNKNOWN; + return (eBhvrMod < RLV_MODIFIER_UNKNOWN) ? m_BehaviourModifiers[eBhvrMod] : nullptr; +} + void RlvBehaviourDictionary::toggleBehaviourFlag(const std::string& strBhvr, ERlvParamType eParamType, RlvBehaviourInfo::EBehaviourFlags eBhvrFlag, bool fEnable) { rlv_string2info_map_t::const_iterator itBhvr = m_String2InfoMap.find(std::make_pair(strBhvr, (eParamType & RLV_TYPE_ADDREM) ? RLV_TYPE_ADDREM : eParamType)); @@ -298,6 +363,74 @@ void RlvBehaviourDictionary::toggleBehaviourFlag(const std::string& strBhvr, ERl } } +// ============================================================================ +// RlvBehaviourModifier +// + +RlvBehaviourModifier::RlvBehaviourModifier(const RlvBehaviourModifierValue& defaultValue, bool fAddDefaultOnEmpty, RlvBehaviourModifier_Comp* pValueComparator) + : m_DefaultValue(defaultValue), m_fAddDefaultOnEmpty(fAddDefaultOnEmpty), m_pValueComparator(pValueComparator) +{ +} + +bool RlvBehaviourModifier::addValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idObject) +{ + if (modValue.which() == m_DefaultValue.which()) + { + m_Values.insert((m_pValueComparator) ? std::lower_bound(m_Values.begin(), m_Values.end(), std::make_pair(modValue, idObject), boost::bind(&RlvBehaviourModifier_Comp::operator(), m_pValueComparator, _1, _2)) : m_Values.end(), std::make_pair(modValue, idObject)); + onValueChange(); + m_ChangeSignal(getValue()); + return true; + } + return false; +} + +void RlvBehaviourModifier::removeValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idObject) +{ + if ( (modValue.which() == m_DefaultValue.which()) ) + { + auto itValue = std::find(m_Values.begin(), m_Values.end(), std::make_pair(modValue, idObject)); + if (m_Values.end() != itValue) + { + m_Values.erase(itValue); + onValueChange(); + m_ChangeSignal(getValue()); + } + } +} + +void RlvBehaviourModifier::setPrimaryObject(const LLUUID& idPrimaryObject) +{ + if (m_pValueComparator) + { + m_pValueComparator->m_idPrimaryObject = idPrimaryObject; + m_Values.sort(boost::bind(&RlvBehaviourModifier_Comp::operator(), m_pValueComparator, _1, _2)); + onValueChange(); + m_ChangeSignal(getValue()); + } +} + +bool RlvBehaviourModifier::convertOptionValue(const std::string& optionValue, RlvBehaviourModifierValue& modValue) const +{ + try + { + if (typeid(float) == m_DefaultValue.type()) + { + modValue = std::stof(optionValue.c_str()); + return true; + } + else if (typeid(int) == m_DefaultValue.type()) + { + modValue = std::stoi(optionValue.c_str()); + return true; + } + return false; + } + catch (const std::invalid_argument&) + { + return false; + } +} + // ============================================================================ // RlvCommmand // diff --git a/indra/newview/rlvhelper.h b/indra/newview/rlvhelper.h index 00feabc1b4f5d16071ad5832788ec14a30aea404..cfe64a48f5283b1400c2ff0f54a15daa15ee4a7f 100644 --- a/indra/newview/rlvhelper.h +++ b/indra/newview/rlvhelper.h @@ -142,14 +142,14 @@ template<ERlvBehaviour eBhvr, typename handlerImpl = RlvCommandHandler<RLV_TYPE_ template<ERlvBehaviour eBhvr, typename handlerImpl = RlvCommandHandler<RLV_TYPE_REPLY, eBhvr>> using RlvReplyProcessor = RlvCommandProcessor<RLV_TYPE_REPLY, eBhvr, handlerImpl>; // Provides pre-defined generic implementations of basic behaviours (template voodoo - see original commit for something that still made sense) -template<RlvBehaviourOptionType optionType> struct RlvBehaviourGenericHandler { static ERlvCmdRet onCommand(const RlvCommand& rlvCmd, bool& fRefCount); }; -template<RlvBehaviourOptionType optionType> using RlvBehaviourGenericProcessor = RlvBehaviourProcessor<RLV_BHVR_UNKNOWN, RlvBehaviourGenericHandler<optionType>>; +template<ERlvBehaviourOptionType optionType> struct RlvBehaviourGenericHandler { static ERlvCmdRet onCommand(const RlvCommand& rlvCmd, bool& fRefCount); }; +template<ERlvBehaviourOptionType optionType> using RlvBehaviourGenericProcessor = RlvBehaviourProcessor<RLV_BHVR_UNKNOWN, RlvBehaviourGenericHandler<optionType>>; // ============================================================================ // RlvBehaviourProcessor and related classes - Handles add/rem comamnds aka "restrictions) // -template <ERlvBehaviour eBhvr, RlvBehaviourOptionType optionType, typename toggleHandlerImpl = RlvBehaviourToggleHandler<eBhvr>> +template <ERlvBehaviour eBhvr, ERlvBehaviourOptionType optionType, typename toggleHandlerImpl = RlvBehaviourToggleHandler<eBhvr>> class RlvBehaviourToggleProcessor : public RlvBehaviourInfo { public: @@ -157,6 +157,152 @@ public: ERlvCmdRet processCommand(const RlvCommand& rlvCmd) const override { return RlvCommandHandlerBaseImpl<RLV_TYPE_ADDREM>::processCommand(rlvCmd, &RlvBehaviourGenericHandler<optionType>::onCommand, &toggleHandlerImpl::onCommandToggle); } }; +// ============================================================================ +// RlvBehaviourModifier - stores behaviour modifiers in an - optionally - sorted list and returns the first element (or default value if there are no modifiers) +// + +typedef std::pair<RlvBehaviourModifierValue, LLUUID> RlvBehaviourModifierValueTuple; + +struct RlvBehaviourModifier_Comp +{ + virtual ~RlvBehaviourModifier_Comp() {} + virtual bool operator()(const RlvBehaviourModifierValueTuple& lhs, const RlvBehaviourModifierValueTuple& rhs) + { + // Values that match the primary object take precedence (otherwise maintain relative ordering) + if ( (rhs.second == m_idPrimaryObject) && (lhs.second != m_idPrimaryObject) ) + return false; + return true; + } + + LLUUID m_idPrimaryObject; +}; +struct RlvBehaviourModifier_CompMin : public RlvBehaviourModifier_Comp +{ + bool operator()(const RlvBehaviourModifierValueTuple& lhs, const RlvBehaviourModifierValueTuple& rhs) override + { + if ( (m_idPrimaryObject.isNull()) || ((lhs.second == m_idPrimaryObject) && (rhs.second == m_idPrimaryObject)) ) + return lhs.first < rhs.first; + return RlvBehaviourModifier_Comp::operator()(lhs, rhs); + } +}; +struct RlvBehaviourModifier_CompMax : public RlvBehaviourModifier_Comp +{ + bool operator()(const RlvBehaviourModifierValueTuple& lhs, const RlvBehaviourModifierValueTuple& rhs) override + { + if ( (m_idPrimaryObject.isNull()) || ((lhs.second == m_idPrimaryObject) && (rhs.second == m_idPrimaryObject)) ) + return rhs.first < lhs.first; + return RlvBehaviourModifier_Comp::operator()(lhs, rhs); + } +}; + +class RlvBehaviourModifier +{ +public: + RlvBehaviourModifier(const RlvBehaviourModifierValue& defaultValue, bool fAddDefaultOnEmpty, RlvBehaviourModifier_Comp* pValueComparator); + virtual ~RlvBehaviourModifier() {} + + /* + * Member functions + */ +protected: + virtual void onValueChange() const {} +public: + bool addValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idObject); + bool convertOptionValue(const std::string& optionValue, RlvBehaviourModifierValue& modValue) const; + bool getAddDefault() const { return m_fAddDefaultOnEmpty; } + const RlvBehaviourModifierValue& getDefaultValue() const { return m_DefaultValue; } + const RlvBehaviourModifierValue& getValue() const { return (!m_Values.empty()) ? m_Values.front().first : m_DefaultValue; } + template<typename T> const T& getValue() const { return boost::get<T>(getValue()); } + void removeValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idObject); + void setPrimaryObject(const LLUUID& idPrimaryObject); + + typedef boost::signals2::signal<void(const RlvBehaviourModifierValue& newValue)> change_signal_t; + change_signal_t& getSignal() { return m_ChangeSignal; } + + /* + * Member variables + */ +protected: + RlvBehaviourModifierValue m_DefaultValue; + bool m_fAddDefaultOnEmpty; + std::list<RlvBehaviourModifierValueTuple> m_Values; + change_signal_t m_ChangeSignal; + RlvBehaviourModifier_Comp* m_pValueComparator; +}; + +template<ERlvBehaviourModifier eBhvrMod> +class RlvBehaviourModifierHandler : public RlvBehaviourModifier +{ +public: + //using RlvBehaviourModifier::RlvBehaviourModifier; // Needs VS2015 and up + RlvBehaviourModifierHandler(const RlvBehaviourModifierValue& defaultValue, bool fAddDefaultOnEmpty, RlvBehaviourModifier_Comp* pValueComparator) + : RlvBehaviourModifier(defaultValue, fAddDefaultOnEmpty, pValueComparator) {} +protected: + void onValueChange() const override; +}; + +// Inspired by LLControlCache<T> +template<typename T> +class RlvBehaviourModifierCache : public LLRefCount, public LLInstanceTracker<RlvBehaviourModifierCache<T>, ERlvBehaviourModifier> +{ +public: + RlvBehaviourModifierCache(ERlvBehaviourModifier eModifier) + : LLInstanceTracker<RlvBehaviourModifierCache<T>, ERlvBehaviourModifier>(eModifier) + { + RlvBehaviourModifier* pBhvrModifier = (eModifier < RLV_MODIFIER_COUNT) ? RlvBehaviourDictionary::instance().getModifier(eModifier) : nullptr; + if (pBhvrModifier) + { + mConnection = pBhvrModifier->getSignal().connect(boost::bind(&RlvBehaviourModifierCache<T>::handleValueChange, this, _1)); + mCachedValue = pBhvrModifier->getValue<T>(); + } + else + { + mCachedValue = {}; + } + } + ~RlvBehaviourModifierCache() {} + + /* + * Member functions + */ +public: + const T& getValue() const { return mCachedValue; } +protected: + void handleValueChange(const RlvBehaviourModifierValue& newValue) { mCachedValue = boost::get<T>(newValue); } + + /* + * Member variables + */ +protected: + T mCachedValue; + boost::signals2::scoped_connection mConnection; +}; + +// Inspired by LLCachedControl<T> +template <typename T> +class RlvCachedBehaviourModifier +{ +public: + RlvCachedBehaviourModifier(ERlvBehaviourModifier eModifier) + { + if ((mCachedModifierPtr = RlvBehaviourModifierCache<T>::getInstance(eModifier)) == nullptr) + mCachedModifierPtr = new RlvBehaviourModifierCache<T>(eModifier); + } + + /* + * Operators + */ +public: + operator const T&() const { return mCachedModifierPtr->getValue(); } + const T& operator()() { return mCachedModifierPtr->getValue(); } + + /* + * Member variables + */ +protected: + LLPointer<RlvBehaviourModifierCache<T>> mCachedModifierPtr; +}; + // ============================================================================ // RlvBehaviourDictionary and related classes // @@ -169,6 +315,7 @@ protected: ~RlvBehaviourDictionary(); public: void addEntry(const RlvBehaviourInfo* pEntry); + void addModifier(ERlvBehaviour eBhvr, ERlvBehaviourModifier eModifier, RlvBehaviourModifier* pModifierEntry); /* * General helper functions @@ -178,7 +325,8 @@ public: const RlvBehaviourInfo* getBehaviourInfo(const std::string& strBhvr, ERlvParamType eParamType, bool* pfStrict = NULL) const; bool getCommands(const std::string& strMatch, ERlvParamType eParamType, std::list<std::string>& cmdList) const; bool getHasStrict(ERlvBehaviour eBhvr) const; - const std::string& getStringFromBehaviour(ERlvBehaviour eBhvr, ERlvParamType eParamType, bool fStrict = false) const; + RlvBehaviourModifier* getModifier(ERlvBehaviourModifier eBhvrMod) const { return (eBhvrMod < RLV_MODIFIER_COUNT) ? m_BehaviourModifiers[eBhvrMod] : nullptr; } + RlvBehaviourModifier* getModifierFromBehaviour(ERlvBehaviour eBhvr) const; void toggleBehaviourFlag(const std::string& strBhvr, ERlvParamType eParamType, RlvBehaviourInfo::EBehaviourFlags eBvhrFlag, bool fEnable); /* @@ -188,10 +336,13 @@ protected: typedef std::list<const RlvBehaviourInfo*> rlv_bhvrinfo_list_t; typedef std::map<std::pair<std::string, ERlvParamType>, const RlvBehaviourInfo*> rlv_string2info_map_t; typedef std::multimap<ERlvBehaviour, const RlvBehaviourInfo*> rlv_bhvr2info_map_t; + typedef std::map<ERlvBehaviour, ERlvBehaviourModifier> rlv_bhvr2mod_map_t; rlv_bhvrinfo_list_t m_BhvrInfoList; rlv_string2info_map_t m_String2InfoMap; rlv_bhvr2info_map_t m_Bhvr2InfoMap; + rlv_bhvr2mod_map_t m_Bhvr2ModifierMap; + RlvBehaviourModifier* m_BehaviourModifiers[RLV_MODIFIER_COUNT]; }; // ============================================================================