Throwing std::string is slightly unconventional, switch to throwing exceptions inheriting std::exception.
987 lines
31 KiB
C++
987 lines
31 KiB
C++
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Test harness.
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <simgear_config.h>
|
|
|
|
#include <simgear/compiler.h>
|
|
|
|
#include <algorithm>
|
|
#include <memory> // std::unique_ptr
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <exception>
|
|
|
|
#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::exception &e) {
|
|
cout << "Aborted with " << e.what() << endl;
|
|
}
|
|
}
|
|
|
|
test_addChild();
|
|
|
|
testListener();
|
|
tiedPropertiesTest();
|
|
tiedPropertiesListeners();
|
|
testDeleterListener();
|
|
|
|
// disable test for the moment
|
|
// testAliasedListeners();
|
|
|
|
return 0;
|
|
}
|