Merge branch 'main/rendor-staging' into fixes
[ryzomcore.git] / nel / src / gui / interface_link.cpp
blob5b9bebb1f911be68aac6da648a83cdbf96b435e9
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2013 Laszlo KIS-ADAM (dfighter) <dfighter1985@gmail.com>
6 // Copyright (C) 2013-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 //
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Affero General Public License as
10 // published by the Free Software Foundation, either version 3 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU Affero General Public License for more details.
18 // You should have received a copy of the GNU Affero General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "stdpch.h"
23 #include "nel/gui/interface_expr.h"
24 #include "nel/gui/interface_expr_node.h"
25 #include "nel/gui/reflect.h"
26 #include "nel/gui/db_manager.h"
27 #include "nel/misc/cdb_branch.h"
28 #include "nel/gui/action_handler.h"
29 #include "nel/gui/interface_link.h"
30 #include "nel/gui/interface_element.h"
31 #include "nel/gui/interface_group.h"
32 #include "nel/gui/widget_manager.h"
33 #include "nel/misc/algo.h"
35 #include <iterator>
37 using namespace std;
38 using namespace NLMISC;
40 #ifdef DEBUG_NEW
41 #define new DEBUG_NEW
42 #endif
44 namespace NLGUI
47 /////////////
48 // GLOBALS //
49 /////////////
51 CInterfaceLink::TLinkList CInterfaceLink::_LinkList;
52 CInterfaceLink::TLinkVect CInterfaceLink::_LinksWithNoTarget;
54 CInterfaceLink *CInterfaceLink::_FirstTriggeredLink[2] = { NULL, NULL };
55 CInterfaceLink *CInterfaceLink::_LastTriggeredLink[2] = { NULL, NULL };
56 CInterfaceLink *CInterfaceLink::_CurrUpdatedLink = NULL;
57 CInterfaceLink *CInterfaceLink::_NextUpdatedLink = NULL;
58 uint CInterfaceLink::_CurrentTriggeredLinkList = 0;
60 bool CInterfaceLink::_UpdateAllLinks = false;
63 ///////////////
64 // FUNCTIONS //
65 ///////////////
67 /** Tool fct : affect a value to a reflected property of an interface element.
69 static bool affect(const CInterfaceExprValue &value, CInterfaceElement &destElem, const CReflectedProperty &property)
72 CInterfaceExprValue valueToAffect = value;
73 switch(property.Type)
75 case CReflectedProperty::Boolean:
76 if (valueToAffect.toBool())
78 (destElem.*(property.SetMethod.SetBool))(valueToAffect.getBool());
80 else
83 return false;
85 break;
86 case CReflectedProperty::SInt32:
87 if (valueToAffect.toInteger())
89 (destElem.*(property.SetMethod.SetSInt32))((sint32) valueToAffect.getInteger());
91 else
94 return false;
96 break;
97 case CReflectedProperty::Float:
98 if (valueToAffect.toDouble())
100 (destElem.*(property.SetMethod.SetFloat))((float) valueToAffect.getDouble());
102 else
105 return false;
107 break;
108 case CReflectedProperty::String:
109 case CReflectedProperty::StringRef:
110 if (valueToAffect.toString())
112 (destElem.*(property.SetMethod.SetString))(valueToAffect.getString());
114 else
117 return false;
119 break;
120 #ifdef RYZOM_LUA_UCSTRING
121 case CReflectedProperty::UCString:
122 case CReflectedProperty::UCStringRef:
123 if (valueToAffect.toString())
125 (destElem.*(property.SetMethod.SetUCString))(ucstring::makeFromUtf8(valueToAffect.getString()));
127 else
129 return false;
131 #endif
132 break;
133 case CReflectedProperty::RGBA:
134 if (valueToAffect.toRGBA())
136 (destElem.*(property.SetMethod.SetRGBA))(valueToAffect.getRGBA());
138 else
141 return false;
143 break;
144 default:
145 break;
148 return true;
151 CInterfaceLink::CInterfaceLinkUpdater::CInterfaceLinkUpdater()
153 NLGUI::CDBManager::getInstance()->addFlushObserver( this );
156 CInterfaceLink::CInterfaceLinkUpdater::~CInterfaceLinkUpdater()
160 void CInterfaceLink::CInterfaceLinkUpdater::onObserverCallFlush()
162 CInterfaceLink::updateTrigeredLinks();
165 /////////////
166 // MEMBERS //
167 /////////////
169 //===========================================================
170 CInterfaceLink::CInterfaceLink() : _On(true)
172 // add an entry in the links list
173 _LinkList.push_front(this);
174 _ListEntry = _LinkList.begin();
175 _PrevTriggeredLink[0] = _PrevTriggeredLink[1] = NULL;
176 _NextTriggeredLink[0] = _NextTriggeredLink[1] = NULL;
177 _Triggered[0] = _Triggered[1] = false;
178 _ParseTree = NULL;
179 _AHCondParsed = NULL;
182 //===========================================================
183 CInterfaceLink::~CInterfaceLink()
185 if (this == _CurrUpdatedLink)
187 _CurrUpdatedLink = NULL;
189 if (this == _NextUpdatedLink)
191 _NextUpdatedLink = _NextUpdatedLink->_NextTriggeredLink[1 - _CurrentTriggeredLinkList];
193 // unlink observer from both update lists
194 unlinkFromTriggerList(0);
195 unlinkFromTriggerList(1);
197 removeObservers(_ObservedNodes);
198 // removes from the link list
199 _LinkList.erase(_ListEntry);
201 delete _ParseTree;
202 _ParseTree = NULL;
203 delete _AHCondParsed;
204 _AHCondParsed = NULL;
207 //===========================================================
208 bool CInterfaceLink::init(const std::vector<CTargetInfo> &targets, const std::vector<CCDBTargetInfo> &cdbTargets, const std::string &expr, const std::string &actionHandler, const std::string &ahParams, const std::string &ahCond, CInterfaceGroup *parentGroup)
210 CInterfaceExprValue result;
211 // Build the parse tree
212 nlassert(!_ParseTree);
213 _ParseTree = CInterfaceExpr::buildExprTree(expr);
214 if (!_ParseTree)
216 // wrong expression : release the link
217 nlwarning("CInterfaceLink::init : can't parse expression %s", expr.c_str());
218 _LinksWithNoTarget.push_back(TLinkSmartPtr(this));
219 return false;
221 // Examine the expression, to see which database nodes it depends on
222 _ParseTree->getDepends(_ObservedNodes);
223 // must keep observerd node sorted (for std::set_xx ops)
224 std::sort(_ObservedNodes.begin(), _ObservedNodes.end());
225 if (!targets.empty())
227 // retrieve properties from the element
228 _Targets.resize(targets.size());
229 for (uint k = 0; k < targets.size(); ++k)
231 CInterfaceElement *elem = targets[k].Elem;
232 if (!elem)
234 nlwarning("<CInterfaceLink::init> : Element %d is NULL", k);
235 _Targets[k]._InterfaceElement = NULL;
236 continue;
238 _Targets[k]._Property = elem->getReflectedProperty(targets[k].PropertyName);
239 if (!_Targets[k]._Property)
241 nlwarning("<CInterfaceLink::init> : Can't retrieve property %s for element %d.", targets[k].PropertyName.c_str(), k);
242 _Targets[k]._InterfaceElement = NULL;
243 continue;
245 _Targets[k]._InterfaceElement = elem;
246 elem->addLink(this);
249 else
251 // There are no target for this link, so, put in a dedicated list to ensure that the link will be destroyed at exit
252 _LinksWithNoTarget.push_back(TLinkSmartPtr(this));
254 _CDBTargets = cdbTargets;
256 // create observers
257 createObservers(_ObservedNodes);
258 _Expr = expr;
260 _ActionHandler = actionHandler;
261 _AHParams = ahParams;
262 nlassert(!_AHCondParsed);
263 _AHCond = ahCond;
264 if (!ahCond.empty())
266 _AHCondParsed = CInterfaceExpr::buildExprTree(ahCond);
268 _AHParent = parentGroup;
269 return true;
272 //===========================================================
273 void CInterfaceLink::uninit()
275 for (uint32 i = 0; i < _LinksWithNoTarget.size(); i++)
277 CInterfaceLink *pLink = _LinksWithNoTarget[i];
278 if (pLink == this)
280 _LinksWithNoTarget.erase(_LinksWithNoTarget.begin()+i);
281 return;
286 //===========================================================
287 void CInterfaceLink::update(ICDBNode * /* node */)
289 // mark link as triggered
290 linkInTriggerList(_CurrentTriggeredLinkList);
293 //===========================================================
294 void CInterfaceLink::createObservers(const TNodeVect &nodes)
296 for(std::vector<ICDBNode *>::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
298 if ((*it)->isLeaf())
300 ICDBNode::CTextId textId;
301 (*it)->addObserver(this, textId);
303 else
305 CCDBNodeBranch *br = static_cast<CCDBNodeBranch *>(*it);
306 NLGUI::CDBManager::getInstance()->addBranchObserver( br, this );
311 //===========================================================
312 void CInterfaceLink::removeObservers(const TNodeVect &nodes)
314 for(std::vector<ICDBNode *>::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
316 if ((*it)->isLeaf())
318 ICDBNode::CTextId textId;
319 (*it)->removeObserver(this, textId);
321 else
323 CCDBNodeBranch *br = static_cast<CCDBNodeBranch *>(*it);
324 NLGUI::CDBManager::getInstance()->removeBranchObserver( br, this );
329 //===========================================================
330 void CInterfaceLink::updateAllLinks()
332 nlassert(!_UpdateAllLinks);
333 _UpdateAllLinks = true;
334 for(TLinkList::iterator it = _LinkList.begin(); it != _LinkList.end(); ++it)
336 CInterfaceLink *pLink = *it;
337 pLink->update();
339 _UpdateAllLinks = false;
342 //===========================================================
343 void CInterfaceLink::update()
345 //_On = false; // prvent recursive calls
346 if (!_ParseTree) return;
347 static TNodeVect observedNodes; // should not be too big, so ok to keep it static.
348 static TNodeVect tmpNodes;
349 observedNodes.clear();
350 CInterfaceExprValue result;
351 _ParseTree->evalWithDepends(result, observedNodes);
352 // std::set_xxx require ordered container
353 std::sort(observedNodes.begin(), observedNodes.end());
354 // we assume that _ObservedNode are also sorted
355 // compute new observers
356 tmpNodes.clear();
357 std::set_difference(observedNodes.begin(), observedNodes.end(), _ObservedNodes.begin(), _ObservedNodes.end(), std::back_inserter(tmpNodes));
358 // add these observers
359 createObservers(tmpNodes);
360 // compute lost observers
361 tmpNodes.clear();
362 std::set_difference(_ObservedNodes.begin(), _ObservedNodes.end(), observedNodes.begin(), observedNodes.end(), std::back_inserter(tmpNodes));
363 // remove these observers
364 removeObservers(tmpNodes);
365 // subsitute new (sorted) list to current
366 _ObservedNodes.swap(observedNodes);
368 for (uint k = 0; k < _Targets.size(); ++k)
370 CInterfaceElement *elem = _Targets[k]._InterfaceElement;
371 const CReflectedProperty *prop = _Targets[k]._Property;
372 if (elem && prop)
374 if (!affect(result, *elem, *prop))
376 nlwarning("CInterfaceLink::update: Result conversion failed");
380 if (_CDBTargets.size())
382 CInterfaceExprValue resultCopy = result;
383 if (resultCopy.toInteger())
385 sint64 resultValue = resultCopy.getInteger();
386 for (uint k = 0; k < _CDBTargets.size(); ++k)
388 NLMISC::CCDBNodeLeaf *node = _CDBTargets[k].Leaf;
389 if (!node)
391 node = _CDBTargets[k].Leaf = NLGUI::CDBManager::getInstance()->getDbProp(_CDBTargets[k].LeafName, false);
393 if (node)
395 // assuming setvalue64 always works
396 node->setValue64(resultValue);
398 else
400 nlwarning("CInterfaceLink::update: Node does not exist: '%s'", _CDBTargets[k].LeafName.c_str());
404 else
406 nlwarning("CInterfaceLink::update: Result conversion to db target failed");
409 // if there's an action handler, execute it
410 if (!_ActionHandler.empty())
412 // If there is a condition, test it.
413 bool launch = _AHCond.empty();
414 if (_AHCondParsed) // todo: maybe makes more sense to make condition also cover target
416 CInterfaceExprValue result;
417 _AHCondParsed->eval(result);
418 launch = result.getBool();
420 if (launch)
422 CAHManager::getInstance()->runActionHandler(_ActionHandler, _AHParent, _AHParams);
423 // do not add any code after this line because this can be deleted !!!!
427 //_On = true; // prevent recursive calls
430 // predicate to remove an element from a list
431 struct CRemoveTargetPred
433 CInterfaceElement *Target;
434 bool operator()(const CInterfaceLink::CTarget &target) const { return target._InterfaceElement == Target; }
437 //===========================================================
438 void CInterfaceLink::removeTarget(CInterfaceElement *elem)
440 CRemoveTargetPred pred;
441 pred.Target = elem;
442 _Targets.erase(std::remove_if(_Targets.begin(), _Targets.end(), pred), _Targets.end());
445 //===========================================================
446 bool CInterfaceLink::CTargetInfo::affect(const CInterfaceExprValue &value)
449 if (!Elem)
451 nlwarning("<CInterfaceLink::CTargetInfo::affect> : Target element is NULL");
453 return false;
455 const CReflectedProperty *property = Elem->getReflectedProperty(PropertyName);
456 if (!property)
458 nlwarning("<CInterfaceLink::CTargetInfo::affect> : Can't retrieve property %s for element %s.", PropertyName.c_str(), Elem->getId().c_str());
460 return false;
462 // try to affect the property
463 if (!NLGUI::affect(value, *Elem, *property))
465 nlwarning("<CInterfaceLink::CTargetInfo::affect> Couldn't convert the value to affect to %s", Elem->getId().c_str());
467 return false;
470 return true;
473 //===========================================================
474 void CInterfaceLink::removeAllLinks()
476 _LinksWithNoTarget.clear();
477 TLinkList::iterator curr = _LinkList.begin();
478 TLinkList::iterator nextIt;
479 uint k;
480 while(curr != _LinkList.end())
482 nextIt = curr;
483 ++ nextIt;
484 CInterfaceLink &link = **curr;
485 link.checkNbRefs();
486 NLMISC::CSmartPtr<CInterfaceLink> linkPtr = *curr; // must keep a smart ptr because of the test in the loop
487 for (k = 0; k < link._Targets.size(); ++k)
489 if (link._Targets[k]._InterfaceElement)
491 nlinfo("InterfaceElement %s was not remove.", link._Targets[k]._InterfaceElement->getId().c_str());
492 link._Targets[k]._InterfaceElement->removeLink(&link);
495 linkPtr = NULL; // effectively destroy link
496 curr = nextIt;
498 nlassert(_LinkList.empty());
501 // ***************************************************************************
502 bool CInterfaceLink::splitLinkTarget(const std::string &target, CInterfaceGroup *parentGroup, std::string &propertyName, CInterfaceElement *&targetElm)
504 // the last token of the target gives the name of the property
505 std::string::size_type lastPos = target.find_last_of(':');
506 if (lastPos == (target.length() - 1))
508 // todo hulud interface syntax error
509 nlwarning("The target should at least contains a path and a property as follow 'path:property'");
510 return false;
512 std::string elmPath;
513 std::string elmProp;
514 CInterfaceElement *elm = NULL;
515 if (parentGroup)
517 if (lastPos == std::string::npos)
519 elmProp = target;
520 elm = parentGroup;
521 elmPath = "current";
523 else
525 elmProp = target.substr(lastPos + 1);
526 elmPath = parentGroup->getId() + ":" + target.substr(0, lastPos);
527 elm = parentGroup->getElement(elmPath);
530 if (!elm)
532 // try the absolute adress of the element
533 elmPath = target.substr(0, lastPos);
534 elm = CWidgetManager::getInstance()->getElementFromId(elmPath);
535 elmProp = target.substr(lastPos + 1);
538 if (!elm)
540 // todo hulud interface syntax error
541 nlwarning("<splitLinkTarget> can't find target link %s", elmPath.c_str());
542 return false;
544 targetElm = elm;
545 propertyName = elmProp;
546 return true;
550 // ***************************************************************************
551 bool CInterfaceLink::splitLinkTargets(const std::string &targets, CInterfaceGroup *parentGroup,std::vector<CInterfaceLink::CTargetInfo> &targetsVect)
553 std::vector<std::string> targetNames;
554 NLMISC::splitString(targets, ",", targetNames);
555 targetsVect.clear();
556 targetsVect.reserve(targetNames.size());
557 bool everythingOk = true;
558 for (uint k = 0; k < targetNames.size(); ++k)
560 CInterfaceLink::CTargetInfo ti;
561 std::string::size_type startPos = targetNames[k].find_first_not_of(" ");
562 if(startPos == std::string::npos)
564 // todo hulud interface syntax error
565 nlwarning("<splitLinkTargets> empty target encountered");
566 continue;
568 std::string::size_type lastPos = targetNames[k].find_last_not_of(" ");
569 if (startPos >= lastPos)
571 nlwarning("<splitLinkTargets> empty target encountered");
572 continue;
575 if (!splitLinkTarget(targetNames[k].substr(startPos, lastPos - startPos+1), parentGroup, ti.PropertyName, ti.Elem))
577 // todo hulud interface syntax error
578 nlwarning("<splitLinkTargets> Can't get link target");
579 everythingOk = false;
580 continue;
582 targetsVect.push_back(ti);
584 return everythingOk;
588 // ***************************************************************************
589 bool CInterfaceLink::splitLinkTargetsExt(const std::string &targets, CInterfaceGroup *parentGroup,std::vector<CInterfaceLink::CTargetInfo> &targetsVect, std::vector<CInterfaceLink::CCDBTargetInfo> &cdbTargetsVect)
591 std::vector<std::string> targetNames;
592 NLMISC::splitString(targets, ",", targetNames);
593 targetsVect.clear();
594 targetsVect.reserve(targetNames.size());
595 cdbTargetsVect.clear(); // no reserve because less used
596 bool everythingOk = true;
597 for (uint k = 0; k < targetNames.size(); ++k)
599 std::string::size_type startPos = targetNames[k].find_first_not_of(" ");
600 if(startPos == std::string::npos)
602 // todo hulud interface syntax error
603 nlwarning("<splitLinkTargets> empty target encountered");
604 continue;
606 std::string::size_type lastPos = targetNames[k].find_last_not_of(" ");
607 if (startPos >= (lastPos+1))
609 nlwarning("<splitLinkTargets> empty target encountered");
610 continue;
613 if (targetNames[k][startPos] == '@')
615 CInterfaceLink::CCDBTargetInfo ti;
616 ti.LeafName = targetNames[k].substr((startPos+1), (lastPos+1) - (startPos+1));
617 // Do not allow Write on SERVER: or LOCAL:
618 static const std::string dbServer= "SERVER:";
619 static const std::string dbLocal= "LOCAL:";
620 static const std::string dbLocalR2= "LOCAL:R2";
621 if( (0==ti.LeafName.compare(0, dbServer.size(), dbServer)) ||
622 (0==ti.LeafName.compare(0, dbLocal.size(), dbLocal))
625 if (0!=ti.LeafName.compare(0, dbLocalR2.size(), dbLocalR2))
627 //nlwarning("You are not allowed to write on 'SERVER:...' or 'LOCAL:...' database");
628 nlstop;
629 return false;
632 ti.Leaf = NLGUI::CDBManager::getInstance()->getDbProp(ti.LeafName, false);
633 cdbTargetsVect.push_back(ti);
635 else
637 CInterfaceLink::CTargetInfo ti;
638 if (!splitLinkTarget(targetNames[k].substr(startPos, lastPos - startPos+1), parentGroup, ti.PropertyName, ti.Elem))
640 // todo hulud interface syntax error
641 nlwarning("<splitLinkTargets> Can't get link target");
642 everythingOk = false;
643 continue;
645 targetsVect.push_back(ti);
648 return everythingOk;
652 //===========================================================
653 void CInterfaceLink::checkNbRefs()
655 uint numValidPtr = 0;
656 for(uint k = 0; k < _Targets.size(); ++k)
658 if (_Targets[k]._InterfaceElement != NULL) ++numValidPtr;
660 nlassert(numValidPtr == (uint) crefs);
664 //===========================================================
665 void CInterfaceLink::setTargetProperty (const std::string &Target, const CInterfaceExprValue &val)
667 // Eval target !
668 string elt = Target.substr(0,Target.rfind(':'));
669 CInterfaceElement *pIE = CWidgetManager::getInstance()->getElementFromId(elt);
670 CInterfaceGroup *pIG = dynamic_cast<CInterfaceGroup*>(pIE);
671 nlassert(pIE);
672 if (pIG == NULL)
673 pIG = pIE->getParent();
675 if (pIG != NULL)
677 std::vector<CTargetInfo> vTargets;
678 splitLinkTargets(Target, pIG, vTargets);
679 if (!vTargets.empty() && (vTargets[0].Elem))
681 vTargets[0].affect(val);
686 //-----------------------------------------------
687 void CInterfaceLink::linkInTriggerList(uint list)
689 nlassert(list < 2);
690 if (_Triggered[list]) return; // already inserted in list
691 _Triggered[list] = true;
692 nlassert(!_PrevTriggeredLink[list]);
693 nlassert(!_NextTriggeredLink[list]);
694 if (!_FirstTriggeredLink[list])
696 _FirstTriggeredLink[list] = _LastTriggeredLink[list] = this;
698 else
700 nlassert(!_LastTriggeredLink[list]->_NextTriggeredLink[list]);
701 _LastTriggeredLink[list]->_NextTriggeredLink[list] = this;
702 _PrevTriggeredLink[list] = _LastTriggeredLink[list];
703 _LastTriggeredLink[list] = this;
707 //-----------------------------------------------
708 void CInterfaceLink::unlinkFromTriggerList(uint list)
710 nlassert(list < 2);
711 if (!_Triggered[list])
713 // not linked in this list
714 nlassert(_PrevTriggeredLink[list] == NULL);
715 nlassert(_NextTriggeredLink[list] == NULL);
716 return;
718 if (_PrevTriggeredLink[list])
720 _PrevTriggeredLink[list]->_NextTriggeredLink[list] = _NextTriggeredLink[list];
722 else
724 // this was the first node
725 _FirstTriggeredLink[list] = _NextTriggeredLink[list];
727 if (_NextTriggeredLink[list])
729 _NextTriggeredLink[list]->_PrevTriggeredLink[list] = _PrevTriggeredLink[list];
731 else
733 // this was the last node
734 _LastTriggeredLink[list] = _PrevTriggeredLink[list];
736 _PrevTriggeredLink[list] = NULL;
737 _NextTriggeredLink[list] = NULL;
738 _Triggered[list] = false;
745 //-----------------------------------------------
746 void CInterfaceLink::updateTrigeredLinks()
748 static bool called = false;
749 nlassert(!called);
750 called = true;
751 _CurrUpdatedLink = _FirstTriggeredLink[_CurrentTriggeredLinkList];
752 while (_CurrUpdatedLink)
754 // modified node should now store them in other list when they're modified
755 _CurrentTriggeredLinkList = 1 - _CurrentTriggeredLinkList;
756 // switch list so that modified node are stored in the other list
757 while (_CurrUpdatedLink)
759 _NextUpdatedLink = _CurrUpdatedLink->_NextTriggeredLink[1 - _CurrentTriggeredLinkList];
760 _CurrUpdatedLink->update();
761 if (_CurrUpdatedLink) // this may be modified by the call (if current observer is removed)
763 _CurrUpdatedLink->unlinkFromTriggerList(1 - _CurrentTriggeredLinkList);
765 _CurrUpdatedLink = _NextUpdatedLink;
767 nlassert(_FirstTriggeredLink[1 - _CurrentTriggeredLinkList] == NULL);
768 nlassert(_LastTriggeredLink[1 - _CurrentTriggeredLinkList] == NULL);
769 // examine other list to see if nodes have been registered
770 _CurrUpdatedLink = _FirstTriggeredLink[_CurrentTriggeredLinkList];
772 called = false;