Ember
Loading...
Searching...
No Matches
MonitorFrame.cpp
Go to the documentation of this file.
1#include "App/MonitorFrame.h"
3#include "Panels/MainPanel.h"
4#include "Panels/RightSidePanel.h"
6#include "Tabs/PropertiesTab.h"
7#include "Utils/DPI.h"
9#include "Utils/Logger.h"
10
11namespace Ember {
12namespace Monitor {
13
14enum { ID_POLL_TIMER = wxID_HIGHEST + 100, ID_QUIT = wxID_EXIT, ID_ABOUT = wxID_ABOUT };
15
19
20 MonitorFrame::MonitorFrame(const wxString &title, const wxPoint &pos, const wxSize &size)
21 : wxFrame(nullptr, wxID_ANY, title, pos, size), m_auiManager(nullptr), m_pollTimer(this, ID_POLL_TIMER) {
22
23 CreateMenuBar();
24 CreateLayout();
25 SetupCallbacks();
26
27 m_pollTimer.Start(33);
28
29 UpdateStatusBar();
30}
31
33 m_pollTimer.Stop();
34 m_server.Stop();
35 if (m_auiManager) {
36 m_auiManager->UnInit();
37 delete m_auiManager;
38 }
39}
40
42 wxMenuBar *menuBar = new wxMenuBar();
43
44 wxMenu *fileMenu = new wxMenu();
45 fileMenu->Append(ID_QUIT, "E&xit\tAlt-F4", "Quit the application");
46 menuBar->Append(fileMenu, "&File");
47
48 wxMenu *helpMenu = new wxMenu();
49 helpMenu->Append(ID_ABOUT, "&About...", "Show about dialog");
50 menuBar->Append(helpMenu, "&Help");
51
52 SetMenuBar(menuBar);
53}
54
56 m_statusBar = CreateStatusBar(3);
58 int widths[] = {-1, sbFieldWidth, sbFieldWidth};
59 m_statusBar->SetStatusWidths(3, widths);
60
61 m_auiManager = new wxAuiManager(this);
62
64 m_mainPanel = new MainPanel(this);
65 m_rightPanel = new RightSidePanel(this);
66 m_rightPanel->SetupTabs();
67 m_navigatorTab = m_rightPanel->GetNavigatorTab();
68 m_propertiesTab = m_rightPanel->GetPropertiesTab();
69
70 wxSize cs = GetClientSize();
72
73 int connWidth = cs.x * 15 / 100;
74 int connMinWidth = cs.x * 12 / 100;
75 int rightWidth = cs.x * 18 / 100;
76 int rightMinWidth = cs.x * 13 / 100;
77
78 m_auiManager->AddPane(m_connectionPanel, wxAuiPaneInfo()
79 .Name("connection_panel")
80 .Caption("Connection")
81 .Left()
82 .BestSize(wxSize(connWidth, -1))
83 .MinSize(wxSize(connMinWidth, -1))
84 .Dockable(false)
85 .Floatable(false)
86 .CloseButton(false)
87 .CaptionVisible(false)
88 .Resizable(true));
89
90 m_auiManager->AddPane(
92 wxAuiPaneInfo().Name("scene_panel").CenterPane().Dockable(false).Floatable(false).CloseButton(false));
93
94 m_auiManager->AddPane(m_rightPanel, wxAuiPaneInfo()
95 .Name("right_panel")
96 .Caption("Inspector")
97 .Right()
98 .BestSize(wxSize(rightWidth, -1))
99 .MinSize(wxSize(rightMinWidth, -1))
100 .Dockable(false)
101 .Floatable(false)
102 .CloseButton(false)
103 .CaptionVisible(false)
104 .Resizable(true));
105
106 m_layout.Track("connection_panel", 0.15, 0.12, true);
107 m_layout.Track("right_panel", 0.18, 0.13, true);
108
109 m_auiManager->Update();
110}
111
113 m_server.SetConnectCallback([this](const std::string &address) { OnClientConnect(address); });
114
115 m_server.SetDisconnectCallback([this]() { OnClientDisconnect(); });
116
117 m_server.SetMessageCallback([this](const Network::MessageFrame &frame) {
119 m_pendingTickUpdate = frame;
121 } else {
122 OnServerMessage(frame);
123 }
124 });
125
126 // Wire node selection: canvas -> hierarchy + properties
127 m_mainPanel->SetNodeSelectionCallback([this](EmberCore::ITreeNode *node) { OnNodeSelected(node); });
128
129 // Wire node selection: navigator hierarchy -> canvas + properties
130 m_navigatorTab->SetNodeSelectionCallback([this](EmberCore::ITreeNode *node) {
131 OnNodeSelected(node);
132 if (node) {
133 m_mainPanel->GetTreeCanvas()->SetSelectedNode(node);
134 }
135 });
136
137 m_navigatorTab->SetCallbacks({[]() { return true; }, [this]() { RefreshVisualization(); },
138 [this](EmberCore::ITreeNode *node) {
139 if (node) {
140 m_mainPanel->GetTreeCanvas()->CenterOnNode(node);
141 }
142 },
143 nullptr, nullptr});
144}
145
146void MonitorFrame::OnFrameResize(wxSizeEvent &event) {
147 wxSize newSize = GetClientSize();
148 if (m_auiManager && m_lastClientSize.x > 0 && m_lastClientSize.y > 0) {
149 m_layout.HandleResize(m_auiManager, m_lastClientSize, newSize);
150 m_auiManager->Update();
151 }
152 m_lastClientSize = newSize;
153 event.Skip();
154}
155
156void MonitorFrame::OnClose(wxCloseEvent &event) {
157 m_pollTimer.Stop();
158 m_server.Stop();
159 if (m_auiManager) {
160 m_auiManager->UnInit();
161 delete m_auiManager;
162 m_auiManager = nullptr;
163 }
164 Destroy();
165}
166
167void MonitorFrame::OnQuit(wxCommandEvent &event) { Close(true); }
168
169void MonitorFrame::OnAbout(wxCommandEvent &event) {
170 wxMessageBox("EmberMonitor v1.0\n\nReal-time Behavior Tree Monitoring", "About EmberMonitor",
171 wxOK | wxICON_INFORMATION, this);
172}
173
174void MonitorFrame::OnPollTimer(wxTimerEvent &event) {
175 m_server.Poll();
176
180 m_mainPanel->GetTreeCanvas()->MarkDirty();
182 }
183
184 if (m_stateChanged) {
186 m_stateChanged = false;
187 }
188}
189
190void MonitorFrame::OnClientConnect(const std::string &address) {
191 LOG_INFO("Monitor", "Client connected: " + address);
193 m_connectionPanel->UpdateStatus();
195}
196
198 LOG_INFO("Monitor", "Client disconnected");
199 m_currentTree.reset();
200 m_stateManager.reset();
201 m_currentTreeId.clear();
203
204 m_mainPanel->ClearTree();
205 m_navigatorTab->ClearMonitorTree();
206 m_propertiesTab->ClearNode();
207
208 m_connectionPanel->UpdateStatus();
210}
211
213 LOG_TRACE("Monitor", "Message type=" + std::to_string(static_cast<int>(frame.messageType)) +
214 " payload=" + std::to_string(frame.payload.size()) + " bytes");
215 switch (frame.messageType) {
217 HandleHandshake(frame);
218 break;
220 HandleTreeInit(frame);
221 break;
223 HandleTickUpdate(frame);
224 break;
227 break;
229 HandleTreeReset(frame);
230 break;
232 HandleDisconnect(frame);
233 break;
234 default:
235 LOG_WARNING("Monitor", "Unknown message type " + std::to_string(static_cast<int>(frame.messageType)));
236 break;
237 }
238}
239
241 LOG_INFO("Monitor", "Handshake received (payload=" + std::to_string(frame.payload.size()) + " bytes)");
243 LOG_ERROR("Monitor", "Handshake verification failed!");
244 auto errorFrame = m_codec.BuildError(Ember::Protocol::ErrorCode_InvalidMessage, "Invalid handshake message");
245 m_server.Send(errorFrame);
246 return;
247 }
248
250
251 std::string clientName = handshake->client_name() ? handshake->client_name()->str() : "";
252 std::string clientId = handshake->client_id() ? handshake->client_id()->str() : "";
253 LOG_INFO("Monitor", "Handshake OK: client='" + clientName + "' id='" + clientId + "'");
254
255 m_server.SetClientName(clientName.empty() ? clientId : clientName);
256
257 auto ackFrame = m_codec.BuildHandshakeAck("1.0.0", "session_" + std::to_string(time(nullptr)), true);
258 m_server.Send(ackFrame);
259
261 m_connectionPanel->UpdateStatus();
262 m_connectionPanel->SetTreeStatus("Receiving tree...");
264}
265
267 LOG_INFO("Monitor", "TreeInit received (payload=" + std::to_string(frame.payload.size()) + " bytes)");
269 LOG_ERROR("Monitor", "TreeInit verification failed!");
270 auto errorFrame = m_codec.BuildError(Ember::Protocol::ErrorCode_InvalidMessage, "Invalid TreeInit message");
271 m_server.Send(errorFrame);
272 return;
273 }
274
276 LOG_INFO("Monitor",
277 "TreeInit: tree_id='" + std::string(treeInit->tree_id() ? treeInit->tree_id()->c_str() : "(null)") +
278 "' tree_name='" + std::string(treeInit->tree_name() ? treeInit->tree_name()->c_str() : "(null)") +
279 "' has_root=" + std::to_string(treeInit->root() != nullptr));
280
281 auto result = m_treeBuilder.Build(treeInit);
282
283 if (!result.success) {
284 LOG_ERROR("Monitor", "TreeBuilder failed: " + result.error);
285 auto ackFrame =
286 m_codec.BuildTreeInitAck(treeInit->tree_id() ? treeInit->tree_id()->str() : "", false, 0, result.error);
287 m_server.Send(ackFrame);
288 return;
289 }
290
291 LOG_INFO("Monitor", "Tree built: " + std::to_string(result.node_count) +
292 " nodes, hasRoot=" + std::to_string(result.tree->HasRootNode()));
293
294 m_currentTree = result.tree;
295 m_currentTreeId = treeInit->tree_id() ? treeInit->tree_id()->str() : "";
296 m_stateManager = std::make_shared<Network::StateManager>(m_currentTree);
297 m_stateManager->EnableHistory(1000);
298
299 auto adapter = std::make_shared<Network::TCPTreeAdapter>(m_currentTree, m_stateManager);
300 m_mainPanel->SetTree(adapter, m_stateManager);
301
302 std::map<std::string, std::shared_ptr<EmberCore::BehaviorTree>> treeMap;
304 m_navigatorTab->UpdateTreeList(treeMap, m_currentTreeId);
305 m_navigatorTab->SetMonitorTree(adapter, m_stateManager, m_currentTreeId);
306
307 m_propertiesTab->SetStateManager(m_stateManager);
308
309 auto ackFrame = m_codec.BuildTreeInitAck(m_currentTreeId, true, result.node_count);
310 m_server.Send(ackFrame);
311
314 m_connectionPanel->UpdateStatus();
316 LOG_INFO("Monitor", "Tree visualization set up. State=TreeReady");
317}
318
320 if (!m_stateManager)
321 return;
322
324 return;
325
327 m_stateManager->ApplyTickUpdate(tickUpdate);
328
329 m_stateChanged = true;
330}
331
333 if (!m_stateManager)
334 return;
335
337 frame.payload.size()))
338 return;
339
341 m_stateManager->ApplyBlackboardUpdate(bbUpdate);
342}
343
345 if (!m_stateManager)
346 return;
347
348 m_stateManager->Reset();
349 m_stateChanged = true;
350}
351
352void MonitorFrame::HandleDisconnect(const Network::MessageFrame &frame) { m_server.DisconnectClient(); }
353
356 return;
357 m_inNodeSelection = true;
358
359 m_propertiesTab->SetSelectedNode(node);
360 m_propertiesTab->Refresh();
361
362 if (node) {
363 m_navigatorTab->SelectNodeById(node->GetId());
364 }
365
366 m_inNodeSelection = false;
367}
368
370 m_navigatorTab->RefreshStatus();
371 m_propertiesTab->Refresh();
372}
373
375 if (m_server.IsRunning()) {
376 m_statusBar->SetStatusText(wxString::Format("Listening on port %d", m_server.GetPort()), 0);
377 } else {
378 m_statusBar->SetStatusText("Server stopped", 0);
379 }
380
381 switch (m_connectionState) {
383 m_statusBar->SetStatusText("Handshaking...", 1);
384 break;
386 m_statusBar->SetStatusText("Receiving tree...", 1);
387 break;
389 if (m_currentTree) {
390 m_statusBar->SetStatusText(wxString::Format("Tree: %s (%lu nodes)", m_currentTree->GetName(),
391 static_cast<unsigned long>(m_currentTree->GetNodeCount())),
392 1);
393 } else {
394 m_statusBar->SetStatusText("Connected", 1);
395 }
396 break;
397 default:
398 m_statusBar->SetStatusText("No client", 1);
399 break;
400 }
401
402 if (m_stateManager) {
403 m_statusBar->SetStatusText(
404 wxString::Format("Tick: %lld", static_cast<long long>(m_stateManager->GetCurrentTick())), 2);
406 m_statusBar->SetStatusText("Building tree...", 2);
407 } else {
408 m_statusBar->SetStatusText("No tree", 2);
409 }
410
411 int tick = m_stateManager ? static_cast<int>(m_stateManager->GetCurrentTick()) : 0;
412 m_mainPanel->GetTreeCanvas()->SetConnectionInfo(m_connectionState == ConnectionState::TreeReady, tick);
413}
414
415} // namespace Monitor
416} // namespace Ember
BehaviorTreeProjectDialog::OnProjectNameChanged BehaviorTreeProjectDialog::OnRemoveFiles wxEND_EVENT_TABLE() BehaviorTreeProjectDialog
LoadingDialog::OnCancel EVT_CLOSE(LoadingDialog::OnClose) wxEND_EVENT_TABLE() LoadingDialog
#define LOG_ERROR(category, message)
Definition Logger.h:116
#define LOG_TRACE(category, message)
Definition Logger.h:113
#define LOG_WARNING(category, message)
Definition Logger.h:115
#define LOG_INFO(category, message)
Definition Logger.h:114
MainFrame::OnExit EVT_MENU(wxID_ABOUT, MainFrame::OnAbout) EVT_MENU(ID_NewProject
Abstract interface for tree nodes that can be visualized.
Definition ITreeNode.h:31
virtual size_t GetId() const =0
UI panel for managing TCP server connection, showing server/client status, port input,...
Central panel containing the MonitorTreeCanvas and search bar.
Definition MainPanel.h:19
Main application frame owning the TCP server, tree builder, codec, state manager, and AUI layout (Con...
void OnClientConnect(const std::string &address)
void OnAbout(wxCommandEvent &event)
PropertiesTab * m_propertiesTab
void OnPollTimer(wxTimerEvent &event)
std::shared_ptr< Network::StateManager > m_stateManager
void OnQuit(wxCommandEvent &event)
void OnServerMessage(const Network::MessageFrame &frame)
void HandleTreeInit(const Network::MessageFrame &frame)
MonitorNavigatorTab * m_navigatorTab
Network::MessageFrame m_pendingTickUpdate
RightSidePanel * m_rightPanel
void HandleTickUpdate(const Network::MessageFrame &frame)
void HandleDisconnect(const Network::MessageFrame &frame)
void HandleHandshake(const Network::MessageFrame &frame)
std::shared_ptr< EmberCore::BehaviorTree > m_currentTree
void HandleTreeReset(const Network::MessageFrame &frame)
Network::TreeBuilder m_treeBuilder
void OnNodeSelected(EmberCore::ITreeNode *node)
void OnClose(wxCloseEvent &event)
EmberUI::ProportionalLayout m_layout
ConnectionState m_connectionState
Network::FlatBufferCodec m_codec
void HandleBlackboardUpdate(const Network::MessageFrame &frame)
~MonitorFrame()
Destroys the frame and releases resources.
void OnFrameResize(wxSizeEvent &event)
ConnectionPanel * m_connectionPanel
Right-side tabbed panel containing navigator and properties tabs.
static bool Verify(const uint8_t *data, size_t size)
static const T * GetRoot(const uint8_t *data)
int Scale(wxWindow *win, int px)
Scales a pixel value from logical to physical units for the given window.
Definition DPI.h:11
constexpr int STATUS_BAR_FIELD_WIDTH
Width of status bar fields.
wxBEGIN_EVENT_TABLE(MonitorFrame, wxFrame) EVT_CLOSE(MonitorFrame
Protocol::MessageType messageType
std::vector< uint8_t > payload