FlightGear next
MirrorPropertyTreeWebsocket.cxx
Go to the documentation of this file.
1// MirrorPropertyTreeWebsocket.cxx -- A websocket for mirroring a property sub-tree
2//
3// Written by James Turner, started November 2016.
4//
5// Copyright (C) 2016 James Turner
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
22#include "jsonprops.hxx"
23
24#include <algorithm>
25#include <unordered_map>
26#include <set>
27
28#include <simgear/debug/logstream.hxx>
29#include <simgear/props/props.hxx>
30#include <simgear/structure/commands.hxx>
31
32#include <simgear/props/props_io.hxx>
33#include <Main/globals.hxx>
34#include <Main/fg_props.hxx>
35
36#include <cJSON.h>
37
38//#define MIRROR_DEBUG 1
39
40namespace flightgear {
41namespace http {
42
43using std::string;
44
45 typedef unsigned int PropertyId; // connection local property id
46
48 {
49 PropertyValue(SGPropertyNode* cur = nullptr) :
50 type(simgear::props::NONE)
51 {
52 if (!cur) {
53 return;
54 }
55
56 type = cur->getType();
57 switch (type) {
58 case simgear::props::INT:
59 intValue = cur->getIntValue();
60 break;
61
62 case simgear::props::BOOL:
63 intValue = cur->getBoolValue();
64 break;
65
66 case simgear::props::FLOAT:
67 case simgear::props::DOUBLE:
68 doubleValue = cur->getDoubleValue();
69 break;
70
71 case simgear::props::STRING:
72 case simgear::props::UNSPECIFIED:
73 stringValue = cur->getStringValue();
74 break;
75
76 case simgear::props::NONE:
77 break;
78
79 default:
80 SG_LOG(SG_NETWORK, SG_DEV_ALERT, "MirrorPropTree PropertyValue : implement me!" << type);
81 break;
82 }
83 }
84
85 bool equals(SGPropertyNode* node, const PropertyValue& other) const
86 {
87 if (other.type != type) return false;
88 switch (type) {
89 case simgear::props::INT:
90 case simgear::props::BOOL:
91 return intValue == other.intValue;
92
93 case simgear::props::FLOAT:
94 case simgear::props::DOUBLE:
95 return std::fabs(doubleValue - other.doubleValue) < 1e-4;
96
97 case simgear::props::STRING:
98 case simgear::props::UNSPECIFIED:
99 return stringValue == other.stringValue;
100
101 case simgear::props::NONE:
102 return true;
103
104 default:
105 break;
106 }
107
108 return false;
109 }
110
111 simgear::props::Type type;
112 union {
115 };
116 std::string stringValue;
117 };
118
119
121 {
122 RemovedNode(SGPropertyNode* node, unsigned int aId) :
123 path(node->getPath()),
124 id(aId)
125 {}
126
127 std::string path;
128 unsigned int id = 0;
129
130 bool operator==(const RemovedNode& other) const
131 {
132 return (path == other.path);
133 }
134 };
135
136 class MirrorTreeListener : public SGPropertyChangeListener
137 {
138 public:
139 MirrorTreeListener() : SGPropertyChangeListener(true /* recursive */)
140 {
141 previousValues.resize(2);
142 }
143
145 {
146 }
147
148 void valueChanged(SGPropertyNode* node) override
149 {
150 auto it = idHash.find(node);
151 if (it == idHash.end()) {
152 // not new to the server, but new to the client
153 newNodes.insert(node);
154 } else {
155 assert(previousValues.size() > it->second);
156 PropertyValue newVal(node);
157 if (!previousValues[it->second].equals(node, newVal)) {
158 previousValues[it->second] = newVal;
159 changedNodes.insert(node);
160 }
161 }
162 }
163
164 void childAdded(SGPropertyNode* parent, SGPropertyNode* child) override
165 {
166 SG_UNUSED(parent);
167 recursiveAdd(child);
168 }
169
170 void recursiveAdd(SGPropertyNode* node)
171 {
172 RemovedNode r(node, 0 /* id not actually used */);
173#if defined (MIRROR_DEBUG)
174 SG_LOG(SG_NETWORK, SG_INFO, "looking for RR:" << r.path);
175#endif
176 auto rrIt = std::find(recentlyRemoved.begin(), recentlyRemoved.end(), r);
177 if (rrIt != recentlyRemoved.end()) {
178#if defined (MIRROR_DEBUG)
179 SG_LOG(SG_NETWORK, SG_INFO, "recycling node:" << node->getPath());
180#endif
181 const auto id = rrIt->id;
182 // recycle nodes which get thrashed from Nasal (deleted + re-created
183 // each time a Nasal timer fires)
184 removedNodes.erase(id); // don't remove it!
185 idHash.insert(std::make_pair(node, id));
186
187 // we can still do change compression here, but this also
188 // deals with type mutation when removing + re-adding with a
189 // different type
190 PropertyValue newVal(node);
191 if (!previousValues[id].equals(node, newVal)) {
192 previousValues[id] = newVal;
193 changedNodes.insert(node);
194#if defined (MIRROR_DEBUG)
195 SG_LOG(SG_NETWORK, SG_INFO, "\tand will actually change" << node->getPath());
196#endif
197 }
198
199 recentlyRemoved.erase(rrIt);
200 return;
201 }
202#if defined (MIRROR_DEBUG)
203 SG_LOG(SG_NETWORK, SG_INFO, "new node:" << node->getPath());
204#endif
205 newNodes.insert(node);
206 int child = 0;
207 for (; child < node->nChildren(); ++child) {
208 recursiveAdd(node->getChild(child));
209 }
210 }
211
212 void childRemoved(SGPropertyNode* parent, SGPropertyNode* child) override
213 {
214 changedNodes.erase(child); // have to do this here with the pointer valid
215 newNodes.erase(child);
216
217 auto it = idHash.find(child);
218 if (it != idHash.end()) {
219 removedNodes.insert(it->second);
220 idHash.erase(it);
221 // record so we can map removed+add of the same property into
222 // a simple value change (this happens commonly with the canvas
223 // due to lazy Nasal scripting)
224 recentlyRemoved.emplace_back(child, it->second);
225#if defined (MIRROR_DEBUG)
226 SG_LOG(SG_NETWORK, SG_INFO, "adding RR:" << recentlyRemoved.back().path);
227#endif
228 }
229#if defined (MIRROR_DEBUG)
230 SG_LOG(SG_NETWORK, SG_INFO, "saw remove of:" << child->getPath());
231#endif
232 }
233
234 void registerSubtree(SGPropertyNode* node)
235 {
236#if defined (MIRROR_DEBUG)
237 SG_LOG(SG_NETWORK, SG_INFO, "register subtree:" << node->getPath());
238#endif
239 valueChanged(node);
240
241 // and recurse
242 int child = 0;
243 for (; child < node->nChildren(); ++child) {
244 registerSubtree(node->getChild(child));
245 }
246 }
247
248 std::set<SGPropertyNode*> newNodes;
249 std::set<SGPropertyNode*> changedNodes;
250 std::set<PropertyId> removedNodes;
251
252 PropertyId idForProperty(SGPropertyNode* prop)
253 {
254 auto it = idHash.find(prop);
255 if (it == idHash.end()) {
256 it = idHash.insert(it, std::make_pair(prop, nextPropertyId++));
257 previousValues.push_back(PropertyValue(prop));
258 }
259 return it->second;
260 }
261
263 {
264#if defined (MIRROR_DEBUG)
265 SGTimeStamp st;
266 st.stamp();
267
268 int newSize = newNodes.size();
269 int changedSize = changedNodes.size();
270 int removedSize = removedNodes.size();
271#endif
272 cJSON* result = cJSON_CreateObject();
273 if (!newNodes.empty()) {
274 cJSON * newNodesArray = cJSON_CreateArray();
275
276 // cJSON_AddItemToArray performance is O(N) due to use of a linked
277 // list, which dominates the performance here. To fix this we maintan
278 // a point to the tail of the array, keeping appends O(1)
279 cJSON* arrayTail = nullptr;
280
281 for (auto prop : newNodes) {
282 changedNodes.erase(prop); // avoid duplicate send
283 cJSON* newPropData = cJSON_CreateObject();
284 cJSON_AddItemToObject(newPropData, "path", cJSON_CreateString(prop->getPath(true).c_str()));
285 cJSON_AddItemToObject(newPropData, "type", cJSON_CreateString(JSON::getPropertyTypeString(prop->getType())));
286 cJSON_AddItemToObject(newPropData, "index", cJSON_CreateNumber(prop->getIndex()));
287 cJSON_AddItemToObject(newPropData, "position", cJSON_CreateNumber(prop->getPosition()));
288 cJSON_AddItemToObject(newPropData, "id", cJSON_CreateNumber(idForProperty(prop)));
289 if (prop->getType() != simgear::props::NONE) {
290 cJSON_AddItemToObject(newPropData, "value", JSON::valueToJson(prop));
291 }
292
293 if (arrayTail) {
294 arrayTail->next = newPropData;
295 newPropData->prev = arrayTail;
296 arrayTail = newPropData;
297 } else {
298 cJSON_AddItemToArray(newNodesArray, newPropData);
299 arrayTail = newPropData;
300 }
301 }
302
303 newNodes.clear();
304 cJSON_AddItemToObject(result, "created", newNodesArray);
305 }
306
307
308 if (!removedNodes.empty()) {
309 cJSON * deletedNodesArray = cJSON_CreateArray();
310 for (auto propId : removedNodes) {
311 cJSON_AddItemToArray(deletedNodesArray, cJSON_CreateNumber(propId));
312 }
313 cJSON_AddItemToObject(result, "removed", deletedNodesArray);
314 removedNodes.clear();
315 }
316
317 if (!changedNodes.empty()) {
318 cJSON * changedNodesArray = cJSON_CreateArray();
319
320 // see comment above about cJSON_AddItemToArray
321 cJSON* tail = nullptr;
322
323 for (auto prop : changedNodes) {
324 cJSON* propData = cJSON_CreateArray();
325 cJSON_AddItemToArray(propData, cJSON_CreateNumber(idForProperty(prop)));
326 cJSON_AddItemToArray(propData, JSON::valueToJson(prop));
327
328
329 if (tail) {
330 tail->next = propData;
331 propData->prev = tail;
332 tail = propData;
333 } else {
334 cJSON_AddItemToArray(changedNodesArray, propData);
335 tail = propData;
336 }
337 }
338
339 changedNodes.clear();
340 cJSON_AddItemToObject(result, "changed", changedNodesArray);
341 }
342#if defined (MIRROR_DEBUG)
343 SG_LOG(SG_NETWORK, SG_INFO, "making JSON data took:" << st.elapsedMSec() << " for " << newSize << "/" << changedSize << "/" << removedSize);
344#endif
345 recentlyRemoved.clear();
346
347 return result;
348 }
349
350 bool haveChangesToSend() const
351 {
352 return !newNodes.empty() || !changedNodes.empty() || !removedNodes.empty();
353 }
354 private:
355 PropertyId nextPropertyId = 1;
356 std::unordered_map<SGPropertyNode*, PropertyId> idHash;
357 std::vector<PropertyValue> previousValues;
358
362 std::vector<RemovedNode> recentlyRemoved;
363 };
364
365#if 0
366
367static void handleSetCommand(const string_list& nodes, cJSON* json, WebsocketWriter &writer)
368{
369 cJSON * value = cJSON_GetObjectItem(json, "value");
370 if ( NULL != value ) {
371 if (nodes.size() > 1) {
372 SG_LOG(SG_NETWORK, SG_WARN, "httpd: WS set: insufficent values for nodes:" << nodes.size());
373 return;
374 }
375
376 SGPropertyNode_ptr n = fgGetNode(nodes.front());
377 if (!n) {
378 SG_LOG(SG_NETWORK, SG_WARN, "httpd: set '" << nodes.front() << "' not found");
379 return;
380 }
381
382 setPropertyFromJson(n, value);
383 return;
384 }
385
386 cJSON * values = cJSON_GetObjectItem(json, "values");
387 if ( ( NULL == values ) || ( static_cast<size_t>(cJSON_GetArraySize(values)) != nodes.size()) ) {
388 SG_LOG(SG_NETWORK, SG_WARN, "httpd: WS set: mismatched nodes/values sizes:" << nodes.size());
389 return;
390 }
391
392 string_list::const_iterator it;
393 int i=0;
394 for (it = nodes.begin(); it != nodes.end(); ++it, ++i) {
395 SGPropertyNode_ptr n = fgGetNode(*it);
396 if (!n) {
397 SG_LOG(SG_NETWORK, SG_WARN, "httpd: get '" << *it << "' not found");
398 return;
399 }
400
401 setPropertyFromJson(n, cJSON_GetArrayItem(values, i));
402 } // of nodes iteration
403}
404
405static void handleExecCommand(cJSON* json)
406{
407 cJSON* name = cJSON_GetObjectItem(json, "fgcommand");
408 if ((NULL == name )|| (NULL == name->valuestring)) {
409 SG_LOG(SG_NETWORK, SG_WARN, "httpd: exec: no fgcommand name");
410 return;
411 }
412
413 SGPropertyNode_ptr arg(new SGPropertyNode);
414 JSON::addChildrenToProp( json, arg );
415
416 globals->get_commands()->execute(name->valuestring, arg);
417}
418#endif
419
421 _rootPath(path),
422 _listener(new MirrorTreeListener),
423 _minSendInterval(100)
424{
425 checkNodeExists();
426}
427
431
433{
434 if (_subtreeRoot) {
435 _subtreeRoot->removeChangeListener(_listener.get());
436 }
437}
438
439void MirrorPropertyTreeWebsocket::checkNodeExists()
440{
441 _subtreeRoot = globals->get_props()->getNode(_rootPath, false);
442 if (_subtreeRoot) {
443 _subtreeRoot->addChangeListener(_listener.get());
444 _listener->registerSubtree(_subtreeRoot);
445 _lastSendTime = SGTimeStamp::now();
446 }
447}
448
450{
451 if (!_subtreeRoot) {
452 checkNodeExists();
453 if (!_subtreeRoot) {
454 return; // still no node exists, we can't process this
455 }
456 }
457
458 if (request.Content.empty()) return;
459#if 0
460 /*
461 * allowed JSON is
462 {
463 command : 'addListener',
464 nodes : [
465 '/bar/baz',
466 '/foo/bar'
467 ],
468 node: '/bax/foo'
469 }
470 */
471 cJSON * json = cJSON_Parse(request.Content.c_str());
472 if ( NULL != json) {
473 string command;
474 cJSON * j = cJSON_GetObjectItem(json, "command");
475 if ( NULL != j && NULL != j->valuestring) {
476 command = j->valuestring;
477 }
478
479 // handle a single node name, or an array of them
480 string_list nodeNames;
481 j = cJSON_GetObjectItem(json, "node");
482 if ( NULL != j && NULL != j->valuestring) {
483 nodeNames.push_back(simgear::strutils::strip(string(j->valuestring)));
484 }
485
486 cJSON * nodes = cJSON_GetObjectItem(json, "nodes");
487 if ( NULL != nodes) {
488 for (int i = 0; i < cJSON_GetArraySize(nodes); i++) {
489 cJSON * node = cJSON_GetArrayItem(nodes, i);
490 if ( NULL == node) continue;
491 if ( NULL == node->valuestring) continue;
492 nodeNames.push_back(simgear::strutils::strip(string(node->valuestring)));
493 }
494 }
495
496 if (command == "get") {
497 handleGetCommand(nodeNames, writer);
498 } else if (command == "set") {
499 handleSetCommand(nodeNames, json, writer);
500 } else if (command == "exec") {
501 handleExecCommand(json);
502 } else {
503 string_list::const_iterator it;
504 for (it = nodeNames.begin(); it != nodeNames.end(); ++it) {
505 _watchedNodes.handleCommand(command, *it, _propertyChangeObserver);
506 }
507 }
508
509 cJSON_Delete(json);
510 }
511 #endif
512}
513
515{
516 if (!_subtreeRoot) {
517 checkNodeExists();
518 if (!_subtreeRoot) {
519 return;
520 }
521 }
522
523 if (!_listener->haveChangesToSend()) {
524 return;
525 }
526
527 if (_lastSendTime.elapsedMSec() < _minSendInterval) {
528 return;
529 }
530
531 // okay, we will send now, update the send stamp
532 _lastSendTime.stamp();
533
534 cJSON * json = _listener->makeJSONData();
535 char * jsonString = cJSON_PrintUnformatted( json );
536 writer.writeText( jsonString );
537 free( jsonString );
538 cJSON_Delete( json );
539}
540
541} // namespace http
542} // namespace flightgear
#define i(x)
SGCommandMgr * get_commands()
Definition globals.hxx:330
static const char * getPropertyTypeString(simgear::props::Type type)
Definition jsonprops.cxx:29
static cJSON * valueToJson(SGPropertyNode_ptr n)
Definition jsonprops.cxx:73
static void addChildrenToProp(cJSON *json, SGPropertyNode_ptr base)
void handleRequest(const HTTPRequest &request, WebsocketWriter &writer) override
void childRemoved(SGPropertyNode *parent, SGPropertyNode *child) override
void childAdded(SGPropertyNode *parent, SGPropertyNode *child) override
void valueChanged(SGPropertyNode *node) override
int writeText(const char *data, size_t len)
Definition Websocket.hxx:40
FGGlobals * globals
Definition globals.cxx:142
std::vector< std::string > string_list
Definition globals.hxx:36
static void setPropertyFromJson(SGPropertyNode_ptr prop, cJSON *json)
static void handleSetCommand(const string_list &nodes, cJSON *json, WebsocketWriter &writer)
static void handleExecCommand(cJSON *json)
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
const char * name
SGCommandMgr::command_t command
@ NONE
Definition options.cxx:1814
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
bool equals(SGPropertyNode *node, const PropertyValue &other) const
RemovedNode(SGPropertyNode *node, unsigned int aId)
bool operator==(const RemovedNode &other) const