From 50a65a139302ec95f639703c5f3fa782beed9c6d Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Sun, 12 Oct 2025 16:24:40 -0400 Subject: [PATCH] cleanup to get more space --- src/MessageStore.cpp | 148 ++++++++++++-------------- src/MessageStore.h | 10 +- src/graphics/draw/MessageRenderer.cpp | 81 +++++--------- src/modules/CannedMessageModule.cpp | 5 +- 4 files changed, 105 insertions(+), 139 deletions(-) diff --git a/src/MessageStore.cpp b/src/MessageStore.cpp index b9f74a1c2..308a4791b 100644 --- a/src/MessageStore.cpp +++ b/src/MessageStore.cpp @@ -11,10 +11,23 @@ using graphics::MessageRenderer::setThreadMode; using graphics::MessageRenderer::ThreadMode; -static size_t getMessageSize(const StoredMessage &m) +// Calculate serialized size for a StoredMessage +static inline size_t getMessageSize(const StoredMessage &m) { // serialized size = fixed 16 bytes + text length (capped at MAX_MESSAGE_SIZE) - return 16 + std::min(static_cast(MAX_MESSAGE_SIZE), m.text.size()); + return 16 + std::min(MAX_MESSAGE_SIZE, strnlen(m.text, MAX_MESSAGE_SIZE)); +} + +static inline void assignTimestamp(StoredMessage &sm) +{ + uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true); + if (nowSecs) { + sm.timestamp = nowSecs; + sm.isBootRelative = false; + } else { + sm.timestamp = millis() / 1000; + sm.isBootRelative = true; + } } void MessageStore::logMemoryUsage(const char *context) const @@ -34,38 +47,34 @@ MessageStore::MessageStore(const std::string &label) } // Live message handling (RAM only) -void MessageStore::addLiveMessage(const StoredMessage &msg) +void MessageStore::addLiveMessage(StoredMessage &&msg) { if (liveMessages.size() >= MAX_MESSAGES_SAVED) { liveMessages.pop_front(); // keep only most recent N } - liveMessages.push_back(msg); + // Use emplace_back with std::move to avoid extra copy + liveMessages.emplace_back(std::move(msg)); } // Persistence queue (used only on shutdown/reboot) -void MessageStore::addMessage(const StoredMessage &msg) +void MessageStore::addMessage(StoredMessage &&msg) { if (messages.size() >= MAX_MESSAGES_SAVED) { messages.pop_front(); } - messages.push_back(msg); + // Use emplace_back with std::move to avoid extra copy + messages.emplace_back(std::move(msg)); } + const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet) { StoredMessage sm; - - // Always use our local time, ignore packet.rx_time - uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true); - if (nowSecs > 0) { - sm.timestamp = nowSecs; - sm.isBootRelative = false; - } else { - sm.timestamp = millis() / 1000; - sm.isBootRelative = true; // mark for later upgrade - } + // Always use our local time (helper handles RTC vs boot time) + assignTimestamp(sm); sm.channelIndex = packet.channel; - sm.text = std::string(reinterpret_cast(packet.decoded.payload.bytes)); + strncpy(sm.text, reinterpret_cast(packet.decoded.payload.bytes), MAX_MESSAGE_SIZE - 1); + sm.text[MAX_MESSAGE_SIZE - 1] = '\0'; if (packet.from == 0) { // Phone-originated (outgoing) @@ -98,7 +107,7 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa sm.ackStatus = AckStatus::ACKED; } - addLiveMessage(sm); + addLiveMessage(std::move(sm)); // Return reference to the most recently stored message return liveMessages.back(); @@ -109,19 +118,13 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st { StoredMessage sm; - // Always use our local time - uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true); - if (nowSecs > 0) { - sm.timestamp = nowSecs; - sm.isBootRelative = false; - } else { - sm.timestamp = millis() / 1000; - sm.isBootRelative = true; // mark for later upgrade - } + // Always use our local time (helper handles RTC vs boot time) + assignTimestamp(sm); sm.sender = sender; sm.channelIndex = channelIndex; - sm.text = text; + strncpy(sm.text, text.c_str(), MAX_MESSAGE_SIZE - 1); + sm.text[MAX_MESSAGE_SIZE - 1] = '\0'; // Default manual adds to broadcast sm.dest = NODENUM_BROADCAST; @@ -130,7 +133,7 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st // Outgoing messages start as NONE until ACK/NACK arrives sm.ackStatus = AckStatus::NONE; - addLiveMessage(sm); + addLiveMessage(std::move(sm)); } #if ENABLE_MESSAGE_PERSISTENCE @@ -157,7 +160,7 @@ void MessageStore::saveToFlash() f.write((uint8_t *)&m.sender, sizeof(m.sender)); f.write((uint8_t *)&m.channelIndex, sizeof(m.channelIndex)); f.write((uint8_t *)&m.dest, sizeof(m.dest)); - f.write((uint8_t *)m.text.c_str(), std::min(static_cast(MAX_MESSAGE_SIZE), m.text.size())); + f.write((uint8_t *)m.text, strnlen(m.text, MAX_MESSAGE_SIZE)); f.write('\0'); // null terminator uint8_t bootFlag = m.isBootRelative ? 1 : 0; @@ -200,15 +203,8 @@ void MessageStore::loadFromFlash() f.readBytes((char *)&m.sender, sizeof(m.sender)); f.readBytes((char *)&m.channelIndex, sizeof(m.channelIndex)); f.readBytes((char *)&m.dest, sizeof(m.dest)); - - char c; - while (m.text.size() < MAX_MESSAGE_SIZE) { - if (f.readBytes(&c, 1) <= 0) - break; - if (c == '\0') - break; - m.text.push_back(c); - } + f.readBytes(m.text, MAX_MESSAGE_SIZE - 1); + m.text[MAX_MESSAGE_SIZE - 1] = '\0'; // Try to read boot-relative flag (new format) uint8_t bootFlag = 0; @@ -272,75 +268,65 @@ void MessageStore::clearAllMessages() #endif } +// Internal helper: erase first or last message matching a predicate +template +static void eraseIf(std::deque &deque, Predicate pred, bool fromBack = false) +{ + if (fromBack) { + // Iterate from the back and erase the first match from the end + for (auto it = deque.rbegin(); it != deque.rend(); ++it) { + if (pred(*it)) { + deque.erase(std::next(it).base()); + break; + } + } + } else { + // Erase the first matching message from the front + auto it = std::find_if(deque.begin(), deque.end(), pred); + if (it != deque.end()) + deque.erase(it); + } +} + // Dismiss oldest message (RAM + persisted queue) void MessageStore::dismissOldestMessage() { - if (!liveMessages.empty()) { - liveMessages.pop_front(); - } - if (!messages.empty()) { - messages.pop_front(); - } + eraseIf(liveMessages, [](StoredMessage &) { return true; }); + eraseIf(messages, [](StoredMessage &) { return true; }); saveToFlash(); } // Dismiss oldest message in a specific channel void MessageStore::dismissOldestMessageInChannel(uint8_t channel) { - auto it = std::find_if(liveMessages.begin(), liveMessages.end(), [channel](const StoredMessage &m) { + auto pred = [channel](const StoredMessage &m) { return m.type == MessageType::BROADCAST && m.channelIndex == channel; - }); - if (it != liveMessages.end()) { - liveMessages.erase(it); - } - - auto it2 = std::find_if(messages.begin(), messages.end(), [channel](const StoredMessage &m) { - return m.type == MessageType::BROADCAST && m.channelIndex == channel; - }); - if (it2 != messages.end()) { - messages.erase(it2); - } - + }; + eraseIf(liveMessages, pred); + eraseIf(messages, pred); saveToFlash(); } // Dismiss oldest message in a direct conversation with a peer void MessageStore::dismissOldestMessageWithPeer(uint32_t peer) { - auto it = std::find_if(liveMessages.begin(), liveMessages.end(), [peer](const StoredMessage &m) { + auto pred = [peer](const StoredMessage &m) { if (m.type == MessageType::DM_TO_US) { uint32_t other = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender; return other == peer; } return false; - }); - if (it != liveMessages.end()) { - liveMessages.erase(it); - } - - auto it2 = std::find_if(messages.begin(), messages.end(), [peer](const StoredMessage &m) { - if (m.type == MessageType::DM_TO_US) { - uint32_t other = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender; - return other == peer; - } - return false; - }); - if (it2 != messages.end()) { - messages.erase(it2); - } - + }; + eraseIf(liveMessages, pred); + eraseIf(messages, pred); saveToFlash(); } // Dismiss newest message (RAM + persisted queue) void MessageStore::dismissNewestMessage() { - if (!liveMessages.empty()) { - liveMessages.pop_back(); - } - if (!messages.empty()) { - messages.pop_back(); - } + eraseIf(liveMessages, [](StoredMessage &) { return true; }, true); + eraseIf(messages, [](StoredMessage &) { return true; }, true); saveToFlash(); } diff --git a/src/MessageStore.h b/src/MessageStore.h index 06eb62139..2b2f50203 100644 --- a/src/MessageStore.h +++ b/src/MessageStore.h @@ -18,7 +18,9 @@ #include // Max number of messages we’ll keep in history -constexpr size_t MAX_MESSAGES_SAVED = 20; +#ifndef MAX_MESSAGES_SAVED +#define MAX_MESSAGES_SAVED 20 +#endif constexpr size_t MAX_MESSAGE_SIZE = 220; // safe bound for text payload // Explicit message classification @@ -37,7 +39,7 @@ struct StoredMessage { uint32_t timestamp; // When message was created (secs since boot/RTC) uint32_t sender; // NodeNum of sender uint8_t channelIndex; // Channel index used - std::string text; // UTF-8 text payload + char text[MAX_MESSAGE_SIZE]; // UTF-8 text payload // Destination node. // 0xffffffff (NODENUM_BROADCAST) means broadcast, @@ -68,11 +70,11 @@ class MessageStore explicit MessageStore(const std::string &label); // Live RAM methods (always current, used by UI and runtime) - void addLiveMessage(const StoredMessage &msg); + void addLiveMessage(StoredMessage &&msg); const std::deque &getLiveMessages() const { return liveMessages; } // Persistence methods (used only on boot/shutdown) - void addMessage(const StoredMessage &msg); + void addMessage(StoredMessage &&msg); const StoredMessage &addFromPacket(const meshtastic_MeshPacket &mp); // Incoming/outgoing → RAM only void addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text); void saveToFlash(); diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 3bb320c77..075bb251e 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -35,6 +35,14 @@ namespace MessageRenderer static std::vector cachedLines; static std::vector cachedHeights; +// UTF-8 skip helper +inline size_t utf8CharLen(uint8_t c) { + if ((c & 0xE0) == 0xC0) return 2; + if ((c & 0xF0) == 0xE0) return 3; + if ((c & 0xF8) == 0xF0) return 4; + return 1; +} + void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount) { int cursorX = x; @@ -43,28 +51,16 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string // Step 1: Find tallest emote in the line int maxIconHeight = fontHeight; for (size_t i = 0; i < line.length();) { - bool matched = false; for (int e = 0; e < emoteCount; ++e) { size_t emojiLen = strlen(emotes[e].label); if (line.compare(i, emojiLen, emotes[e].label) == 0) { if (emotes[e].height > maxIconHeight) maxIconHeight = emotes[e].height; i += emojiLen; - matched = true; break; } } - if (!matched) { - uint8_t c = static_cast(line[i]); - if ((c & 0xE0) == 0xC0) - i += 2; - else if ((c & 0xF0) == 0xE0) - i += 3; - else if ((c & 0xF8) == 0xF0) - i += 4; - else - i += 1; - } + i += utf8CharLen(static_cast(line[i])); } // Step 2: Baseline alignment @@ -233,60 +229,41 @@ const std::vector &getSeenPeers() return seenPeers; } +inline int centerYForRow(int y, int size) { + int midY = y + (FONT_HEIGHT_SMALL / 2); + return midY - (size / 2); +} + // Helpers for drawing status marks (thickened strokes) void drawCheckMark(OLEDDisplay *display, int x, int y, int size = 8) { - int h = size; - int w = size; - - // Center mark vertically with the text row - int midY = y + (FONT_HEIGHT_SMALL / 2); - int topY = midY - (h / 2); - - display->setColor(WHITE); // ensure we use current fg - - // Draw thicker checkmark by overdrawing lines with 1px offset - // arm 1 - display->drawLine(x, topY + h / 2, x + w / 3, topY + h); - display->drawLine(x, topY + h / 2 + 1, x + w / 3, topY + h + 1); - // arm 2 - display->drawLine(x + w / 3, topY + h, x + w, topY); - display->drawLine(x + w / 3, topY + h + 1, x + w, topY + 1); + int topY = centerYForRow(y, size); + display->setColor(WHITE); + display->drawLine(x, topY + size / 2, x + size / 3, topY + size); + display->drawLine(x, topY + size / 2 + 1, x + size / 3, topY + size + 1); + display->drawLine(x + size / 3, topY + size, x + size, topY); + display->drawLine(x + size / 3, topY + size + 1, x + size, topY + 1); } void drawXMark(OLEDDisplay *display, int x, int y, int size = 8) { - int h = size; - int w = size; - - // Center mark vertically with the text row - int midY = y + (FONT_HEIGHT_SMALL / 2); - int topY = midY - (h / 2); - + int topY = centerYForRow(y, size); display->setColor(WHITE); - - // Draw thicker X with 1px offset - display->drawLine(x, topY, x + w, topY + h); - display->drawLine(x, topY + 1, x + w, topY + h + 1); - display->drawLine(x + w, topY, x, topY + h); - display->drawLine(x + w, topY + 1, x, topY + h + 1); + display->drawLine(x, topY, x + size, topY + size); + display->drawLine(x, topY + 1, x + size, topY + size + 1); + display->drawLine(x + size, topY, x, topY + size); + display->drawLine(x + size, topY + 1, x, topY + size + 1); } void drawRelayMark(OLEDDisplay *display, int x, int y, int size = 8) { int r = size / 2; - int midY = y + (FONT_HEIGHT_SMALL / 2); - int centerY = midY; + int centerY = centerYForRow(y, size) + r; int centerX = x + r; - display->setColor(WHITE); - - // Draw circle outline (relay = uncertain status) display->drawCircle(centerX, centerY, r); - - // Draw "?" inside (approx, 3px wide) - display->drawLine(centerX, centerY - 2, centerX, centerY); // stem - display->setPixel(centerX, centerY + 2); // dot + display->drawLine(centerX, centerY - 2, centerX, centerY); + display->setPixel(centerX, centerY + 2); display->drawLine(centerX - 1, centerY - 4, centerX + 1, centerY - 4); } @@ -478,7 +455,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 ackForLine.push_back(m.ackStatus); // Split message text into wrapped lines - std::vector wrapped = generateLines(display, "", m.text.c_str(), textWidth); + std::vector wrapped = generateLines(display, "", m.text, textWidth); for (auto &ln : wrapped) { allLines.push_back(ln); isMine.push_back(mine); diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 377afb6f9..fb77c9662 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -997,7 +997,8 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha sm.sender = nodeDB->getNodeNum(); // us sm.channelIndex = channel; - sm.text = std::string(message); + strncpy(sm.text, message, MAX_MESSAGE_SIZE - 1); + sm.text[MAX_MESSAGE_SIZE - 1] = '\0'; // Classify broadcast vs DM if (dest == NODENUM_BROADCAST) { @@ -1009,7 +1010,7 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha } sm.ackStatus = AckStatus::NONE; - messageStore.addLiveMessage(sm); + messageStore.addLiveMessage(std::move(sm)); // Auto-switch thread view on outgoing message if (sm.type == MessageType::BROADCAST) {