FlightGear next
ViewPropertyEvaluator.cxx
Go to the documentation of this file.
1// This program is free software; you can redistribute it and/or
2// modify it under the terms of the GNU General Public License as
3// published by the Free Software Foundation; either version 2 of the
4// License, or (at your option) any later version.
5//
6// This program is distributed in the hope that it will be useful, but
7// WITHOUT ANY WARRANTY; without even the implied warranty of
8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9// General Public License for more details.
10//
11// You should have received a copy of the GNU General Public License
12// along with this program; if not, write to the Free Software
13// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14
16
17#include "Main/globals.hxx"
18
19#include <algorithm>
20#include <cassert>
21
22
24
25 /* We represent a spec as graph, using alternating Sequence and Node
26 objects so that different specs share common information; e.g. this ensures
27 that we don't install more than one listener for the same SGPropertyNode.
28
29 Evaluating top-level nodes:
30
31 Currently ViewPropertyEvaluator::getDoubleValue() will always
32 reevaluate the top-level SGPropertyNode by calling its getDoubleValue()
33 member. This usually gives the desired behaviour because most
34 final property nodes that we are used with don't appear to make
35 valueChanged() callbacks, and it's anyway probably more efficient to
36 not use such callbacks for rapidly-changing values.
37
38 However it would be good to be clearer about this, e.g. maybe we could
39 have a second bracket notation to indicate that we should evaluate and
40 cache the SGPropertyNode but not its string/double value. E.g.:
41
42 ViewPropertyEvaluator::getDoubleValue(
43 "{(/sim/view[0]/config/root)/position/altitude-ft}"
44 );
45
46 - would not attempt to install a valueChanged() callback for the
47 top-level SGPropertyNode.
48*/
49
50 struct Sequence;
51 struct Node;
52
53 struct Sequence
54 {
55 Sequence();
56
57 std::vector<std::shared_ptr<Node>> _nodes;
58 std::vector<Node*> _parents;
59 bool _rescan;
60 std::string _value;
61 };
62
63 struct Node : SGPropertyChangeListener
64 {
65 explicit Node(const char* spec);
66
67 /* SGPropertyChangeListener callback. */
68 void valueChanged(SGPropertyNode* node);
69
70 const char* _begin;
71 const char* _end;
72 bool _rescan;
73 std::string _value;
74
75 std::vector<Sequence*> _parents;
76
77 // Only used if _begin.._end is (...).
78 std::shared_ptr<Sequence> _child;
79 SGPropertyNode_ptr _sgnode;
80 SGPropertyNode_ptr _sgnode_listen;
81 };
82
83 /* Helper for dumping a Sequence to an ostream. Prefixes all lines with
84 <indent>. If <deep> is true, recursively shows all child sequences and
85 nodes. */
87 {
88 SequenceDump(const Sequence& sequence, const std::string& indent="", bool deep=false);
89
91 const std::string& _indent;
92 bool _deep;
93
94 friend std::ostream& operator << (std::ostream& out, const SequenceDump& self);
95 };
96
97 /* Helper for dumping a Node to an ostream. Prefixes all lines with
98 <indent>. If <deep> is true, recursively shows all child sequences and
99 nodes. */
100 struct NodeDump
101 {
102 NodeDump(const Node& node, const std::string& indent="", bool deep=false);
103
104 const Node& _node;
105 const std::string& _indent;
106 bool _deep;
107
108 friend std::ostream& operator << (std::ostream& out, const NodeDump& self);
109 };
110
111 /* Support for debug statistics. */
112 struct Debug
113 {
114 /* Support for tracking how many property system accesses we are making. */
115 struct Stat
116 {
117 Stat() : n(0) {}
118 int n;
119 };
120
121 /* Increments counter for <name>. Periodically outputs stats with
122 SG_LOG(SG_VIEW, SG_DEBUG, ...) and detailed information about Sequences
123 and Nodes with SG_LOG(SG_VIEW, SG_BULK, ...). */
124 void statsAdd(const char* name);
125 void statsReset();
126 struct StatsShow {};
127 friend std::ostream& operator << (std::ostream& out, const StatsShow&);
128
129 /* Track how many listeners we have. */
130 void listensAdd(SGPropertyNode_ptr node);
131 void listensRemove(SGPropertyNode_ptr node);
132
133 time_t statsT0 = 0;
134 std::map<std::string, std::shared_ptr<Stat>> stats;
135 std::vector<SGPropertyNode_ptr> listens;
136 };
137
139
140 /* Forces this node and all of its sequence and node parents to be
141 re-read the next time they are evaluated - e.g. the next call of
142 getNodeStringValue() will call getSequenceStringValue() on _child and
143 write the result into the <_value> member before returning <_value>. */
144 void rescanNode(Node& node);
145
146 /* Forces this sequence and all of its node and sequence parents to
147 be re-read the next time they are evaluated - e.g. the next call of
148 getSequenceStringValue() will call getNodeStringValue() on each child
149 node and concatenate the results into the <_value> member before
150 returning <_value>. */
151 void rescanSequence(Sequence& sequence);
152
153 /* Returns Sequence for spec starting at <spec>, which can be subsequently
154 used to evaluate the spec efficiently. We require that the string <spec>
155 will remain unchanged forever. */
156 std::shared_ptr<Sequence> getSequence(const char* spec);
157
158 /* Returns evaluated spec for <node> using caching. Mainly used
159 internally.
160
161 If node spec is of the form "<...>", we look up the value in the
162 property system. */
163 const std::string& getNodeStringValue(Node& node);
164
165 /* Returns evaluated spec for <sequence> using caching. Mainly used
166 internally. Concatenates the string value of each child node. */
167 const std::string& getSequenceStringValue(Sequence& sequence);
168
169 /* <sequence> must be from a spec with a top-level '(...)'. Returns the
170 specified property-tree node. */
171 SGPropertyNode* getSequenceNode(Sequence& sequence);
172
173 double getSequenceDoubleValue(Sequence& sequence, double default_=0);
174
175 /* Finds end of section of spec starting at <spec>, to be used as the
176 region of a Sequence; this will contain one or more regions corresponding
177 to a Node. Any ')' without a corresponding '(' is treated as a terminator.
178 */
179 const char* getSequenceEnd(const char* spec)
180 {
181 int nesting = 0;
182 for (const char* s=spec; ; ++s) {
183 if (*s == 0) {
184 assert(nesting == 0);
185 return s;
186 }
187 if (*s == '(') {
188 nesting += 1;
189 }
190 if (*s == ')') {
191 if (nesting == 0) {
192 return s;
193 }
194 nesting -= 1;
195 }
196 }
197 }
198
199 /* Finds end of section of spec starting at <spec>, to be used as the
200 region of a Node.
201
202 We parse things similarly to getSequenceEnd() except that we also terminate
203 early in the following situations:
204
205 The first character is not '(' and we find a '('.
206
207 The first character is '(' and we find the corresponding ')'.
208 */
209 const char* getNodeEnd(const char* spec)
210 {
211 int nesting = 0;
212 for (const char* s=spec; ; ++s) {
213 if (*s == 0) {
214 assert(nesting == 0);
215 return s;
216 }
217 if (*s == '(') {
218 if (spec[0] != '(') {
219 return s;
220 }
221 nesting += 1;
222 }
223 if (*s == ')') {
224 if (nesting == 0) {
225 return s;
226 }
227 nesting -= 1;
228 if (spec[0] == '(' && nesting == 0) {
229 return s + 1;
230 }
231 }
232 }
233 }
234
235 /* Raw constructor. We only set up basic values; the rest is done
236 by getNodeInternal(). */
237 Node::Node(const char* spec)
238 :
239 _begin(spec),
241 _rescan(true)
242 {
243 }
244
245 void Node::valueChanged(SGPropertyNode* node)
246 {
247 debug.statsAdd("valueChanged");
248 SG_LOG(SG_VIEW, SG_DEBUG, "valueChanged():"
249 << " node->_sgnode_listen->getPath()='" << _sgnode_listen->getPath() << "'"
250 << " node->getPath()='" << node->getPath() << "'"
251 );
252 rescanNode(*this);
253 }
254
256 :
257 _rescan(true)
258 {
259 }
260
261 void rescanNode(Node& node)
262 {
263 node._rescan = true;
264 for (auto sequence: node._parents) {
265 rescanSequence(*sequence);
266 }
267 }
268
269 void rescanSequence(Sequence& sequence)
270 {
271 sequence._rescan = true;
272 for (auto node: sequence._parents) {
273 rescanNode(*node);
274 }
275 }
276
277 /* Caches of various structures so that we can look things up quickly.
278 */
279
280 /* This is the main cache. It uses raw C string pointers (pointing
281 to specs) as keys, so lookups only require a handful of pointer
282 comparisons. */
283 std::map<const char*, std::shared_ptr<Sequence>> spec_to_sequence;
284
285 /* These are only used when parsing new specs and creating new nodes and
286 sequencies, so are not speed-critical. */
287 std::map<std::string, std::shared_ptr<Sequence>> string_to_sequence;
288 std::map<std::string, std::shared_ptr<Node>> string_to_node;
289
290
291 /* Finds or creates new Sequence for (possibly initial) portion of <spec>.
292 */
293 std::shared_ptr<Sequence> getSequenceInternal(const char* spec, Node* parent);
294
295 /* Finds or creates new Node for (possibly initial) portion of <spec>.
296 */
297 std::shared_ptr<Node> getNodeInternal(const char* spec, Sequence* parent);
298
299 std::shared_ptr<Sequence> getSequenceInternal(const char* spec, Node* parent)
300 {
301 if (spec[0] == 0 || spec[0] == ')') {
302 return NULL;
303 }
304
305 std::shared_ptr<Sequence> sequence;
306
307 std::string spec_string(spec, getSequenceEnd(spec));
308 auto it = string_to_sequence.find(spec_string);
309 if (it == string_to_sequence.end()) {
310 sequence.reset(new Sequence);
311 for(const char* s = spec;;) {
312 std::shared_ptr<Node> node
313 = getNodeInternal(s, sequence.get() /*parent*/);
314 if (!node) break;
315 sequence->_nodes.push_back(node);
316 s += (node->_end - node->_begin);
317 }
318 string_to_sequence[spec_string] = sequence;
319 }
320 else {
321 sequence = it->second;
322 }
323
324 if (parent) {
325 auto it = std::find(
326 sequence->_parents.begin(),
327 sequence->_parents.end(),
328 parent
329 );
330 if (it == sequence->_parents.end()) {
331 sequence->_parents.push_back(parent);
332 }
333 }
334 return sequence;
335 }
336
337 std::shared_ptr<Node> getNodeInternal(const char* spec, Sequence* parent)
338 {
339 if (spec[0] == 0 || spec[0] == ')') {
340 return NULL;
341 }
342
343 std::shared_ptr<Node> node;
344
345 std::string s(spec, getNodeEnd(spec));
346 auto it = string_to_node.find(s);
347
348 if (it == string_to_node.end()) {
349 node.reset(new Node(spec));
350 if (node->_begin[0] == '(') {
351 node->_child = getSequenceInternal(node->_begin + 1, node.get() /*parent*/);
352 }
353 string_to_node[s] = node;
354 }
355 else {
356 node = it->second;
357 }
358
359 if (parent) {
360 if (std::find(node->_parents.begin(), node->_parents.end(), parent)
361 == node->_parents.end()) {
362 node->_parents.push_back(parent);
363 }
364 }
365
366 return node;
367 }
368
369 /* Finds or creates new Sequence for (possibly initial) portion of <spec>
370 and sets spec_to_sequence[spec] so that it can be quickly looked up in
371 future. */
372 std::shared_ptr<Sequence> getSequence(const char* spec)
373 {
374 auto it = spec_to_sequence.find(spec);
375 if (it != spec_to_sequence.end()) {
376 return it->second;
377 }
378
379 std::shared_ptr<Sequence> sequence
380 = getSequenceInternal(spec, NULL /*parent*/);
381 spec_to_sequence[spec] = sequence;
382 SG_LOG(SG_VIEW, SG_DEBUG,
383 "Created new sequence:\n"
384 << SequenceDump(*sequence, " ", true /*deep*/)
385 );
386 return sequence;
387 }
388
389 /* Evaluates <node> to find path and returns corresponding SGPropertyNode*
390 in global properties. If <cache> is true, we install a listener on
391 the returned node, so that we force a rescan of all the node's parent
392 Sequence's if its value changes. */
393 SGPropertyNode* getNodeSGNode(Node& node, bool cache=true)
394 {
395 assert(node._begin[0] == '(');
396 assert(node._child);
397 if (node._rescan) {
398 node._rescan = false;
399 if (!node._sgnode || node._child->_rescan) {
400 const std::string& path = getSequenceStringValue(*node._child);
401 SGPropertyNode* sgnode = NULL;
402 if (path != "") {
403 debug.statsAdd( "propertypath_getNode");
404 sgnode = globals->get_props()->getNode(path, true /*create*/);
405 if (!sgnode) {
406 debug.statsAdd( "propertypath_getNode_failed");
407 SG_LOG(SG_VIEW, SG_DEBUG, ": getNodeSGNode(): getNode() failed, path='" << path << "'");
408 }
409 }
410 if (sgnode != node._sgnode) {
411 if (node._sgnode_listen) {
412 node._sgnode_listen->removeChangeListener(&node);
413 debug.listensRemove(node._sgnode_listen);
414 node._sgnode_listen = NULL;
415 }
416 if (node._sgnode) {
417 node._sgnode->removeChangeListener(&node);
418 }
419 node._sgnode = sgnode;
420 if (node._sgnode && cache) {
421 node._sgnode_listen = node._sgnode;
422 node._sgnode->addChangeListener(&node, false /*initial*/);
423 debug.listensAdd(node._sgnode);
424 }
425 }
426 if (!node._sgnode && path != "") {
427 /* Ideally we'd ask Simgear's property system to call us
428 back if <path> was created, but this is non-trivial, and
429 not actually required when we are used by view.cxx. */
430 }
431 }
432 }
433 return node._sgnode;
434 }
435
436 const std::string& getNodeStringValue(Node& node)
437 {
438 if (node._rescan) {
439 if (node._begin[0] == '(') {
440 getNodeSGNode(node);
441 if (node._sgnode) {
442 debug.statsAdd( "property_getStringValue");
443 node._value = node._sgnode->getStringValue();
444 }
445 else {
446 node._value = "";
447 }
448 }
449 else {
450 node._rescan = false;
451 node._value = std::string(node._begin, node._end);
452 }
453 }
454
455 return node._value;
456 }
457
458 const std::string& getSequenceStringValue(Sequence& sequence)
459 {
460 if (sequence._rescan) {
461 sequence._rescan = false;
462 sequence._value = "";
463 for (auto node: sequence._nodes) {
464 sequence._value += getNodeStringValue(*node);
465 }
466 }
467 return sequence._value;
468 }
469
470 /* Assumes that sequence has a single child Node object with a non-empty
471 Sequence child object whose value is a path in the property system. */
472 SGPropertyNode* getSequenceNode(Sequence& sequence)
473 {
474 assert(sequence._nodes.size() == 1);
475 Node& node = *sequence._nodes.front();
476 return getNodeSGNode(node, false /*cache*/);
477 }
478
479 /* This is different from getSequenceStringValue() in that it assumes that
480 a spec has a single top-level (...) and so the top-level sequence has a
481 single child Node object which in turn has a Sequence child object whose
482 value is a path in the property system.
483
484 We always call the SGPropertyNode's getDoubleValue() method - we don't
485 cache the double value. But the underlying SGPropertyNode* will be cached.
486 */
487 double getSequenceDoubleValue(Sequence& sequence, double default_)
488 {
489 SGPropertyNode* node = getSequenceNode(sequence);
490 if (!node) {
491 return default_;
492 }
493 if (!node->getParent()) {
494 /* Root node. */
495 return default_;
496 }
497 if (node->getType() == simgear::props::BOOL) {
498 /* 2020-03-22: there is a problem with aircraft rah-66 setting type
499 of the root node to bool which gives string value "false". So we
500 force a return of default_. */
501 return default_;
502 }
503 if (node->getStringValue()[0] == 0) {
504 /* If we reach here, the node exists but its value is an empty
505 string, so node->getDoubleValue() would return 0 which isn't
506 useful, so instead we return default_. */
507 return default_;
508 }
509 return node->getDoubleValue();
510 }
511
512 bool getSequenceBoolValue(Sequence& sequence, bool default_)
513 {
514 SGPropertyNode* node = getSequenceNode(sequence);
515 if (node) {
516 if (node->getStringValue()[0] != 0) {
517 return node->getBoolValue();
518 }
519 }
520 return default_;
521 }
522
523 const std::string& getStringValue(const char* spec)
524 {
525 std::shared_ptr<Sequence> sequence = getSequence(spec);
526 return getSequenceStringValue(*sequence);
527 }
528
529 double getDoubleValue(const char* spec, double default_)
530 {
531 std::shared_ptr<Sequence> sequence = getSequence(spec);
532 if (sequence->_nodes.size() != 1 || sequence->_nodes.front()->_begin[0] != '(') {
533 SG_LOG(SG_VIEW, SG_DEBUG, "bad sequence for getDoubleValue() - must have outermost '(...)': '" << spec);
534 abort();
535 }
536 double ret = getSequenceDoubleValue(*sequence, default_);
537 return ret;
538 }
539
540 bool getBoolValue(const char* spec, bool default_)
541 {
542 std::shared_ptr<Sequence> sequence = getSequence(spec);
543 if (sequence->_nodes.size() != 1 || sequence->_nodes.front()->_begin[0] != '(') {
544 SG_LOG(SG_VIEW, SG_DEBUG, "bad sequence for getBoolValue() - must have outermost '(...)': '" << spec);
545 abort();
546 }
547 bool ret = getSequenceBoolValue(*sequence, default_);
548 return ret;
549 }
550
551 std::ostream& operator << (std::ostream& out, const Dump& dump)
552 {
553 out << "ViewPropertyEvaluator\n";
554 out << " Number of specs: " << spec_to_sequence.size() << ":\n";
555 int i = 0;
556 for (auto it: spec_to_sequence) {
557 out << " " << (i+1) << "/" << spec_to_sequence.size() << ": spec: " << it.first << "\n";
558 out << SequenceDump(*it.second, " ", true /*deep*/);
559 i += 1;
560 }
561 out << " Number of sequences: " << string_to_sequence.size() << "\n";
562 i = 0;
563 for (auto it: string_to_sequence) {
564 out << " " << (i+1) << "/" << string_to_sequence.size()
565 << ": spec='" << it.first << "'"
566 << ": " << SequenceDump(*it.second, "", false /*deep*/)
567 ;
568 i += 1;
569 }
570 out << " Number of nodes: " << string_to_node.size() << "\n";
571 i = 0;
572 for (auto it: string_to_node) {
573 out << " " << (i+1) << "/" << string_to_node.size()
574 << ": spec='" << it.first << "'"
575 << ": " << NodeDump(*it.second, "", false /*deep*/)
576 ;
577 i += 1;
578 }
579 out << " Number of listens: " << debug.listens.size() << "\n";
580 i = 0;
581 for (auto it: debug.listens) {
582 out << " " << (i+1) << "/" << debug.listens.size()
583 << ": " << it->getPath()
584 << "\n";
585 i += 1;
586 }
587 return out;
588 }
589
590 DumpOne::DumpOne(const char* spec)
591 : _spec(spec)
592 {}
593
594 std::ostream& operator << (std::ostream& out, const DumpOne& dumpone)
595 {
596 out << "ViewPropertyEvaluator\n";
597 std::shared_ptr<Sequence> sequence = getSequence(dumpone._spec);
598 if (sequence) {
599 out << " " << ": spec: '" << dumpone._spec << "'\n";
600 out << SequenceDump(*sequence, " ", true /*deep*/);
601 }
602 return out;
603 }
604
605 void clear()
606 {
607 spec_to_sequence.clear();
608 string_to_sequence.clear();
609 string_to_node.clear();
610
611 debug = Debug();
612 }
613
614 /* === Everything below here is for diagnostics and/or debugging. */
615
616 SequenceDump::SequenceDump(const Sequence& sequence, const std::string& indent, bool deep)
617 :
618 _sequence(sequence),
619 _indent(indent),
620 _deep(deep)
621 {}
622
623 NodeDump::NodeDump(const Node& node, const std::string& indent, bool deep)
624 :
625 _node(node),
626 _indent(indent),
627 _deep(deep)
628 {}
629
630
631 void Debug::listensAdd(SGPropertyNode_ptr node)
632 {
633 debug.listens.push_back(node);
634 }
635
636 void Debug::listensRemove(SGPropertyNode_ptr node)
637 {
638 auto it = std::find(debug.listens.begin(), debug.listens.end(), node);
639 if (it == debug.listens.end()) {
640 SG_LOG(SG_VIEW, SG_ALERT, "Unable to find node in debug.listens");
641 }
642 else {
643 debug.listens.erase(it);
644 }
645 }
646
647 std::ostream& operator << (std::ostream& out, const Debug::StatsShow&)
648 {
649 time_t t = time(NULL);
650 time_t dt = t - debug.statsT0;
651 if (dt == 0) dt = 1;
652 out << "ViewPropertyEvaluator stats: dt=" << dt << "\n";
653 for (auto it: debug.stats) {
654 const std::string& name = it.first;
655 int n = it.second->n;
656 out << " : n=" << n << " n/sec=" << (1.0 * n / dt) << ": " << name << "\n";
657 }
658 return out;
659 }
660
662 for (auto it: debug.stats) {
663 it.second->n = 0;
664 }
665 debug.statsT0 = time(NULL);
666 }
667
668 void Debug::statsAdd(const char* name) {
669 if (debug.statsT0 == 0) debug.statsT0 = time(NULL);
670 std::shared_ptr<Debug::Stat>& stat = debug.stats[name];
671 if (!stat) {
672 stat.reset(new(Debug::Stat));
673 }
674 stat->n += 1;
675
676 if (1) {
677 static time_t t0 = time(NULL);
678 time_t t = time(NULL);
679 if (t - t0 > 10) {
680 t0 = t;
681 SG_LOG(SG_VIEW, SG_DEBUG, StatsShow());
682 statsReset();
683
684 /* Output all specs with SG_BULK. */
685 SG_LOG(SG_VIEW, SG_BULK, Dump());
686 }
687 }
688 }
689
690 std::ostream& operator << (std::ostream& out, const SequenceDump& self)
691 {
692 std::string spec;
693 for (auto node: self._sequence._nodes) {
694 spec += std::string(node->_begin, node->_end);
695 }
696
697 out << self._indent
698 << "Sequence at " << &self._sequence << ":"
699 << " _rescan=" << self._sequence._rescan
700 << " _parents.size()=" << self._sequence._parents.size()
701 << " _nodes.size()=" << self._sequence._nodes.size()
702 << " _value='" << self._sequence._value << "'"
703 << " spec='" << spec << "'"
704 << "\n";
705 if (self._deep) {
706 for (auto node: self._sequence._nodes) {
707 out << NodeDump(*node, self._indent + " ", self._deep);
708 }
709 }
710 return out;
711 }
712
713 std::ostream& operator << (std::ostream& out, const NodeDump& self)
714 {
715 out << self._indent
716 << "Node at " << &self._node << ":"
717 << " _rescan=" << self._node._rescan
718 << " _parents.size()=" << self._node._parents.size()
719 << " _child=" << self._node._child.get()
720 << " _value='" << self._node._value << "'"
721 << " _sgnode=" << self._node._sgnode
722 ;
723 if (self._node._sgnode) {
724 out
725 << " (path=" << self._node._sgnode->getPath()
726 << ", value=" << self._node._sgnode->getStringValue()
727 << ")";
728 }
729 out
730 << " _begin.._end='" << std::string(self._node._begin, self._node._end) << "'"
731 << "\n";
732 if (self._deep) {
733 if (self._node._child) {
734 out << SequenceDump(*self._node._child, self._indent + " ", self._deep);
735 }
736 }
737 return out;
738 }
739
740 void dump()
741 {
742 std::cerr << Dump() << "\n";
743 }
744}
#define i(x)
const char * name
FGGlobals * globals
Definition globals.cxx:142
const char * getSequenceEnd(const char *spec)
void rescanSequence(Sequence &sequence)
std::shared_ptr< Sequence > getSequenceInternal(const char *spec, Node *parent)
std::map< std::string, std::shared_ptr< Node > > string_to_node
const std::string & getSequenceStringValue(Sequence &sequence)
std::map< const char *, std::shared_ptr< Sequence > > spec_to_sequence
double getSequenceDoubleValue(Sequence &sequence, double default_=0)
const std::string & getNodeStringValue(Node &node)
const char * getNodeEnd(const char *spec)
const std::string & getStringValue(const char *spec)
bool getSequenceBoolValue(Sequence &sequence, bool default_)
std::shared_ptr< Node > getNodeInternal(const char *spec, Sequence *parent)
std::shared_ptr< Sequence > getSequence(const char *spec)
std::map< std::string, std::shared_ptr< Sequence > > string_to_sequence
double getDoubleValue(const char *spec, double default_)
std::ostream & operator<<(std::ostream &out, const Dump &dump)
SGPropertyNode * getSequenceNode(Sequence &sequence)
SGPropertyNode * getNodeSGNode(Node &node, bool cache=true)
bool getBoolValue(const char *spec, bool default_)
std::vector< SGPropertyNode_ptr > listens
void listensAdd(SGPropertyNode_ptr node)
void listensRemove(SGPropertyNode_ptr node)
std::map< std::string, std::shared_ptr< Stat > > stats
friend std::ostream & operator<<(std::ostream &out, const StatsShow &)
NodeDump(const Node &node, const std::string &indent="", bool deep=false)
friend std::ostream & operator<<(std::ostream &out, const NodeDump &self)
std::vector< Sequence * > _parents
void valueChanged(SGPropertyNode *node)
std::shared_ptr< Sequence > _child
friend std::ostream & operator<<(std::ostream &out, const SequenceDump &self)
SequenceDump(const Sequence &sequence, const std::string &indent="", bool deep=false)
std::vector< std::shared_ptr< Node > > _nodes