simgear/props/props_test.cxx: test for self-unregister. SGPropertyNode::removeChangeListener() used to modify the _listeners vector or even delete it and set _listeners to NULL, if a listener was removed. This could cause a SEGV if removeChangeListener() was called from a listener, because listeners are called from methods such as SGPropertyNode::fireValueChanged() which iterate through _listeners and don't expect it to be changed to NULL. The fix here is to keep a track of whether we are iterating through the _listeners vector; if we are, SGPropertyNode::removeChangeListener() sets the entry to NULL, instead of removing it. We have a new internal function forEachListener() which takes care of the iterating details - incrementing/decrementing the new _listeners->_num_iterators count and skipping NULL entries. Before returning, if _num_iterators is now zero it removes any NULL entries in the vector of listeners. The nListeners() member is no longer inline, because it needs to ignore NULL entries in the vector. And _listeners now points to an internally-defined struct which contains a new _num_iterators as well as the vector of listeners. Added self-unregister test to simgear/props/props_test.cxx.
988 lines
31 KiB
C++
988 lines
31 KiB
C++
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Test harness.
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <simgear_config.h>
|
|
#endif
|
|
|
|
#include <simgear/compiler.h>
|
|
|
|
#include <algorithm>
|
|
#include <memory> // std::unique_ptr
|
|
#include <iostream>
|
|
#include <map>
|
|
|
|
#include "props.hxx"
|
|
#include "props_io.hxx"
|
|
|
|
#include <simgear/misc/test_macros.hxx>
|
|
#include <simgear/misc/sg_path.hxx>
|
|
#include <simgear/misc/test_macros.hxx>
|
|
|
|
using std::cout;
|
|
using std::cerr;
|
|
using std::endl;
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Sample object.
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
class Stuff {
|
|
public:
|
|
Stuff () : _stuff(199.0) {}
|
|
virtual float getStuff () const { return _stuff; }
|
|
virtual void setStuff (float stuff) { _stuff = stuff; }
|
|
virtual float getStuff (int index) const { return _stuff * index; }
|
|
virtual void setStuff (int index, float val) {
|
|
_stuff = val / index;
|
|
}
|
|
private:
|
|
float _stuff;
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Sample function.
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
static int get100 () { return 100; }
|
|
|
|
static double getNum (int index) { return 1.0 / index; }
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Show a value.
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
static void
|
|
show_values (const SGPropertyNode * node)
|
|
{
|
|
cout << "Bool: " << (node->getBoolValue() ? "true" : "false") << endl;
|
|
cout << "Int: " << node->getIntValue() << endl;
|
|
cout << "Float: " << node->getFloatValue() << endl;
|
|
cout << "Double: " << node->getDoubleValue() << endl;
|
|
cout << "String: " << node->getStringValue() << endl;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Test individual values.
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
static void
|
|
test_value ()
|
|
{
|
|
SGPropertyNode * node;
|
|
|
|
cout << endl << "Value" << endl << endl;
|
|
|
|
//
|
|
// Test coercion for getters.
|
|
//
|
|
|
|
cout << "Testing coercion from bool (expect true)" << endl;
|
|
node = new SGPropertyNode;
|
|
node->setBoolValue(true);
|
|
show_values(node);
|
|
delete node;
|
|
cout << endl;
|
|
|
|
cout << "Testing coercion from int (expect 128)" << endl;
|
|
node = new SGPropertyNode;
|
|
node->setIntValue(128);
|
|
show_values(node);
|
|
delete node;
|
|
cout << endl;
|
|
|
|
cout << "Testing coercion from float (expect 1.0/3.0)" << endl;
|
|
node = new SGPropertyNode;
|
|
node->setFloatValue(1.0/3.0);
|
|
show_values(node);
|
|
delete node;
|
|
cout << endl;
|
|
|
|
cout << "Testing coercion from double (expect 1.0/3.0)" << endl;
|
|
node = new SGPropertyNode;
|
|
node->setDoubleValue(1.0/3.0);
|
|
show_values(node);
|
|
delete node;
|
|
cout << endl;
|
|
|
|
cout << "Testing coercion from string (expect 10e4)" << endl;
|
|
node = new SGPropertyNode;
|
|
node->setStringValue("10e4");
|
|
show_values(node);
|
|
delete node;
|
|
cout << endl;
|
|
|
|
cout << "Testing coercion from unspecified (expect -10e-4)" << endl;
|
|
node = new SGPropertyNode;
|
|
node->setUnspecifiedValue("-10e-4");
|
|
show_values(node);
|
|
delete node;
|
|
cout << endl;
|
|
|
|
//
|
|
// Test coercion for setters.
|
|
//
|
|
|
|
node = new SGPropertyNode;
|
|
|
|
cout << "Testing coercion to bool from bool (expect false)" << endl;
|
|
node->setBoolValue(false);
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Testing coercion to bool from int (expect 1)" << endl;
|
|
node->setIntValue(1);
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Testing coercion to bool from float (expect 1.1)" << endl;
|
|
node->setFloatValue(1.1);
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Testing coercion to bool from double (expect 1.1)" << endl;
|
|
node->setDoubleValue(1.1);
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Testing coercion to bool from string (expect 1e10)" << endl;
|
|
node->setStringValue("1e10");
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Testing coercion to bool from unspecified (expect 1e10)" << endl;
|
|
node->setUnspecifiedValue("1e10");
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
// Test tying to a pointer.
|
|
|
|
static int myValue = 10;
|
|
|
|
cout << "Testing tying to a pointer (expect 10)" << endl;
|
|
if (!node->tie(SGRawValuePointer<int>(&myValue), false))
|
|
cout << "*** FAILED TO TIE VALUE!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Changing base variable (expect -5)" << endl;
|
|
myValue = -5;
|
|
show_values(node);
|
|
if (!node->untie())
|
|
cout << "*** FAILED TO UNTIE VALUE!!!" << endl;
|
|
cout << endl;
|
|
|
|
|
|
// Test tying to static functions.
|
|
|
|
cout << "Create a new int value (expect 10)" << endl;
|
|
node->setIntValue(10);
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Testing tying to static getter (expect 100)" << endl;
|
|
if (!node->tie(SGRawValueFunctions<int>(get100)))
|
|
cout << "*** FAILED TO TIE VALUE!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Try changing value with no setter (expect 100)" << endl;
|
|
if (node->setIntValue(200))
|
|
cout << "*** setIntValue did not return false!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Untie value (expect 100)" << endl;
|
|
if (!node->untie())
|
|
cout << "*** FAILED TO UNTIE VALUE!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Try changing value (expect 200)" << endl;
|
|
if (!node->setIntValue(200))
|
|
cout << "*** setIntValue RETURNED FALSE!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
// Test tying to indexed static functions.
|
|
|
|
cout << "Create a new int value (expect 10)" << endl;
|
|
node->setIntValue(10);
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Testing tying to indexed static getter (0.3333...)" << endl;
|
|
if (!node->tie(SGRawValueFunctionsIndexed<double>(3, getNum)))
|
|
cout << "*** FAILED TO TIE VALUE!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Untie value (expect 0.3333...)" << endl;
|
|
if (!node->untie())
|
|
cout << "*** FAILED TO UNTIE VALUE!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
|
|
// Test methods.
|
|
|
|
cout << "Try tying to an object without defaults (expect 199)" << endl;
|
|
Stuff stuff;
|
|
SGRawValueMethods<class Stuff,float> tiedstuff(stuff,
|
|
&Stuff::getStuff,
|
|
&Stuff::setStuff);
|
|
if (!node->tie(tiedstuff, false))
|
|
cout << "*** FAILED TO TIE VALUE!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Try untying from object (expect 199)" << endl;
|
|
if (!node->untie())
|
|
cout << "*** FAILED TO UNTIE VALUE!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Try tying to an indexed method (expect 199)" << endl;
|
|
if (!node->tie(SGRawValueMethodsIndexed<class Stuff, float>
|
|
(stuff, 2, &Stuff::getStuff, &Stuff::setStuff)))
|
|
cout << "*** FAILED TO TIE VALUE!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
node->untie();
|
|
|
|
cout << "Change value (expect 50)" << endl;
|
|
if (!node->setIntValue(50))
|
|
cout << "*** FAILED TO SET VALUE!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
cout << "Try tying to an object with defaults (expect 50)" << endl;
|
|
if (!node->tie(tiedstuff, true))
|
|
cout << "*** FAILED TO TIE VALUE!!!" << endl;
|
|
show_values(node);
|
|
cout << endl;
|
|
|
|
delete node;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Check property nodes.
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
static void
|
|
dump_node (const SGPropertyNode * node)
|
|
{
|
|
writeProperties(cout, node, true);
|
|
}
|
|
|
|
static void
|
|
test_property_nodes ()
|
|
{
|
|
SGPropertyNode root;
|
|
cout << "Created root node " << root.getPath() << endl;
|
|
|
|
SGPropertyNode * child = root.getChild("foo", 0, true);
|
|
|
|
SGPropertyNode *grandchild = child->getChild("bar", 0, true);
|
|
grandchild->setDoubleValue(100);
|
|
grandchild = child->getChild("bar", 1, true);
|
|
grandchild->setDoubleValue(200);
|
|
grandchild = child->getChild("bar", 2, true);
|
|
grandchild->setDoubleValue(300);
|
|
grandchild = child->getChild("bar", 3, true);
|
|
grandchild->setDoubleValue(400);
|
|
|
|
SG_CHECK_EQUAL(grandchild->getPosition(), 3);
|
|
|
|
child = root.getChild("hack", 0, true);
|
|
|
|
SG_CHECK_EQUAL(child->getPosition(), 1);
|
|
|
|
|
|
grandchild = child->getChild("bar", 0, true);
|
|
grandchild->setDoubleValue(100);
|
|
grandchild = child->getChild("bar", 3, true);
|
|
grandchild->setDoubleValue(200);
|
|
|
|
SG_CHECK_EQUAL(grandchild->getPosition(), 1);
|
|
|
|
|
|
grandchild = child->getChild("bar", 1, true);
|
|
grandchild->setDoubleValue(300);
|
|
grandchild = child->getChild("bar", 2, true);
|
|
grandchild->setDoubleValue(400);
|
|
|
|
SG_CHECK_EQUAL(grandchild->getPosition(), 3);
|
|
|
|
|
|
dump_node(&root);
|
|
|
|
SG_CHECK_EQUAL(child->getPosition(), 1);
|
|
SG_CHECK_EQUAL(grandchild->getPosition(), 3);
|
|
|
|
cout << "Trying path (expect /foo[0]/bar[0])" << endl;
|
|
grandchild = root.getNode("/hack/../foo/./bar[0]");
|
|
cout << "Path is " << grandchild->getPath() << endl;
|
|
cout << endl;
|
|
|
|
cout << "Looking for all /hack[0]/bar children" << endl;
|
|
std::vector<SGPropertyNode_ptr> bar = child->getChildren("bar");
|
|
cout << "There are " << bar.size() << " matches" << endl;
|
|
for (int i = 0; i < (int)bar.size(); i++)
|
|
cout << bar[i]->getName() << '[' << bar[i]->getIndex() << ']' << endl;
|
|
cout << endl;
|
|
|
|
cout << "Testing addition of a totally empty node" << endl;
|
|
if (root.getNode("/a/b/c", true) == 0)
|
|
cerr << "** FAILED to create /a/b/c" << endl;
|
|
dump_node(&root);
|
|
cout << endl;
|
|
|
|
|
|
}
|
|
|
|
void test_addChild()
|
|
{
|
|
SGPropertyNode root;
|
|
|
|
cout << "Testing the addChild function " << endl;
|
|
cout << "Created root node " << root.getPath() << endl;
|
|
|
|
SGPropertyNode *test = root.getChild("test", 0, true);
|
|
SGPropertyNode *n = test->getNode("foo", true);
|
|
|
|
cout << "Testing appending initial child node" << endl;
|
|
test = root.addChild("child", 0, true);
|
|
if (test == 0)
|
|
cerr << "** FAILED to append initial child node" << endl;
|
|
if (root.getNode("child", 0, false) != test)
|
|
cerr << "** FAILED to create initial child node at expected index #0" << endl;
|
|
|
|
n->getChild("child", 1, true)->setIntValue(1);
|
|
n->getChild("child", 2, true)->setIntValue(2);
|
|
n->getChild("child", 4, true)->setIntValue(4);
|
|
dump_node(&root);
|
|
|
|
SGPropertyNode *ch = n->addChild("child");
|
|
ch->setIntValue(5);
|
|
if (n->getChild("child", 5, false) != ch)
|
|
cerr << "** FAILED to create child node at expected index #5" << endl;
|
|
cerr << endl << "ADDED: " << ch->getPath() << endl << endl;
|
|
|
|
cout << "Testing appending child node at first empty index (Skipping 0)" << endl;
|
|
ch = n->addChild("child", 1, false);
|
|
ch->setIntValue(3);
|
|
if (n->getChild("child", 3, false) != ch)
|
|
cerr << "** FAILED to create child node at expected index #3" << endl;
|
|
|
|
cout << "Testing appending child node at first empty index" << endl;
|
|
ch = n->addChild("child", 0, false);
|
|
ch->setIntValue(0);
|
|
if (n->getChild("child", 0, false) != ch)
|
|
cerr << "** FAILED to create child node at expected index #0" << endl;
|
|
|
|
cout << "Testing appending child node" << endl;
|
|
ch = n->addChild("first", 0, false);
|
|
if (ch == 0)
|
|
cerr << "** Failed to add child node" << endl;
|
|
else
|
|
ch->setStringValue("append first");
|
|
if (n->getChild("first", 0, false) != ch)
|
|
cerr << "** FAILED to append child node with expected index #0" << endl;
|
|
cerr << "ADDED: " << ch->getPath() << endl;
|
|
if (root.getNode("test/foo/first", false) != ch)
|
|
cerr << "** FAILED to append child node at expected path 'test/foo/first'" << endl;
|
|
|
|
dump_node(&root);
|
|
}
|
|
|
|
|
|
bool ensureNListeners(SGPropertyNode* node, int n)
|
|
{
|
|
if (node->nListeners() != n) {
|
|
return false;
|
|
}
|
|
|
|
for (int c=0; c < node->nChildren(); ++c) {
|
|
if (!ensureNListeners(node->getChild(c), n)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void defineSamplePropertyTree(SGPropertyNode_ptr root)
|
|
{
|
|
root->setIntValue("position/body/a", 42);
|
|
root->setIntValue("position/body/c", 1);
|
|
root->setIntValue("position/body/d", 600);
|
|
|
|
root->setDoubleValue("velocity/body/x", 1.23);
|
|
root->setDoubleValue("velocity/body/y", 4.56);
|
|
root->setDoubleValue("velocity/body/z", 7.89);
|
|
|
|
root->setStringValue("settings/render/foo", "flightgear");
|
|
root->setStringValue("version", "10.3.19");
|
|
|
|
for (int i=0; i<4; ++i) {
|
|
root->setDoubleValue("controls/engine[" + std::to_string(i) + "]/throttle", 0.1 * i);
|
|
root->setBoolValue("controls/engine[" + std::to_string(i) + "]/starter", i % 2);
|
|
root->setDoubleValue("engine[" + std::to_string(i) + "]/rpm", 1000 * i);
|
|
root->setDoubleValue("engine[" + std::to_string(i) + "]/temp", 100 * i);
|
|
}
|
|
|
|
root->setDoubleValue("controls/doors/door[2]/position-norm", 0.5);
|
|
}
|
|
|
|
class TestListener : public SGPropertyChangeListener
|
|
{
|
|
public:
|
|
TestListener(SGPropertyNode* root, bool recursive = false, bool self_unregister = false) :
|
|
_root(root), _self_unregister(self_unregister) {}
|
|
|
|
virtual void valueChanged(SGPropertyNode* node) override
|
|
{
|
|
std::cout << "saw value changed for:" << node->getPath() << std::endl;
|
|
valueChangeCount[node]++;
|
|
if (_self_unregister) {
|
|
std::cout << "valueChanged(): calling removeChangeListener() to self-remove\n";
|
|
node->removeChangeListener(this);
|
|
}
|
|
}
|
|
|
|
int checkValueChangeCount(const std::string& path) const
|
|
{
|
|
auto it = valueChangeCount.find(_root->getNode(path));
|
|
if (it == valueChangeCount.end()) {
|
|
return 0;
|
|
}
|
|
|
|
return it->second;
|
|
}
|
|
|
|
struct ParentChange
|
|
{
|
|
SGPropertyNode_ptr parent, child;
|
|
};
|
|
|
|
virtual void childAdded(SGPropertyNode * parent, SGPropertyNode * child) override
|
|
{
|
|
adds.push_back({parent, child});
|
|
}
|
|
|
|
virtual void childRemoved(SGPropertyNode * parent, SGPropertyNode * child) override
|
|
{
|
|
removes.push_back({parent, child});
|
|
}
|
|
|
|
std::vector<SGPropertyNode*> addedTo(SGPropertyNode* pr)
|
|
{
|
|
std::vector<SGPropertyNode*> result;
|
|
for (auto c : adds) {
|
|
if (c.parent == pr) {
|
|
result.push_back(c.child);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<SGPropertyNode*> removedFrom(SGPropertyNode* pr)
|
|
{
|
|
std::vector<SGPropertyNode*> result;
|
|
for (auto c : removes) {
|
|
if (c.parent == pr) {
|
|
result.push_back(c.child);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool checkAdded(const std::string& pr, SGPropertyNode* n)
|
|
{
|
|
auto adds = addedTo(_root->getNode(pr));
|
|
return std::find(adds.begin(), adds.end(), n) != adds.end();
|
|
}
|
|
|
|
bool checkRemoved(const std::string& pr, SGPropertyNode* n)
|
|
{
|
|
auto removes = removedFrom(_root->getNode(pr));
|
|
return std::find(removes.begin(), removes.end(), n) != removes.end();
|
|
}
|
|
|
|
bool checkRemoved(SGPropertyNode* pr, SGPropertyNode* n)
|
|
{
|
|
auto removes = removedFrom(pr);
|
|
return std::find(removes.begin(), removes.end(), n) != removes.end();
|
|
}
|
|
|
|
void resetChangeCounts()
|
|
{
|
|
valueChangeCount.clear();
|
|
adds.clear();
|
|
removes.clear();
|
|
}
|
|
private:
|
|
SGPropertyNode* _root;
|
|
bool _self_unregister;
|
|
std::map<SGPropertyNode*, unsigned int> valueChangeCount;
|
|
|
|
std::vector<ParentChange> adds;
|
|
std::vector<ParentChange> removes;
|
|
|
|
};
|
|
|
|
void testListener()
|
|
{
|
|
SGPropertyNode_ptr tree(new SGPropertyNode);
|
|
defineSamplePropertyTree(tree);
|
|
|
|
// basic listen
|
|
{
|
|
TestListener l(tree.get());
|
|
tree->getNode("position/body/c")->addChangeListener(&l);
|
|
tree->getNode("velocity/body/z")->addChangeListener(&l);
|
|
tree->getNode("controls/engine[2]/starter")->addChangeListener(&l);
|
|
|
|
tree->setBoolValue("controls/engine[2]/starter", true);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("controls/engine[2]/starter"), 1);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/c"), 0);
|
|
|
|
tree->setIntValue("position/body/c", 123);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("controls/engine[2]/starter"), 1);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/c"), 1);
|
|
|
|
// verify that changing non-listened props doesn't affect anything
|
|
tree->setIntValue("position/body/a", 19);
|
|
tree->setBoolValue("controls/engine[1]/starter", true);
|
|
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("controls/engine[2]/starter"), 1);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/c"), 1);
|
|
|
|
}
|
|
|
|
// initial value set
|
|
{
|
|
TestListener l(tree.get());
|
|
tree->getNode("velocity/body/y")->addChangeListener(&l, true);
|
|
tree->getNode("controls/engine[2]/starter")->addChangeListener(&l, true);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("controls/engine[2]/starter"), 1);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("velocity/body/y"), 1);
|
|
}
|
|
|
|
// delete listener while listening, should be fine
|
|
{
|
|
std::unique_ptr<TestListener> l( new TestListener(tree.get()));
|
|
tree->getNode("position/body/c")->addChangeListener(l.get());
|
|
tree->getNode("velocity/body/z")->addChangeListener(l.get());
|
|
tree->getNode("controls/engine[2]/starter")->addChangeListener(l.get());
|
|
|
|
SG_CHECK_EQUAL(tree->getNode("position/body/c")->nListeners(), 1);
|
|
SG_CHECK_EQUAL(
|
|
tree->getNode("controls/engine[2]/starter")->nListeners(), 1);
|
|
|
|
l.reset();
|
|
|
|
SG_CHECK_EQUAL(tree->getNode("position/body/c")->nListeners(), 0);
|
|
SG_CHECK_EQUAL(
|
|
tree->getNode("controls/engine[2]/starter")->nListeners(), 0);
|
|
|
|
tree->getNode("position/body/c")->setIntValue(49.0);
|
|
tree->getNode("controls/engine[2]/starter")->setBoolValue(true);
|
|
}
|
|
|
|
|
|
// add/remove of child listen
|
|
{
|
|
TestListener l(tree.get());
|
|
tree->getNode("controls")->addChangeListener(&l);
|
|
tree->getNode("velocity/body")->addChangeListener(&l);
|
|
|
|
tree->setDoubleValue("controls/pitch", 0.5);
|
|
SG_VERIFY(l.checkAdded("controls", tree->getNode("controls/pitch")));
|
|
|
|
tree->setIntValue("controls/yaw", 12);
|
|
|
|
SG_VERIFY(l.checkAdded("controls", tree->getNode("controls/yaw")));
|
|
|
|
// branch as well as leaf nodes should work the same
|
|
tree->setBoolValue("controls/gears/gear[1]/locked", true);
|
|
SG_VERIFY(l.checkAdded("controls", tree->getNode("controls/gears")));
|
|
|
|
SGPropertyNode_ptr rm = tree->getNode("velocity/body/z");
|
|
tree->getNode("velocity/body")->removeChild("z");
|
|
SG_VERIFY(l.checkRemoved("velocity/body", rm.get()));
|
|
|
|
SG_CHECK_EQUAL(
|
|
l.checkRemoved("velocity/body", tree->getNode("velocity/body/x")),
|
|
false);
|
|
}
|
|
|
|
tree = new SGPropertyNode;
|
|
defineSamplePropertyTree(tree);
|
|
|
|
// ensure basic listenter is not recursive
|
|
// disabled for now, since all listeners are recurisve
|
|
#if 0
|
|
{
|
|
TestListener l(tree.get(), false /* not recursive */);
|
|
tree->getNode("position/body")->addChangeListener(&l);
|
|
tree->getNode("controls/")->addChangeListener(&l);
|
|
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 0);
|
|
|
|
tree->setIntValue("position/body/a", 111);
|
|
tree->setIntValue("position/body/z", 43);
|
|
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 0);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/z"), 0);
|
|
|
|
tree->getNode("position/body/new", true);
|
|
SG_VERIFY(l.checkAdded("position/body",
|
|
tree->getNode("position/body/new")));
|
|
}
|
|
#endif
|
|
|
|
tree = new SGPropertyNode;
|
|
defineSamplePropertyTree(tree);
|
|
// recursive listen
|
|
{
|
|
TestListener l(tree.get(), true /* recursive */);
|
|
tree->getNode("position/body")->addChangeListener(&l);
|
|
tree->getNode("controls/")->addChangeListener(&l);
|
|
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 0);
|
|
|
|
tree->setIntValue("position/body/a", 111);
|
|
tree->setIntValue("position/body/z", 43);
|
|
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/z"), 1);
|
|
|
|
SG_VERIFY(l.checkAdded("position/body",
|
|
tree->getNode("position/body/z")));
|
|
|
|
|
|
tree->setBoolValue("controls/engine[3]/starter", true);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("controls/engine[3]/starter"), 1);
|
|
|
|
tree->setBoolValue("controls/engines[1]/fuel-cutoff", true);
|
|
SG_VERIFY(
|
|
l.checkAdded("controls/engines[1]",
|
|
tree->getNode("controls/engines[1]/fuel-cutoff")));
|
|
|
|
SG_CHECK_EQUAL(
|
|
l.checkValueChangeCount("controls/engines[1]/fuel-cutoff"), 1);
|
|
|
|
tree->setDoubleValue("controls/doors/door[2]/position-norm", 0.5);
|
|
SG_CHECK_EQUAL(
|
|
l.checkValueChangeCount("controls/doors/door[2]/position-norm"), 1);
|
|
|
|
SGPropertyNode_ptr door2Node = tree->getNode("controls/doors/door[2]");
|
|
SGPropertyNode_ptr door2PosNode = tree->getNode("controls/doors/door[2]/position-norm");
|
|
|
|
tree->getNode("controls/doors")->removeChild(door2Node);
|
|
|
|
SG_VERIFY(l.checkRemoved("controls/doors", door2Node));
|
|
|
|
// default is not recurse for children
|
|
SG_CHECK_EQUAL(l.checkRemoved(door2Node, door2PosNode), false);
|
|
|
|
// adds *are* seen recursively
|
|
tree->setStringValue("controls/lights/light[3]/foo/state", "dim");
|
|
SG_VERIFY(
|
|
l.checkAdded("controls/lights/light[3]/foo",
|
|
tree->getNode("controls/lights/light[3]/foo/state")));
|
|
|
|
|
|
// remove a listener
|
|
tree->getNode("controls")->removeChangeListener(&l);
|
|
|
|
// removing listener does not trigger remove notifications
|
|
SG_VERIFY(!l.checkRemoved("controls",
|
|
tree->getNode("controls/engine[3]")));
|
|
SG_VERIFY(!l.checkRemoved("controls/engine[3]",
|
|
tree->getNode("controls/engine[3]/starter")));
|
|
|
|
tree->setBoolValue("controls/engines[1]/fuel-cutoff", false);
|
|
tree->setBoolValue("controls/engines[9]/fuel-cutoff", true);
|
|
|
|
// values should be unchanged
|
|
SG_CHECK_EQUAL(
|
|
l.checkValueChangeCount("controls/engines[1]/fuel-cutoff"), 1);
|
|
SG_CHECK_EQUAL(
|
|
l.checkValueChangeCount("controls/engines[9]/fuel-cutoff"), 0);
|
|
|
|
// ensure additional calls to fireChildrenRecursive don't cause multiple adds
|
|
|
|
// disabled for now since we don't add recursive listeners to sub-trees
|
|
// SG_VERIFY(ensureNListeners(tree->getNode("position/body"), 1));
|
|
SG_VERIFY(ensureNListeners(tree->getNode("controls"), 0));
|
|
|
|
tree->getNode("position/body")->fireCreatedRecursive();
|
|
// disabled for now since we don't add recursive listeners to sub-trees
|
|
//SG_VERIFY(ensureNListeners(tree->getNode("position/body"), 1));
|
|
}
|
|
|
|
}
|
|
|
|
void testAliasedListeners()
|
|
{
|
|
SGPropertyNode_ptr tree(new SGPropertyNode);
|
|
defineSamplePropertyTree(tree);
|
|
TestListener l(tree.get());
|
|
|
|
tree->getNode("position/world/x", true)->alias(tree->getNode("position/body/a"));
|
|
tree->getNode("position/earth", true)->alias(tree->getNode("position/body"));
|
|
|
|
tree->getNode("position/world/x")->addChangeListener(&l);
|
|
tree->getNode("position/earth")->addChangeListener(&l);
|
|
|
|
tree->setIntValue("position/body/a", 99);
|
|
SG_CHECK_EQUAL(tree->getIntValue("position/world/x"), 99);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/world/x"), 1);
|
|
|
|
tree->setIntValue("position/world/x", 101);
|
|
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 101);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/world/x"), 2);
|
|
}
|
|
|
|
class TiedPropertyDonor
|
|
{
|
|
|
|
public:
|
|
|
|
int someMember = 100;
|
|
|
|
const char* getWrappedA() const { return wrappedMember.c_str(); }
|
|
void setWrappedA(const char* s) { wrappedMember = std::string(s); }
|
|
|
|
std::string wrappedMember;
|
|
double wrappedB = 1.23;
|
|
|
|
double getWrappedB() const { return wrappedB; }
|
|
};
|
|
|
|
void tiedPropertiesTest()
|
|
{
|
|
TiedPropertyDonor donor;
|
|
|
|
SGPropertyNode_ptr tree(new SGPropertyNode);
|
|
defineSamplePropertyTree(tree);
|
|
|
|
tree->setDoubleValue("position/body/mass", 145.0);
|
|
|
|
tree->tie("position/body/a", SGRawValuePointer<int>(&donor.someMember));
|
|
tree->tie("settings/render/foo", SGRawValueMethods<TiedPropertyDonor, const char*>(donor, &TiedPropertyDonor::getWrappedA, &TiedPropertyDonor::setWrappedA));
|
|
tree->tie("position/body/mass", SGRawValueMethods<TiedPropertyDonor, double>(donor, &TiedPropertyDonor::getWrappedB, nullptr));
|
|
|
|
// tie sets current values of the property onto the setter
|
|
SG_CHECK_EQUAL(tree->getStringValue("settings/render/foo"),
|
|
std::string("flightgear"));
|
|
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 42);
|
|
|
|
// but can't write to this one!
|
|
SG_CHECK_EQUAL(tree->getDoubleValue("position/body/mass"), 1.23);
|
|
|
|
donor.setWrappedA("hello world");
|
|
donor.someMember = 13;
|
|
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 13);
|
|
SG_CHECK_EQUAL(tree->getStringValue("settings/render/foo"),
|
|
std::string("hello world"));
|
|
|
|
donor.someMember = 45;
|
|
donor.wrappedMember = "apples";
|
|
donor.wrappedB = 5000.0;
|
|
|
|
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 45);
|
|
SG_CHECK_EQUAL(tree->getStringValue("settings/render/foo"),
|
|
std::string("apples"));
|
|
SG_CHECK_EQUAL(tree->getDoubleValue("position/body/mass"), 5000.0);
|
|
|
|
// set value externally
|
|
tree->setIntValue("position/body/a", 99);
|
|
SG_CHECK_EQUAL(donor.someMember, 99);
|
|
|
|
tree->setStringValue("settings/render/foo", "lemons");
|
|
SG_CHECK_EQUAL(donor.wrappedMember, "lemons");
|
|
|
|
// set read-only
|
|
bool success = tree->setDoubleValue("position/body/mass", 10000.0);
|
|
SG_VERIFY(!success);
|
|
SG_CHECK_EQUAL(donor.wrappedB, 5000.0); // must not have changed
|
|
|
|
// un-tieing
|
|
|
|
tree->untie("position/body/a");
|
|
tree->untie("position/body/mass");
|
|
|
|
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 99);
|
|
SG_CHECK_EQUAL(tree->getDoubleValue("position/body/mass"), 5000.0);
|
|
}
|
|
|
|
void tiedPropertiesListeners()
|
|
{
|
|
|
|
TiedPropertyDonor donor;
|
|
|
|
SGPropertyNode_ptr tree(new SGPropertyNode);
|
|
defineSamplePropertyTree(tree);
|
|
|
|
tree->tie("position/body/a", SGRawValuePointer<int>(&donor.someMember));
|
|
tree->tie("settings/render/foo", SGRawValueMethods<TiedPropertyDonor, const char*>(donor, &TiedPropertyDonor::getWrappedA, &TiedPropertyDonor::setWrappedA));
|
|
tree->tie("position/body/mass", SGRawValueMethods<TiedPropertyDonor, double>(donor, &TiedPropertyDonor::getWrappedB, nullptr));
|
|
|
|
// tie sets current values of the property onto the setter
|
|
SG_CHECK_EQUAL(tree->getStringValue("settings/render/foo"),
|
|
std::string("flightgear"));
|
|
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 42);
|
|
|
|
TestListener l(tree.get());
|
|
|
|
tree->getNode("position/body/a")->addChangeListener(&l);
|
|
tree->getNode("position/body/mass")->addChangeListener(&l);
|
|
tree->getNode("settings/render/foo")->addChangeListener(&l);
|
|
|
|
// firstly test changes via setXXX API and verify they work
|
|
tree->setIntValue("position/body/a", 99);
|
|
SG_CHECK_EQUAL(donor.someMember, 99);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1);
|
|
|
|
tree->setDoubleValue("position/body/mass", -123.0);
|
|
SG_CHECK_EQUAL(donor.wrappedB, 1.23); // read-only!
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/mass"), 0);
|
|
|
|
tree->setStringValue("settings/render/foo", "thingA");
|
|
tree->setStringValue("settings/render/foo", "thingB");
|
|
SG_CHECK_EQUAL(donor.wrappedMember, std::string("thingB"));
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("settings/render/foo"), 2);
|
|
|
|
// now change values from inside the donor and verify it doesn't fire
|
|
// the listener
|
|
|
|
donor.wrappedMember = "pineapples";
|
|
SG_CHECK_EQUAL(tree->getStringValue("settings/render/foo"),
|
|
std::string("pineapples"));
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("settings/render/foo"), 2);
|
|
|
|
donor.someMember = 256;
|
|
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 256);
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1);
|
|
|
|
// now fire value changed
|
|
l.resetChangeCounts();
|
|
donor.wrappedB = 4.000000001;
|
|
|
|
|
|
tree->getNode("position/body/a")->fireValueChanged();
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1);
|
|
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 256);
|
|
|
|
l.resetChangeCounts();
|
|
|
|
tree->getNode("position/body/a")->fireValueChanged();
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1);
|
|
|
|
tree->setDoubleValue("position/body/mass", 4.000000001);
|
|
// Not changed
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/mass"), 0);
|
|
|
|
donor.someMember = 970;
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1); // not changed
|
|
tree->getNode("position/body/a")->fireValueChanged();
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 2); // changed
|
|
|
|
|
|
donor.wrappedMember = "pears";
|
|
// Not changed
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("settings/render/foo"), 0);
|
|
tree->getNode("settings/render/foo")->fireValueChanged();
|
|
// Changed
|
|
SG_CHECK_EQUAL(l.checkValueChangeCount("settings/render/foo"), 1);
|
|
|
|
}
|
|
|
|
|
|
void testDeleterListener()
|
|
{
|
|
SGPropertyNode_ptr tree = new SGPropertyNode;
|
|
defineSamplePropertyTree(tree);
|
|
|
|
|
|
// recursive listen
|
|
{
|
|
TestListener* l = new TestListener(tree.get(), true /* recursive */);
|
|
tree->getNode("position/body")->addChangeListener(l);
|
|
tree->getNode("controls/")->addChangeListener(l);
|
|
|
|
SG_CHECK_EQUAL(l->checkValueChangeCount("position/body/a"), 0);
|
|
|
|
// create additional children
|
|
tree->setFloatValue("position/body/sub/theta", 0.1234);
|
|
|
|
SG_VERIFY(l->checkAdded("position/body/sub",
|
|
tree->getNode("position/body/sub/theta")));
|
|
|
|
SG_CHECK_EQUAL(l->checkValueChangeCount("position/body/sub/theta"), 1);
|
|
|
|
delete l;
|
|
|
|
tree->setFloatValue("position/body/sub/theta", 99.123);
|
|
tree->setIntValue("position/body/a", 33);
|
|
|
|
// verify no listeners at all
|
|
SG_VERIFY(ensureNListeners(tree, 0));
|
|
}
|
|
|
|
// Self-unregister. prior to 2019-07-08 this would segv.
|
|
{
|
|
std::shared_ptr<TestListener> l1( new TestListener(tree.get(), true /* recursive */, true /*self_unregister*/));
|
|
tree->setFloatValue("position/body/sub/self-unregister", 0.1);
|
|
tree->getNode("position/body/sub/self-unregister")->addChangeListener(l1.get());
|
|
tree->setFloatValue("position/body/sub/self-unregister", 0.2);
|
|
}
|
|
}
|
|
|
|
int main (int ac, char ** av)
|
|
{
|
|
test_value();
|
|
test_property_nodes();
|
|
|
|
for (int i = 1; i < ac; i++) {
|
|
try {
|
|
// cout << "Reading " << av[i] << endl;
|
|
SGPropertyNode root;
|
|
readProperties(SGPath::fromLocal8Bit(av[i]), &root);
|
|
writeProperties(cout, &root, true);
|
|
// cout << endl;
|
|
} catch (std::string &message) {
|
|
cout << "Aborted with " << message << endl;
|
|
}
|
|
}
|
|
|
|
test_addChild();
|
|
|
|
testListener();
|
|
tiedPropertiesTest();
|
|
tiedPropertiesListeners();
|
|
testDeleterListener();
|
|
|
|
// disable test for the moment
|
|
// testAliasedListeners();
|
|
|
|
return 0;
|
|
}
|