From 667ff17fdb2a224a842f4136d58ed2715e832de4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 25 Jun 2025 17:20:36 -0500 Subject: [PATCH 1/7] Make NodeDB sort its internal vector when lastheard is updated. Don't sort in NodeListRenderer --- src/graphics/draw/NodeListRenderer.cpp | 40 +++----------------------- src/graphics/draw/NodeListRenderer.h | 7 ----- src/mesh/NodeDB.cpp | 20 +++++++++++++ src/mesh/NodeDB.h | 1 + 4 files changed, 25 insertions(+), 43 deletions(-) diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 98d9996a5..bec26be3e 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -113,35 +113,6 @@ int calculateMaxScroll(int totalEntries, int visibleRows) return std::max(0, (totalEntries - 1) / (visibleRows * 2)); } -void retrieveAndSortNodes(std::vector &nodeList) -{ - size_t numNodes = nodeDB->getNumMeshNodes(); - for (size_t i = 0; i < numNodes; i++) { - meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); - if (!node || node->num == nodeDB->getNodeNum()) - continue; - - NodeEntry entry; - entry.node = node; - entry.sortValue = sinceLastSeen(node); - - nodeList.push_back(entry); - } - - // Sort nodes: favorites first, then by last heard (most recent first) - std::sort(nodeList.begin(), nodeList.end(), [](const NodeEntry &a, const NodeEntry &b) { - bool aFav = a.node->is_favorite; - bool bFav = b.node->is_favorite; - if (aFav != bFav) - return aFav; - if (a.sortValue == 0 || a.sortValue == UINT32_MAX) - return false; - if (b.sortValue == 0 || b.sortValue == UINT32_MAX) - return true; - return a.sortValue < b.sortValue; - }); -} - void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd) { int columnWidth = display->getWidth() / 2; @@ -440,17 +411,14 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t // Space below header y += COMMON_HEADER_HEIGHT; - // Fetch and display sorted node list - std::vector nodeList; - retrieveAndSortNodes(nodeList); - - int totalEntries = nodeList.size(); + int totalEntries = nodeDB->getNumMeshNodes(); int totalRowsAvailable = (display->getHeight() - y) / rowYOffset; int visibleNodeRows = totalRowsAvailable; int totalColumns = 2; int startIndex = scrollIndex * visibleNodeRows * totalColumns; + startIndex++; // skip own node int endIndex = std::min(startIndex + visibleNodeRows * totalColumns, totalEntries); int yOffset = 0; @@ -462,10 +430,10 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t for (int i = startIndex; i < endIndex; ++i) { int xPos = x + (col * columnWidth); int yPos = y + yOffset; - renderer(display, nodeList[i].node, xPos, yPos, columnWidth); + renderer(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth); if (extras) { - extras(display, nodeList[i].node, xPos, yPos, columnWidth, heading, lat, lon); + extras(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth, heading, lat, lon); } lastNodeY = std::max(lastNodeY, yPos + FONT_HEIGHT_SMALL); diff --git a/src/graphics/draw/NodeListRenderer.h b/src/graphics/draw/NodeListRenderer.h index 63f0d1c69..ea8df8bd9 100644 --- a/src/graphics/draw/NodeListRenderer.h +++ b/src/graphics/draw/NodeListRenderer.h @@ -23,12 +23,6 @@ namespace NodeListRenderer typedef void (*EntryRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int); typedef void (*NodeExtrasRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int, float, double, double); -// Node entry structure -struct NodeEntry { - meshtastic_NodeInfoLite *node; - uint32_t sortValue; -}; - // Node list mode enumeration enum NodeListMode { MODE_LAST_HEARD = 0, MODE_HOP_SIGNAL = 1, MODE_DISTANCE = 2, MODE_COUNT = 3 }; @@ -57,7 +51,6 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, // Utility functions const char *getCurrentModeTitle(int screenWidth); -void retrieveAndSortNodes(std::vector &nodeList); const char *getSafeNodeName(meshtastic_NodeInfoLite *node); void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index d13864bd9..a30a54e99 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1337,6 +1337,24 @@ bool NodeDB::saveNodeDatabaseToDisk() return saveProto(nodeDatabaseFileName, nodeDatabaseSize, &meshtastic_NodeDatabase_msg, &nodeDatabase, false); } +void NodeDB::sortMeshDB() +{ + std::sort(meshNodes->begin(), meshNodes->end(), [](const meshtastic_NodeInfoLite a, const meshtastic_NodeInfoLite b) { + if (a.num == myNodeInfo.my_node_num) { + return true; + } + bool aFav = a.is_favorite; + bool bFav = b.is_favorite; + if (aFav != bFav) + return aFav; + if (a.last_heard == 0 || a.last_heard == UINT32_MAX) + return false; + if (b.last_heard == 0 || b.last_heard == UINT32_MAX) + return true; + return a.last_heard > b.last_heard; + }); +} + bool NodeDB::saveToDiskNoRetry(int saveWhat) { bool success = true; @@ -1558,6 +1576,7 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact) // Mark the node's key as manually verified to indicate trustworthiness. updateGUIforNode = info; // powerFSM.trigger(EVENT_NODEDB_UPDATED); This event has been retired + sortMeshDB(); notifyObservers(true); // Force an update whether or not our node counts have changed } saveNodeDatabaseToDisk(); @@ -1661,6 +1680,7 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) info->has_hops_away = true; info->hops_away = mp.hop_start - mp.hop_limit; } + sortMeshDB(); } } diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 90ca5aefd..4d4fb883d 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -310,6 +310,7 @@ class NodeDB bool saveChannelsToDisk(); bool saveDeviceStateToDisk(); bool saveNodeDatabaseToDisk(); + void sortMeshDB(); }; extern NodeDB *nodeDB; From 8fb1e0f874e7eb5e452db58c20119be05114980a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 25 Jun 2025 18:06:52 -0500 Subject: [PATCH 2/7] Update src/graphics/draw/NodeListRenderer.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/graphics/draw/NodeListRenderer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index bec26be3e..00bedeb7a 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -418,7 +418,9 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t int totalColumns = 2; int startIndex = scrollIndex * visibleNodeRows * totalColumns; - startIndex++; // skip own node + if (scrollIndex == 0) { + startIndex++; // skip own node + } int endIndex = std::min(startIndex + visibleNodeRows * totalColumns, totalEntries); int yOffset = 0; From b1e3353ceb73ebc29a3a4f81341acfb3910d8626 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 25 Jun 2025 18:07:02 -0500 Subject: [PATCH 3/7] Update src/mesh/NodeDB.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/mesh/NodeDB.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index a30a54e99..b839b0aba 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1343,6 +1343,9 @@ void NodeDB::sortMeshDB() if (a.num == myNodeInfo.my_node_num) { return true; } + if (b.num == myNodeInfo.my_node_num) { + return false; + } bool aFav = a.is_favorite; bool bFav = b.is_favorite; if (aFav != bFav) From 18098fb1cb71a24a087aecbe5b02e19698018e4e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 25 Jun 2025 18:08:24 -0500 Subject: [PATCH 4/7] Pass by reference -- Thanks Copilot! --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index b839b0aba..470c11cbd 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1339,7 +1339,7 @@ bool NodeDB::saveNodeDatabaseToDisk() void NodeDB::sortMeshDB() { - std::sort(meshNodes->begin(), meshNodes->end(), [](const meshtastic_NodeInfoLite a, const meshtastic_NodeInfoLite b) { + std::sort(meshNodes->begin(), meshNodes->end(), [](const meshtastic_NodeInfoLite &a, const meshtastic_NodeInfoLite &b) { if (a.num == myNodeInfo.my_node_num) { return true; } From e1b1e35a27373deec46286df4e81ef06b9656e8e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 25 Jun 2025 19:43:36 -0500 Subject: [PATCH 5/7] Throttle sorting just a touch --- src/mesh/NodeDB.cpp | 37 ++++++++++++++++++++----------------- src/mesh/NodeDB.h | 1 + 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 470c11cbd..f68f95f83 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1339,23 +1339,26 @@ bool NodeDB::saveNodeDatabaseToDisk() void NodeDB::sortMeshDB() { - std::sort(meshNodes->begin(), meshNodes->end(), [](const meshtastic_NodeInfoLite &a, const meshtastic_NodeInfoLite &b) { - if (a.num == myNodeInfo.my_node_num) { - return true; - } - if (b.num == myNodeInfo.my_node_num) { - return false; - } - bool aFav = a.is_favorite; - bool bFav = b.is_favorite; - if (aFav != bFav) - return aFav; - if (a.last_heard == 0 || a.last_heard == UINT32_MAX) - return false; - if (b.last_heard == 0 || b.last_heard == UINT32_MAX) - return true; - return a.last_heard > b.last_heard; - }); + if (!Throttle::isWithinTimespanMs(lastSort, 1000 * 5)) { + lastSort = millis(); + std::sort(meshNodes->begin(), meshNodes->end(), [](const meshtastic_NodeInfoLite &a, const meshtastic_NodeInfoLite &b) { + if (a.num == myNodeInfo.my_node_num) { + return true; + } + if (b.num == myNodeInfo.my_node_num) { + return false; + } + bool aFav = a.is_favorite; + bool bFav = b.is_favorite; + if (aFav != bFav) + return aFav; + if (a.last_heard == 0 || a.last_heard == UINT32_MAX) + return false; + if (b.last_heard == 0 || b.last_heard == UINT32_MAX) + return true; + return a.last_heard > b.last_heard; + }); + } } bool NodeDB::saveToDiskNoRetry(int saveWhat) diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 4d4fb883d..b6e4d600b 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -282,6 +282,7 @@ class NodeDB bool duplicateWarned = false; uint32_t lastNodeDbSave = 0; // when we last saved our db to flash uint32_t lastBackupAttempt = 0; // when we last tried a backup automatically or manually + uint32_t lastSort = 0; // When last sorted the nodeDB /// Find a node in our DB, create an empty NodeInfoLite if missing meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n); From 3dd77ace859f66f58b45ee97d5c3e1ae92397188 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 25 Jun 2025 19:43:50 -0500 Subject: [PATCH 6/7] Check more carefully for own node --- src/graphics/draw/NodeListRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 00bedeb7a..1aded74ac 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -418,7 +418,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t int totalColumns = 2; int startIndex = scrollIndex * visibleNodeRows * totalColumns; - if (scrollIndex == 0) { + if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) { startIndex++; // skip own node } int endIndex = std::min(startIndex + visibleNodeRows * totalColumns, totalEntries); From dbc67973c6ea43f7d5bd911e9b3e7b7a77498af5 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 25 Jun 2025 19:44:31 -0500 Subject: [PATCH 7/7] Eliminate some now-unneeded sorting --- src/modules/CannedMessageModule.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index c00c84049..dd37f0dec 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -245,12 +245,15 @@ void CannedMessageModule::updateDestinationSelectionList() } } + /* As the nodeDB is sorted, can skip this step // Sort by favorite, then last heard std::sort(this->filteredNodes.begin(), this->filteredNodes.end(), [](const NodeEntry &a, const NodeEntry &b) { if (a.node->is_favorite != b.node->is_favorite) return a.node->is_favorite > b.node->is_favorite; return a.lastHeard < b.lastHeard; }); + */ + scrollIndex = 0; // Show first result at the top destIndex = 0; // Highlight the first entry if (nodesChanged && runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) {