// 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 . #include "stdpch.h" #include "interface_link.h" #include "interface_expr.h" #include "interface_element.h" #include "interface_manager.h" #include "interface_expr_node.h" #include "nel/gui/reflect.h" #include "nel/misc/cdb_branch.h" using namespace std; using namespace NLMISC; ///////////// // GLOBALS // ///////////// CInterfaceLink::TLinkList CInterfaceLink::_LinkList; CInterfaceLink::TLinkVect CInterfaceLink::_LinksWithNoTarget; // CInterfaceLink *CInterfaceLink::_FirstTriggeredLink[2] = { NULL, NULL }; CInterfaceLink *CInterfaceLink::_LastTriggeredLink[2] = { NULL, NULL }; CInterfaceLink *CInterfaceLink::_CurrUpdatedLink = NULL; CInterfaceLink *CInterfaceLink::_NextUpdatedLink = NULL; uint CInterfaceLink::_CurrentTriggeredLinkList = 0; bool CInterfaceLink::_UpdateAllLinks = false; /////////////// // FUNCTIONS // /////////////// /** Tool fct : affect a value to a reflected property of an interface element. */ static bool affect(const CInterfaceExprValue &value, CInterfaceElement &destElem, const CReflectedProperty &property) { CInterfaceExprValue valueToAffect = value; switch(property.Type) { case CReflectedProperty::Boolean: if (valueToAffect.toBool()) { (destElem.*(property.SetMethod.SetBool))(valueToAffect.getBool()); } else { return false; } break; case CReflectedProperty::SInt32: if (valueToAffect.toInteger()) { (destElem.*(property.SetMethod.SetSInt32))((sint32) valueToAffect.getInteger()); } else { return false; } break; case CReflectedProperty::Float: if (valueToAffect.toDouble()) { (destElem.*(property.SetMethod.SetFloat))((float) valueToAffect.getDouble()); } else { return false; } break; case CReflectedProperty::String: if (valueToAffect.toString()) { (destElem.*(property.SetMethod.SetString))(valueToAffect.getString()); } else { return false; } break; case CReflectedProperty::UCString: if (valueToAffect.toString()) { (destElem.*(property.SetMethod.SetUCString))(valueToAffect.getUCString()); } else { return false; } break; case CReflectedProperty::RGBA: if (valueToAffect.toRGBA()) { (destElem.*(property.SetMethod.SetRGBA))(valueToAffect.getRGBA()); } else { return false; } break; default: break; } return true; } CInterfaceLink::CInterfaceLinkUpdater::CInterfaceLinkUpdater() { CInterfaceManager::getInstance()->addFlushObserver( this ); } CInterfaceLink::CInterfaceLinkUpdater::~CInterfaceLinkUpdater() { } void CInterfaceLink::CInterfaceLinkUpdater::onObserverCallFlush() { CInterfaceLink::updateTrigeredLinks(); } ///////////// // MEMBERS // ///////////// //=========================================================== CInterfaceLink::CInterfaceLink() : _On(true) { // add an entry in the links list _LinkList.push_front(this); _ListEntry = _LinkList.begin(); _PrevTriggeredLink[0] = _PrevTriggeredLink[1] = NULL; _NextTriggeredLink[0] = _NextTriggeredLink[1] = NULL; _Triggered[0] = _Triggered[1] = false; _ParseTree = NULL; } //=========================================================== CInterfaceLink::~CInterfaceLink() { if (this == _CurrUpdatedLink) { _CurrUpdatedLink = NULL; } if (this == _NextUpdatedLink) { _NextUpdatedLink = _NextUpdatedLink->_NextTriggeredLink[1 - _CurrentTriggeredLinkList]; } // unlink observer from both update lists unlinkFromTriggerList(0); unlinkFromTriggerList(1); removeObservers(_ObservedNodes); // removes from the link list _LinkList.erase(_ListEntry); delete _ParseTree; } //=========================================================== bool CInterfaceLink::init(const std::vector &targets, const std::string &expr, const std::string &actionHandler, const std::string &ahParams, const std::string &ahCond, CInterfaceGroup *parentGroup) { CInterfaceExprValue result; // Build the parse tree nlassert(!_ParseTree); _ParseTree = CInterfaceExpr::buildExprTree(expr); if (!_ParseTree) { // wrong expression : release the link nlwarning("CInterfaceLink::init : can't parse expression %s", expr.c_str()); _LinksWithNoTarget.push_back(TLinkSmartPtr(this)); return false; } // Examine the expression, to see which database nodes it depends on _ParseTree->getDepends(_ObservedNodes); // must keep observerd node sorted (for std::set_xx ops) std::sort(_ObservedNodes.begin(), _ObservedNodes.end()); if (!targets.empty()) { // retrieve properties from the element _Targets.resize(targets.size()); for (uint k = 0; k < targets.size(); ++k) { CInterfaceElement *elem = targets[k].Elem; if (!elem) { nlwarning(" : Element %d is NULL", k); _Targets[k]._InterfaceElement = NULL; continue; } _Targets[k]._Property = elem->getReflectedProperty(targets[k].PropertyName); if (!_Targets[k]._Property) { nlwarning(" : Can't retrieve property %s for element %d.", targets[k].PropertyName.c_str(), k); _Targets[k]._InterfaceElement = NULL; continue; } _Targets[k]._InterfaceElement = elem; elem->addLink(this); } } else { // There are no target for this link, so, put in a dedicated list to ensure that the link will be destroyed at exit _LinksWithNoTarget.push_back(TLinkSmartPtr(this)); } // create observers createObservers(_ObservedNodes); _Expr = expr; // _ActionHandler = actionHandler; _AHParams = ahParams; _AHCond = ahCond; _AHParent = parentGroup; return true; } //=========================================================== void CInterfaceLink::uninit() { for (uint32 i = 0; i < _LinksWithNoTarget.size(); i++) { CInterfaceLink *pLink = _LinksWithNoTarget[i]; if (pLink == this) { _LinksWithNoTarget.erase(_LinksWithNoTarget.begin()+i); return; } } } //=========================================================== void CInterfaceLink::update(ICDBNode * /* node */) { // mark link as triggered linkInTriggerList(_CurrentTriggeredLinkList); } //=========================================================== void CInterfaceLink::createObservers(const TNodeVect &nodes) { for(std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { if ((*it)->isLeaf()) { ICDBNode::CTextId textId; (*it)->addObserver(this, textId); } else { CCDBNodeBranch *br = static_cast(*it); CInterfaceManager::getInstance()->addBranchObserver( br, this ); } } } //=========================================================== void CInterfaceLink::removeObservers(const TNodeVect &nodes) { for(std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { if ((*it)->isLeaf()) { ICDBNode::CTextId textId; (*it)->removeObserver(this, textId); } else { CCDBNodeBranch *br = static_cast(*it); CInterfaceManager::getInstance()->removeBranchObserver( br, this ); } } } //=========================================================== void CInterfaceLink::updateAllLinks() { nlassert(!_UpdateAllLinks); _UpdateAllLinks = true; for(TLinkList::iterator it = _LinkList.begin(); it != _LinkList.end(); ++it) { CInterfaceLink *pLink = *it; pLink->update(); } _UpdateAllLinks = false; } //=========================================================== void CInterfaceLink::update() { //_On = false; // prvent recursive calls if (!_ParseTree) return; static TNodeVect observedNodes; // should not be too big, so ok to keep it static. static TNodeVect tmpNodes; observedNodes.clear(); CInterfaceExprValue result; _ParseTree->evalWithDepends(result, observedNodes); // std::set_xxx require ordered container std::sort(observedNodes.begin(), observedNodes.end()); // we assume that _ObservedNode are also sorted // compute new observers tmpNodes.clear(); std::set_difference(observedNodes.begin(), observedNodes.end(), _ObservedNodes.begin(), _ObservedNodes.end(), std::back_inserter(tmpNodes)); // add these observers createObservers(tmpNodes); // compute lost observers tmpNodes.clear(); std::set_difference(_ObservedNodes.begin(), _ObservedNodes.end(), observedNodes.begin(), observedNodes.end(), std::back_inserter(tmpNodes)); // remove these observers removeObservers(tmpNodes); // subsitute new (sorted) list to current _ObservedNodes.swap(observedNodes); // for (uint k = 0; k < _Targets.size(); ++k) { CInterfaceElement *elem = _Targets[k]._InterfaceElement; const CReflectedProperty *prop = _Targets[k]._Property; if (elem && prop) { if (!affect(result, *elem, *prop)) { nlwarning("CInterfaceLink::update: Result conversion failed"); } } } // if there's an action handler, execute it if (!_ActionHandler.empty()) { // If there is a condition, test it. bool launch= true; if(!_AHCond.empty()) { launch= false; CInterfaceExprValue result; if(CInterfaceExpr::eval(_AHCond, result)) launch= result.getBool(); } if(launch) { CInterfaceManager *im = CInterfaceManager::getInstance(); im->runActionHandler(_ActionHandler, _AHParent, _AHParams); // do not add any code after this line because this can be deleted !!!! } } //_On = true; // prevent recursive calls } // predicate to remove an element from a list struct CRemoveTargetPred { CInterfaceElement *Target; bool operator()(const CInterfaceLink::CTarget &target) const { return target._InterfaceElement == Target; } }; //=========================================================== void CInterfaceLink::removeTarget(CInterfaceElement *elem) { CRemoveTargetPred pred; pred.Target = elem; _Targets.erase(std::remove_if(_Targets.begin(), _Targets.end(), pred), _Targets.end()); } //=========================================================== bool CInterfaceLink::CTargetInfo::affect(const CInterfaceExprValue &value) { if (!Elem) { nlwarning(" : Target element is NULL"); return false; } const CReflectedProperty *property = Elem->getReflectedProperty(PropertyName); if (!property) { nlwarning(" : Can't retrieve property %s for element %s.", PropertyName.c_str(), Elem->getId().c_str()); return false; } // try to affect the property if (!::affect(value, *Elem, *property)) { nlwarning(" Couldn't convert the value to affect to %s", Elem->getId().c_str()); return false; } return true; } //=========================================================== void CInterfaceLink::removeAllLinks() { _LinksWithNoTarget.clear(); TLinkList::iterator curr = _LinkList.begin(); TLinkList::iterator nextIt; uint k; while(curr != _LinkList.end()) { nextIt = curr; ++ nextIt; CInterfaceLink &link = **curr; link.checkNbRefs(); NLMISC::CSmartPtr linkPtr = *curr; // must keep a smart ptr because of the test in the loop for (k = 0; k < link._Targets.size(); ++k) { if (link._Targets[k]._InterfaceElement) { nlinfo("InterfaceElement %s was not remove.", link._Targets[k]._InterfaceElement->getId().c_str()); link._Targets[k]._InterfaceElement->removeLink(&link); } } linkPtr = NULL; // effectively destroy link curr = nextIt; } nlassert(_LinkList.empty()); } //=========================================================== void CInterfaceLink::checkNbRefs() { uint numValidPtr = 0; for(uint k = 0; k < _Targets.size(); ++k) { if (_Targets[k]._InterfaceElement != NULL) ++numValidPtr; } nlassert(numValidPtr == (uint) crefs); } //=========================================================== void CInterfaceLink::setTargetProperty (const std::string &Target, const CInterfaceExprValue &val) { // Eval target ! string elt = Target.substr(0,Target.rfind(':')); CInterfaceManager *pIM = CInterfaceManager::getInstance(); CInterfaceElement *pIE = pIM->getElementFromId(elt); CInterfaceGroup *pIG = dynamic_cast(pIE); if (pIG == NULL) pIG = pIE->getParent(); if (pIG != NULL) { std::vector vTargets; CInterfaceParser::splitLinkTargets(Target, pIG, vTargets); if ((vTargets.size() > 0) && (vTargets[0].Elem)) { vTargets[0].affect(val); } } } //----------------------------------------------- void CInterfaceLink::linkInTriggerList(uint list) { nlassert(list < 2); if (_Triggered[list]) return; // already inserted in list _Triggered[list] = true; nlassert(!_PrevTriggeredLink[list]); nlassert(!_NextTriggeredLink[list]); if (!_FirstTriggeredLink[list]) { _FirstTriggeredLink[list] = _LastTriggeredLink[list] = this; } else { nlassert(!_LastTriggeredLink[list]->_NextTriggeredLink[list]); _LastTriggeredLink[list]->_NextTriggeredLink[list] = this; _PrevTriggeredLink[list] = _LastTriggeredLink[list]; _LastTriggeredLink[list] = this; } } //----------------------------------------------- void CInterfaceLink::unlinkFromTriggerList(uint list) { nlassert(list < 2); if (!_Triggered[list]) { // not linked in this list nlassert(_PrevTriggeredLink[list] == NULL); nlassert(_NextTriggeredLink[list] == NULL); return; } if (_PrevTriggeredLink[list]) { _PrevTriggeredLink[list]->_NextTriggeredLink[list] = _NextTriggeredLink[list]; } else { // this was the first node _FirstTriggeredLink[list] = _NextTriggeredLink[list]; } if (_NextTriggeredLink[list]) { _NextTriggeredLink[list]->_PrevTriggeredLink[list] = _PrevTriggeredLink[list]; } else { // this was the last node _LastTriggeredLink[list] = _PrevTriggeredLink[list]; } _PrevTriggeredLink[list] = NULL; _NextTriggeredLink[list] = NULL; _Triggered[list] = false; } //----------------------------------------------- void CInterfaceLink::updateTrigeredLinks() { static bool called = false; nlassert(!called); called = true; _CurrUpdatedLink = _FirstTriggeredLink[_CurrentTriggeredLinkList]; while (_CurrUpdatedLink) { // modified node should now store them in other list when they're modified _CurrentTriggeredLinkList = 1 - _CurrentTriggeredLinkList; // switch list so that modified node are stored in the other list while (_CurrUpdatedLink) { _NextUpdatedLink = _CurrUpdatedLink->_NextTriggeredLink[1 - _CurrentTriggeredLinkList]; _CurrUpdatedLink->update(); if (_CurrUpdatedLink) // this may be modified by the call (if current observer is removed) { _CurrUpdatedLink->unlinkFromTriggerList(1 - _CurrentTriggeredLinkList); } _CurrUpdatedLink = _NextUpdatedLink; } nlassert(_FirstTriggeredLink[1 - _CurrentTriggeredLinkList] == NULL); nlassert(_LastTriggeredLink[1 - _CurrentTriggeredLinkList] == NULL); // examine other list to see if nodes have been registered _CurrUpdatedLink = _FirstTriggeredLink[_CurrentTriggeredLinkList]; } called = false; }