ExampleMemory.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @package RobotAPI::ArmarXObjects::ExampleMemory
17 * @author Rainer Kartmann ( rainer dot kartmann at kit dot edu )
18 * @date 2020
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
23#include "ExampleMemory.h"
24
25#include <atomic>
26
27#include <SimoxUtility/algorithm/string.h>
28
32
33#include <RobotAPI/components/armem/server/ExampleMemory/aron/ExampleData.aron.generated.h>
34#include <RobotAPI/components/armem/server/ExampleMemory/aron/PythonExampleData.aron.generated.h>
40
41namespace armarx
42{
45 {
48
49 defs->topic(debugObserver);
50
51 setMemoryName("Example");
52
53 p.core._defaultSegmentsStr = simox::alg::join(p.core.defaultCoreSegments, ", ");
54 defs->optional(p.core._defaultSegmentsStr,
55 "core.DefaultSegments",
56 "Core segments to add on start up (just as example).");
57 defs->optional(
58 p.core.addOnUsage,
59 "core.AddOnUsage",
60 "If enabled, core segments are added when required by a new provider segment."
61 "This will usually be off for most memory servers.");
62
63 defs->optional(p.enableRemoteGui,
64 "p.enableRemoteGui",
65 "If true, the remote gui is enabled.");
66 defs->optional(p.showMemoryContent,
67 "p.showMemoryContent",
68 "If true, memory content is shown in remote gui (requires enableRemoteGui=true). "
69 "Can be very slow for high-frequency updates and may cause nullptr errors!");
70 defs->optional(p.showClientStats,
71 "p.showClientStats",
72 "If true, client statistics are shown in the remote gui (requires enableRemoteGui=true).");
73 defs->optional(p.publishToDebugObserver,
74 "p.publishToDebugObserver",
75 "If true, client statistics are published to DebugObserver. "
76 "Channel format: '<ServerName> | Client<N>' with datafields: commits, queries, address, port, pid (if available).");
77 return defs;
78 }
79
80 std::string
82 {
83 return "ExampleMemory";
84 }
85
86 void
88 {
89 // Usually, the memory server will specify a number of core segments with a specific aron type.
90 workingMemory().addCoreSegment("ExampleData", armem::example::ExampleData::ToAronType());
91 workingMemory().addCoreSegment("LinkedData", armem::example::LinkedData::ToAronType());
92
93 workingMemory().addCoreSegment("PythonExampleData",
94 armem::example::PythonExampleData::ToAronType());
95
96
97 // We support the "Latest" prediction engine for the entire memory.
99 [this](const armem::PredictionRequest& request)
100 { return this->predictLatest(request); });
101
102 // For illustration purposes, we add more segments (without types).
103 bool trim = true;
104 p.core.defaultCoreSegments = simox::alg::split(p.core._defaultSegmentsStr, ",", trim);
105 p.core._defaultSegmentsStr.clear();
106
107 for (const std::string& name : p.core.defaultCoreSegments)
108 {
109 auto& c = workingMemory().addCoreSegment(name);
110 c.setMaxHistorySize(100);
111 }
112 }
113
114 void
116 {
117 if (p.enableRemoteGui)
118 {
119 tab.lastStatsUpdate = armem::Time::Now();
122 }
123 }
124
125 void
127 {
128 // Log client statistics before shutdown
129 logClientStatistics();
130 }
131
132 void
136
137 // WRITING
138
139 armem::data::AddSegmentsResult
140 ExampleMemory::addSegments(const armem::data::AddSegmentsInput& input, const Ice::Current&)
141 {
142 // This function is overloaded to trigger the remote gui rebuild.
143 armem::data::AddSegmentsResult result =
144 ReadWritePluginUser::addSegments(input, p.core.addOnUsage);
145 tab.rebuild = true;
146 return result;
147 }
148
149 armem::data::CommitResult
150 ExampleMemory::commit(const armem::data::Commit& commit, const Ice::Current& current)
151 {
152 // Track client statistics
153 recordCommit(current);
154
155 // This function is overloaded to trigger the remote gui rebuild.
156 armem::data::CommitResult result = ReadWritePluginUser::commit(commit);
157 tab.rebuild = true;
158 return result;
159 }
160
161 armem::query::data::Result
162 ExampleMemory::query(const armem::query::data::Input& input, const Ice::Current& current)
163 {
164 // Track client statistics (GUI updates only via RemoteGui_update timer)
165 recordQuery(current);
166
167 // Forward to base implementation
168 return ReadWritePluginUser::query(input);
169 }
170
171 // READING
172
173 // Inherited from Plugin
174
175
176 // ACTIONS
177 armem::actions::GetActionsOutputSeq
178 ExampleMemory::getActions(const armem::actions::GetActionsInputSeq& input)
179 {
180 using namespace armem::actions;
181 Action greeting{"hi", "Say hello to " + input[0].id.entityName};
182 Action failure{"fail", "Fail dramatically"};
183 Action nothing{"null", "Do nothing, but deeply nested"};
184
185 SubMenu one{"one", "One", {nothing}};
186 SubMenu two{"two", "Two", {one}};
187 SubMenu three{"three", "Three", {two}};
188 SubMenu four{"four", "Four", {three}};
189
190 Menu menu{greeting,
191 failure,
192 four,
193 SubMenu{"mut", "Mutate", {Action{"copy", "Copy latest instance"}}}};
194
195 return {{menu.toIce()}};
196 }
197
198 armem::actions::ExecuteActionOutputSeq
199 ExampleMemory::executeActions(const armem::actions::ExecuteActionInputSeq& input)
200 {
201 using namespace armem::actions;
202
203 ExecuteActionOutputSeq output;
204 for (const auto& [id, path] : input)
205 {
207 if (path == ActionPath{"hi"})
208 {
209 ARMARX_INFO << "Hello, " << memoryID.str() << "!";
210 output.emplace_back(true, "");
211 }
212 else if (path == ActionPath{"fail"})
213 {
214 ARMARX_WARNING << "Alas, I am gravely wounded!";
215 output.emplace_back(false, "Why would you do that to him?");
216 }
217 else if (not path.empty() and path.front() == "four" and path.back() == "null")
218 {
219 // Do nothing.
220 ARMARX_INFO << "Nested action (path: " << path << ")";
221 output.emplace_back(true, "");
222 }
223 else if (path == ActionPath{"mut", "copy"})
224 {
225 auto* instance = workingMemory().findLatestInstance(memoryID);
226 if (instance != nullptr)
227 {
228 armem::EntityUpdate update;
229 armem::MemoryID newID =
230 memoryID.getCoreSegmentID()
232 .withEntityName(memoryID.entityName);
233 update.entityID = newID;
234 update.referencedTime = armem::Time::Now();
235 update.instancesData = {instance->data()};
236
237 armem::Commit newCommit;
238 newCommit.add(update);
240
241 tab.rebuild = true;
242 ARMARX_INFO << "Duplicated " << memoryID;
243 output.emplace_back(true, "");
244 }
245 else
246 {
247 output.emplace_back(false, "Couldn't duplicate " + memoryID.str());
248 }
249 }
250 }
251
252 return output;
253 }
254
255 // PREDICTING
257 ExampleMemory::predictLatest(const armem::PredictionRequest& request)
258 {
260 auto memID = request.snapshotID;
261 result.snapshotID = memID;
262
264 builder.latestEntitySnapshot(memID);
265 auto queryResult =
267 if (queryResult.success)
268 {
269 auto readMemory = fromIce<armem::wm::Memory>(queryResult.memory);
270 auto* latest = readMemory.findLatestSnapshot(memID);
271 if (latest != nullptr)
272 {
273 auto instance = memID.hasInstanceIndex()
274 ? latest->getInstance(memID)
275 : latest->getInstance(latest->getInstanceIndices().at(0));
276 result.success = true;
277 result.prediction = instance.data();
278 }
279 else
280 {
281 result.success = false;
282 result.errorMessage =
283 "Could not find entity referenced by MemoryID '" + memID.str() + "'.";
284 }
285 }
286 else
287 {
288 result.success = false;
289 result.errorMessage =
290 "Could not find entity referenced by MemoryID '" + memID.str() + "'.";
291 }
292
293 return result;
294 }
295
296 // REMOTE GUI
297
298 void
300 {
301 using namespace armarx::RemoteGui::Client;
302
303 VBoxLayout root;
304
305 // Show memory content (if enabled)
306 if (p.showMemoryContent)
307 {
308 try
309 {
310 // Core segments are locked by MemoryRemoteGui.
312 root.addChild(tab.memoryGroup);
313 }
314 catch (const std::exception& e)
315 {
316 ARMARX_WARNING << "Failed to create memory content display: " << e.what();
317 GroupBox errorBox;
318 errorBox.setLabel("Memory Content");
319 errorBox.addChild(Label("Error displaying memory content - see logs"));
320 root.addChild(errorBox);
321 }
322 }
323
324 // Create client statistics group box (if enabled)
325 if (p.showClientStats)
326 {
327 tab.clientStatsGroup = createClientStatisticsGroupBox();
328 root.addChild(tab.clientStatsGroup);
329 }
330
331 root.addChild(VSpacer());
332 RemoteGui_createTab(getName(), root, &tab);
333 }
334
335 void
337 {
338 // Full rebuild if explicitly requested (e.g., memory content changed)
339 if (tab.rebuild.exchange(false))
340 {
342 tab.lastStatsUpdate = armem::Time::Now();
343 return;
344 }
345
346 // Periodic update for client statistics (every 10 seconds) - just update labels, no rebuild
347 if (p.showClientStats)
348 {
350 if ((now - tab.lastStatsUpdate).toMilliSecondsDouble() > tab.statsUpdateIntervalMs)
351 {
352 // Check if we need to rebuild (new client appeared)
353 bool needsRebuild = false;
354 {
355 std::lock_guard lock(clientStatsMutex);
356 // Rebuild if number of clients changed (new client or client disconnected would require new labels)
357 if (clientStats.size() != tab.clientLabels.size())
358 {
359 needsRebuild = true;
360 }
361 }
362
363 if (needsRebuild)
364 {
365 // Rebuild structure when clients added/removed
366 tab.rebuild = true;
367 }
368 else
369 {
370 // Just update existing labels (much faster)
371 updateClientStatisticsLabels();
372 }
373
374 tab.lastStatsUpdate = now;
375 }
376 }
377 }
378
380 ExampleMemory::createClientStatisticsGroupBox()
381 {
382 using namespace armarx::RemoteGui::Client;
383
384 std::lock_guard lock(clientStatsMutex);
385
386 GroupBox statsBox;
387 statsBox.setLabel("Client Statistics");
388
389 // Create summary label
390 tab.summaryLabel = Label("No clients connected yet");
391 statsBox.addChild(tab.summaryLabel);
392
393 // Create labels for each existing client
394 tab.clientLabels.clear();
395 tab.connectionLabels.clear();
396
397 int clientIndex = 1;
398 for (const auto& [clientId, stats] : clientStats)
399 {
400 // Client info label
401 Label clientLabel = Label("");
402 tab.clientLabels[clientId] = clientLabel;
403 statsBox.addChild(clientLabel);
404
405 // Connection label
406 Label connLabel = Label("");
407 tab.connectionLabels[clientId] = connLabel;
408 statsBox.addChild(connLabel);
409
410 clientIndex++;
411 }
412
413 // Update the text content (mutex already locked)
414 updateClientStatisticsLabelsInternal();
415
416 return statsBox;
417 }
418
419 void
420 ExampleMemory::updateClientStatisticsLabels()
421 {
422 std::lock_guard lock(clientStatsMutex);
423 updateClientStatisticsLabelsInternal();
424 }
425
426 void
427 ExampleMemory::updateClientStatisticsLabelsInternal()
428 {
429 using namespace armarx::RemoteGui::Client;
430
431 // NOTE: Assumes clientStatsMutex is already locked by caller
432
433 // Update summary
434 if (clientStats.empty())
435 {
436 tab.summaryLabel.setText("No clients connected yet");
437 }
438 else
439 {
440 std::stringstream summary;
441 summary << "Total clients: " << clientStats.size();
442 tab.summaryLabel.setText(summary.str());
443 }
444
445 // Update each client's labels
446 int clientIndex = 1;
447 for (const auto& [clientId, stats] : clientStats)
448 {
449 // Update client info
450 if (tab.clientLabels.count(clientId) > 0)
451 {
452 std::stringstream clientInfo;
453 clientInfo << "Client " << clientIndex << ": "
454 << "Commits=" << stats.commitCount << ", "
455 << "Queries=" << stats.queryCount;
456 tab.clientLabels[clientId].setText(clientInfo.str());
457 }
458
459 // Update connection info
460 if (tab.connectionLabels.count(clientId) > 0)
461 {
462 std::stringstream connInfo;
463 connInfo << " " << stats.remoteAddress;
464 if (stats.remotePort > 0)
465 {
466 connInfo << ":" << stats.remotePort;
467 }
468 if (stats.clientPid > 0)
469 {
470 connInfo << " (PID: " << stats.clientPid << ")";
471 }
472
473 std::string connStr = connInfo.str();
474 if (connStr.length() > 70)
475 {
476 connStr = connStr.substr(0, 67) + "...";
477 }
478 tab.connectionLabels[clientId].setText(connStr);
479 }
480
481 clientIndex++;
482 }
483 }
484
485 // CLIENT STATISTICS TRACKING
486
487 std::string
488 ExampleMemory::extractClientIdentifier(const Ice::Current& current) const
489 {
490 if (current.con)
491 {
492 // Get connection string which uniquely identifies the client connection
493 return current.con->toString();
494 }
495 return "unknown";
496 }
497
498 std::string
499 ExampleMemory::formatClientIdentifier(const std::string& connectionString) const
500 {
501 // Ice connection strings have format:
502 // "local address = <IP>:<port>\nremote address = <IP>:<port>"
503 // Extract the remote address for cleaner display
504
505 // Find "remote address = " marker
506 size_t remotePos = connectionString.find("remote address = ");
507 if (remotePos != std::string::npos)
508 {
509 // Extract from "remote address = " to end (or newline)
510 std::string remote = connectionString.substr(remotePos + 17); // 17 = length of "remote address = "
511
512 // Remove any trailing whitespace or newlines
513 size_t end = remote.find_first_of("\r\n");
514 if (end != std::string::npos)
515 {
516 remote = remote.substr(0, end);
517 }
518
519 return remote;
520 }
521
522 // If no "remote address" found, try to clean up the full string
523 // Replace newlines with spaces for single-line display
524 std::string cleaned = connectionString;
525 size_t pos = 0;
526 while ((pos = cleaned.find('\n', pos)) != std::string::npos)
527 {
528 cleaned.replace(pos, 1, " | ");
529 pos += 3;
530 }
531
532 return cleaned;
533 }
534
535 void
536 ExampleMemory::parseConnectionInfo(const std::string& connectionString, std::string& address, int& port) const
537 {
538 // Extract remote address and port from Ice connection string
539 std::string fullAddress = formatClientIdentifier(connectionString);
540
541 // Find last colon to separate address from port
542 // Need to handle IPv6 addresses which contain colons
543 size_t lastColon = fullAddress.rfind(':');
544
545 if (lastColon != std::string::npos)
546 {
547 // Extract port
548 std::string portStr = fullAddress.substr(lastColon + 1);
549 try
550 {
551 port = std::stoi(portStr);
552 address = fullAddress.substr(0, lastColon);
553 }
554 catch (const std::exception&)
555 {
556 // If port parsing fails, treat entire string as address
557 address = fullAddress;
558 port = -1;
559 }
560 }
561 else
562 {
563 // No colon found
564 address = fullAddress;
565 port = -1;
566 }
567 }
568
569 int
570 ExampleMemory::extractClientPid(const Ice::Current& current) const
571 {
572 // Try to extract PID from Ice context
573 auto it = current.ctx.find("clientPid");
574 if (it != current.ctx.end())
575 {
576 try
577 {
578 return std::stoi(it->second);
579 }
580 catch (const std::exception&)
581 {
582 return -1;
583 }
584 }
585 return -1;
586 }
587
588 int
589 ExampleMemory::getClientIndex(const std::string& clientId)
590 {
591 // NOTE: Assumes clientStatsMutex is already locked by caller
592
593 // Check if client already has an index
594 auto it = clientIndices.find(clientId);
595 if (it != clientIndices.end())
596 {
597 return it->second;
598 }
599
600 // Assign new index
601 int index = nextClientIndex++;
602 clientIndices[clientId] = index;
603 return index;
604 }
605
606 void
607 ExampleMemory::recordCommit(const Ice::Current& current)
608 {
609 std::lock_guard lock(clientStatsMutex);
610 std::string clientId = extractClientIdentifier(current);
611
612 auto& stats = clientStats[clientId];
613 bool isNewClient = stats.connectionString.empty();
614
615 if (isNewClient)
616 {
617 stats.connectionString = clientId;
618 stats.firstSeen = armem::Time::Now();
619
620 // Parse connection info to extract IP and port
621 parseConnectionInfo(clientId, stats.remoteAddress, stats.remotePort);
622
623 // Extract PID from Ice context if available
624 stats.clientPid = extractClientPid(current);
625
626 ARMARX_INFO << "New client connected: " << clientId;
627 }
628
629 stats.commitCount++;
630 stats.lastCommit = armem::Time::Now();
631
632 // Publish to DebugObserver if enabled
633 if (p.publishToDebugObserver && debugObserver)
634 {
635 int clientIndex = getClientIndex(clientId);
636 std::string channel = getName() + " | Client" + std::to_string(clientIndex);
638 values["commits"] = new armarx::Variant(static_cast<int>(stats.commitCount));
639 values["queries"] = new armarx::Variant(static_cast<int>(stats.queryCount));
640 values["address"] = new armarx::Variant(stats.remoteAddress);
641 values["port"] = new armarx::Variant(stats.remotePort);
642 if (stats.clientPid > 0)
643 {
644 values["pid"] = new armarx::Variant(stats.clientPid);
645 }
646 debugObserver->setDebugChannel(channel, values);
647 }
648 }
649
650 void
651 ExampleMemory::recordQuery(const Ice::Current& current)
652 {
653 std::lock_guard lock(clientStatsMutex);
654 std::string clientId = extractClientIdentifier(current);
655
656 auto& stats = clientStats[clientId];
657 bool isNewClient = stats.connectionString.empty();
658
659 if (isNewClient)
660 {
661 stats.connectionString = clientId;
662 stats.firstSeen = armem::Time::Now();
663
664 // Parse connection info to extract IP and port
665 parseConnectionInfo(clientId, stats.remoteAddress, stats.remotePort);
666
667 // Extract PID from Ice context if available
668 stats.clientPid = extractClientPid(current);
669
670 ARMARX_INFO << "New client connected: " << clientId;
671 }
672
673 stats.queryCount++;
674 stats.lastQuery = armem::Time::Now();
675
676 // Publish to DebugObserver if enabled
677 if (p.publishToDebugObserver && debugObserver)
678 {
679 int clientIndex = getClientIndex(clientId);
680 std::string channel = getName() + " | Client" + std::to_string(clientIndex);
682 values["commits"] = new armarx::Variant(static_cast<int>(stats.commitCount));
683 values["queries"] = new armarx::Variant(static_cast<int>(stats.queryCount));
684 values["address"] = new armarx::Variant(stats.remoteAddress);
685 values["port"] = new armarx::Variant(stats.remotePort);
686 if (stats.clientPid > 0)
687 {
688 values["pid"] = new armarx::Variant(stats.clientPid);
689 }
690 debugObserver->setDebugChannel(channel, values);
691 }
692 }
693
694 void
695 ExampleMemory::logClientStatistics() const
696 {
697 std::lock_guard lock(clientStatsMutex);
698
699 if (clientStats.empty())
700 {
701 ARMARX_INFO << "No client statistics available.";
702 return;
703 }
704
705 ARMARX_IMPORTANT << "=== Client Statistics ===";
706 ARMARX_IMPORTANT << "Total clients tracked: " << clientStats.size();
707
708 for (const auto& [clientId, stats] : clientStats)
709 {
710 ARMARX_IMPORTANT << "\nClient: " << stats.remoteAddress
711 << (stats.remotePort > 0 ? ":" + std::to_string(stats.remotePort) : "");
712 if (stats.clientPid > 0)
713 {
714 ARMARX_IMPORTANT << " PID: " << stats.clientPid;
715 }
716 ARMARX_IMPORTANT << " Commits: " << stats.commitCount;
717 ARMARX_IMPORTANT << " Queries: " << stats.queryCount;
718 ARMARX_IMPORTANT << " First seen: " << stats.firstSeen.toDateTimeString();
719
720 if (stats.commitCount > 0)
721 {
722 ARMARX_IMPORTANT << " Last commit: " << stats.lastCommit.toDateTimeString();
723 }
724 if (stats.queryCount > 0)
725 {
726 ARMARX_IMPORTANT << " Last query: " << stats.lastQuery.toDateTimeString();
727 }
728
729 // Show full connection string for debugging
730 ARMARX_INFO << " Raw connection: " << stats.connectionString;
731 }
732
733 ARMARX_IMPORTANT << "========================";
734 }
735
736} // namespace armarx
int Label(int n[], int size, int *curLabel, MiscLib::Vector< std::pair< int, size_t > > *labels)
Definition Bitmap.cpp:801
uint8_t index
if(!yyvaluep)
Definition Grammar.cpp:645
constexpr T c
Default component property definition container.
Definition Component.h:70
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
void onInitComponent() override
Pure virtual hook for the subclass.
armem::query::data::Result query(const armem::query::data::Input &input, const Ice::Current &=Ice::emptyCurrent) override
void onDisconnectComponent() override
Hook for subclass.
void RemoteGui_update() override
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
armem::data::CommitResult commit(const armem::data::Commit &commit, const Ice::Current &=Ice::emptyCurrent) override
armem::actions::ExecuteActionOutputSeq executeActions(const armem::actions::ExecuteActionInputSeq &input) override
armem::actions::GetActionsOutputSeq getActions(const armem::actions::GetActionsInputSeq &input) override
void onConnectComponent() override
Pure virtual hook for the subclass.
void onExitComponent() override
Hook for subclass.
armem::data::AddSegmentsResult addSegments(const armem::data::AddSegmentsInput &input, const Ice::Current &) override
std::string getDefaultName() const override
std::string getName() const
Retrieve name of object.
MemoryID withProviderSegmentName(const std::string &name) const
Definition MemoryID.cpp:417
MemoryID getCoreSegmentID() const
Definition MemoryID.cpp:294
std::string str(bool escapeDelimiters=true) const
Get a string representation of this memory ID.
Definition MemoryID.cpp:102
MemoryID withEntityName(const std::string &name) const
Definition MemoryID.cpp:425
std::string entityName
Definition MemoryID.h:53
std::string providerSegmentName
Definition MemoryID.h:52
void latestEntitySnapshot(const MemoryID &entityID)
Definition Builder.cpp:131
Utility for memory Remote Guis.
GroupBox makeGroupBox(const armem::wm::Memory &memory) const
CoreSegment & addCoreSegment(const std::string &name, Args... args)
void addPredictor(const PredictionEngine &engine, Predictor &&predictor)
Definition Prediction.h:68
static DateTime Now()
Definition DateTime.cpp:51
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
armarx::core::time::DateTime Time
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::map< std::string, VariantBasePtr > StringVariantBaseMap
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
void fromIce(const std::map< IceKeyT, IceValueT > &iceMap, boost::container::flat_map< CppKeyT, CppValueT > &cppMap)
void toIce(std::map< IceKeyT, IceValueT > &iceMap, const boost::container::flat_map< CppKeyT, CppValueT > &cppMap)
void RemoteGui_createTab(std::string const &name, RemoteGui::Client::Widget const &rootWidget, RemoteGui::Client::Tab *tab)
void addChild(Widget const &child)
Definition Widgets.cpp:95
void setLabel(std::string const &text)
Definition Widgets.cpp:420
A bundle of updates to be sent to the memory.
Definition Commit.h:90
EntityUpdate & add()
Definition Commit.cpp:80
An update of an entity for a specific point in time.
Definition Commit.h:26
aron::data::DictPtr prediction
Definition Prediction.h:63
const auto * findLatestInstance(int instanceIndex=0) const
Find the latest entity instance.