More optimization

This commit is contained in:
HarukiToreda
2025-10-15 01:57:51 -04:00
parent 67c24c08cc
commit 62eaabc940
6 changed files with 166 additions and 59 deletions

View File

@@ -9,6 +9,48 @@
#include "graphics/draw/MessageRenderer.h"
#include <cstring> // memcpy
#ifndef MESSAGE_TEXT_POOL_SIZE
#define MESSAGE_TEXT_POOL_SIZE (MAX_MESSAGES_SAVED * MAX_MESSAGE_SIZE)
#endif
// Global message text pool and state
static char g_messagePool[MESSAGE_TEXT_POOL_SIZE];
static size_t g_poolWritePos = 0;
// Reset pool (called on boot or clear)
static inline void resetMessagePool()
{
g_poolWritePos = 0;
memset(g_messagePool, 0, sizeof(g_messagePool));
}
// Allocate text in pool and return offset
// If not enough space remains, wrap around (ring buffer style)
static inline uint16_t storeTextInPool(const char *src, size_t len)
{
if (len >= MAX_MESSAGE_SIZE)
len = MAX_MESSAGE_SIZE - 1;
// Wrap pool if out of space
if (g_poolWritePos + len + 1 >= MESSAGE_TEXT_POOL_SIZE) {
g_poolWritePos = 0;
}
uint16_t offset = g_poolWritePos;
memcpy(&g_messagePool[g_poolWritePos], src, len);
g_messagePool[g_poolWritePos + len] = '\0';
g_poolWritePos += (len + 1);
return offset;
}
// Retrieve a const pointer to message text by offset
static inline const char *getTextFromPool(uint16_t offset)
{
if (offset >= MESSAGE_TEXT_POOL_SIZE)
return "";
return &g_messagePool[offset];
}
// Helper: assign a timestamp (RTC if available, else boot-relative)
static inline void assignTimestamp(StoredMessage &sm)
{
@@ -40,6 +82,7 @@ template <typename T> static inline void pushWithLimit(std::deque<T> &queue, T &
MessageStore::MessageStore(const std::string &label)
{
filename = "/Messages_" + label + ".msgs";
resetMessagePool(); // initialize text pool on boot
}
// Live message handling (RAM only)
@@ -56,24 +99,42 @@ void MessageStore::addLiveMessage(const StoredMessage &msg)
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
{
StoredMessage sm;
assignTimestamp(sm); // set timestamp (RTC or boot-relative)
assignTimestamp(sm);
sm.channelIndex = packet.channel;
strncpy(sm.text, reinterpret_cast<const char *>(packet.decoded.payload.bytes), MAX_MESSAGE_SIZE - 1);
sm.text[MAX_MESSAGE_SIZE - 1] = '\0';
const char *payload = reinterpret_cast<const char *>(packet.decoded.payload.bytes);
size_t len = strnlen(payload, MAX_MESSAGE_SIZE - 1);
sm.textOffset = storeTextInPool(payload, len);
sm.textLength = len;
uint32_t localNode = nodeDB->getNodeNum();
sm.sender = (packet.from == 0) ? localNode : packet.from;
sm.dest = packet.decoded.dest;
// DM detection: use decoded.dest if valid, otherwise fallback to header 'to'
bool isDM = false;
uint32_t actualDest = sm.dest;
if (actualDest == 0 || actualDest == 0xffffffff) {
actualDest = packet.to;
}
if (actualDest != 0 && actualDest != NODENUM_BROADCAST && actualDest == localNode) {
isDM = true;
}
// Incoming vs outgoing classification
if (packet.from == 0) {
// Outgoing (phone-originated)
sm.sender = nodeDB->getNodeNum();
sm.dest = (packet.decoded.dest == 0) ? NODENUM_BROADCAST : packet.decoded.dest;
sm.type = (sm.dest == NODENUM_BROADCAST) ? MessageType::BROADCAST : MessageType::DM_TO_US;
// Sent by us
sm.type = isDM ? MessageType::DM_TO_US : MessageType::BROADCAST;
sm.ackStatus = AckStatus::NONE;
} else {
// Incoming
sm.sender = packet.from;
sm.dest = packet.decoded.dest;
sm.type = (sm.dest == NODENUM_BROADCAST) ? MessageType::BROADCAST
: (sm.dest == nodeDB->getNodeNum()) ? MessageType::DM_TO_US
: MessageType::BROADCAST;
// Received from another node
if (isDM) {
sm.type = MessageType::DM_TO_US;
} else {
sm.type = MessageType::BROADCAST;
}
sm.ackStatus = AckStatus::ACKED;
}
@@ -91,8 +152,8 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
sm.sender = sender;
sm.channelIndex = channelIndex;
strncpy(sm.text, text.c_str(), MAX_MESSAGE_SIZE - 1);
sm.text[MAX_MESSAGE_SIZE - 1] = '\0';
sm.textOffset = storeTextInPool(text.c_str(), text.size());
sm.textLength = text.size();
// Default manual adds to broadcast
sm.dest = NODENUM_BROADCAST;
@@ -106,18 +167,17 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
#if ENABLE_MESSAGE_PERSISTENCE
// Use a compile-time constant so the array bound can be used in the struct
static constexpr size_t TEXT_LEN = MAX_MESSAGE_SIZE;
// Compact, fixed-size on-flash representation
// Compact, fixed-size on-flash representation using offset + length
struct __attribute__((packed)) StoredMessageRecord {
uint32_t timestamp;
uint32_t sender;
uint8_t channelIndex;
uint32_t dest;
uint8_t isBootRelative;
uint8_t ackStatus; // static_cast<uint8_t>(AckStatus)
char text[TEXT_LEN]; // null-terminated
uint8_t ackStatus; // static_cast<uint8_t>(AckStatus)
uint8_t type; // static_cast<uint8_t>(MessageType)
uint16_t textLength; // message length
char text[MAX_MESSAGE_SIZE]; // <-- store actual text here
};
// Serialize one StoredMessage to flash
@@ -130,9 +190,14 @@ static inline void writeMessageRecord(SafeFile &f, const StoredMessage &m)
rec.dest = m.dest;
rec.isBootRelative = m.isBootRelative;
rec.ackStatus = static_cast<uint8_t>(m.ackStatus);
rec.type = static_cast<uint8_t>(m.type);
rec.textLength = m.textLength;
// Copy the actual text into the record from RAM pool
const char *txt = getTextFromPool(m.textOffset);
strncpy(rec.text, txt, MAX_MESSAGE_SIZE - 1);
rec.text[MAX_MESSAGE_SIZE - 1] = '\0';
strncpy(rec.text, m.text, TEXT_LEN - 1);
rec.text[TEXT_LEN - 1] = '\0';
f.write(reinterpret_cast<const uint8_t *>(&rec), sizeof(rec));
}
@@ -149,9 +214,13 @@ static inline bool readMessageRecord(File &f, StoredMessage &m)
m.dest = rec.dest;
m.isBootRelative = rec.isBootRelative;
m.ackStatus = static_cast<AckStatus>(rec.ackStatus);
strncpy(m.text, rec.text, MAX_MESSAGE_SIZE - 1);
m.text[MAX_MESSAGE_SIZE - 1] = '\0';
m.type = (m.dest == NODENUM_BROADCAST) ? MessageType::BROADCAST : MessageType::DM_TO_US;
m.type = static_cast<MessageType>(rec.type);
m.textLength = rec.textLength;
// 💡 Re-store text into pool and update offset
m.textLength = strnlen(rec.text, MAX_MESSAGE_SIZE - 1);
m.textOffset = storeTextInPool(rec.text, m.textLength);
return true;
}
@@ -183,6 +252,8 @@ void MessageStore::saveToFlash()
void MessageStore::loadFromFlash()
{
liveMessages.clear();
resetMessagePool(); // reset pool when loading
#ifdef FSCom
concurrency::LockGuard guard(spiLock);
@@ -219,6 +290,7 @@ void MessageStore::loadFromFlash() {}
void MessageStore::clearAllMessages()
{
liveMessages.clear();
resetMessagePool();
#ifdef FSCom
SafeFile f(filename.c_str(), false);
@@ -266,7 +338,7 @@ void MessageStore::dismissOldestMessageInChannel(uint8_t channel)
saveToFlash();
}
// Dismiss oldest message in a direct conversation with a peer
// Dismiss oldest message in a direct chat with a node
void MessageStore::dismissOldestMessageWithPeer(uint32_t peer)
{
auto pred = [peer](const StoredMessage &m) {
@@ -279,7 +351,6 @@ void MessageStore::dismissOldestMessageWithPeer(uint32_t peer)
saveToFlash();
}
// Helper filters for future use
std::deque<StoredMessage> MessageStore::getChannelMessages(uint8_t channel) const
{
std::deque<StoredMessage> result;
@@ -320,12 +391,23 @@ void MessageStore::upgradeBootRelativeTimestamps()
m.timestamp += bootOffset;
m.isBootRelative = false;
}
// else: persisted from old boot → stays ??? forever
}
};
fix(liveMessages);
}
const char *MessageStore::getText(const StoredMessage &msg)
{
// Wrapper around the internal helper
return getTextFromPool(msg.textOffset);
}
uint16_t MessageStore::storeText(const char *src, size_t len)
{
// Wrapper around the internal helper
return storeTextInPool(src, len);
}
// Global definition
MessageStore messageStore("default");
#endif

View File

@@ -27,10 +27,16 @@
// Internal alias used everywhere in code do NOT redefine elsewhere.
#define MAX_MESSAGES_SAVED MESSAGE_HISTORY_LIMIT
// Maximum text payload size per message in bytes (fixed).
// All messages use the same size to simplify memory handling and avoid dynamic allocations.
// Maximum text payload size per message in bytes.
// This still defines the max message length, but we no longer reserve this space per message.
#define MAX_MESSAGE_SIZE 220
// Total shared text pool size for all messages combined.
// The text pool is RAM-only. Text is re-stored from flash into the pool on boot.
#ifndef MESSAGE_TEXT_POOL_SIZE
#define MESSAGE_TEXT_POOL_SIZE (MAX_MESSAGES_SAVED * MAX_MESSAGE_SIZE)
#endif
// Explicit message classification
enum class MessageType : uint8_t {
BROADCAST = 0, // broadcast message
@@ -46,25 +52,23 @@ enum class AckStatus : uint8_t {
RELAYED = 4 // got an ACK from relay, not destination
};
// A single stored message in RAM and/or flash
struct StoredMessage {
uint32_t timestamp; // When message was created (secs since boot or RTC)
uint32_t sender; // NodeNum of sender
uint8_t channelIndex; // Channel index used
char text[MAX_MESSAGE_SIZE]; // Fixed-size buffer for message text (null-terminated)
uint32_t timestamp; // When message was created (secs since boot or RTC)
uint32_t sender; // NodeNum of sender
uint8_t channelIndex; // Channel index used
uint32_t dest; // Destination node (broadcast or direct)
MessageType type; // Derived from dest (explicit classification)
bool isBootRelative; // true = millis()/1000 fallback; false = epoch/RTC absolute
AckStatus ackStatus; // Delivery status (only meaningful for our own sent messages)
uint32_t dest; // Destination node (broadcast or direct)
MessageType type; // Derived from dest (explicit classification)
bool isBootRelative; // true = millis()/1000 fallback; false = epoch/RTC absolute
AckStatus ackStatus; // Delivery status (only meaningful for our own sent messages)
// Text storage metadata — rebuilt from flash at boot
uint16_t textOffset; // Offset into global text pool (valid only after loadFromFlash())
uint16_t textLength; // Length of text in bytes
// Default constructor initializes all fields safely
StoredMessage()
: timestamp(0), sender(0), channelIndex(0), text(""), dest(0xffffffff), type(MessageType::BROADCAST),
isBootRelative(false), ackStatus(AckStatus::NONE) // start as NONE (waiting, no symbol)
: timestamp(0), sender(0), channelIndex(0), dest(0xffffffff), type(MessageType::BROADCAST), isBootRelative(false),
ackStatus(AckStatus::NONE), textOffset(0), textLength(0)
{
}
};
@@ -87,7 +91,7 @@ class MessageStore
void saveToFlash(); // Save messages to flash
void loadFromFlash(); // Load messages from flash
// Clear all messages (RAM + persisted queue)
// Clear all messages (RAM + persisted queue + text pool)
void clearAllMessages();
// Dismiss helpers
@@ -107,6 +111,15 @@ class MessageStore
// Upgrade boot-relative timestamps once RTC is valid
void upgradeBootRelativeTimestamps();
// Retrieve the C-string text for a stored message
static const char *getText(const StoredMessage &msg);
// Allocate text into pool (used by sender-side code)
static uint16_t storeText(const char *src, size_t len);
// Used when loading from flash to rebuild the text pool
static uint16_t rebuildTextFromFlash(const char *src, size_t len);
private:
std::deque<StoredMessage> liveMessages; // Single in-RAM message buffer (also used for persistence)
std::string filename; // Flash filename for persistence

View File

@@ -481,9 +481,6 @@ void menuHandler::messageResponseMenu()
} else if (selected == DismissAll) {
messageStore.clearAllMessages();
graphics::MessageRenderer::clearThreadRegistries();
// Reset back to "View All"
graphics::MessageRenderer::setThreadMode(graphics::MessageRenderer::ThreadMode::ALL);
} else if (selected == DismissOldest) {
auto mode = graphics::MessageRenderer::getThreadMode();
int ch = graphics::MessageRenderer::getThreadChannel();

View File

@@ -416,6 +416,14 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
}
if (filtered.empty()) {
// If current conversation is empty go back to ALL view
if (currentMode != ThreadMode::ALL) {
setThreadMode(ThreadMode::ALL);
resetScrollState();
return; // Next draw will rerun in ALL mode
}
// Still in ALL mode and no messages at all → show placeholder
graphics::drawCommonHeader(display, x, y, titleStr);
didReset = false;
const char *messageString = "No messages";
@@ -528,8 +536,9 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
isHeader.push_back(true);
ackForLine.push_back(m.ackStatus);
// Split message text into wrapped lines
std::vector<std::string> wrapped = generateLines(display, "", m.text, textWidth);
const char *msgText = MessageStore::getText(m);
std::vector<std::string> wrapped = generateLines(display, "", msgText, textWidth);
for (auto &ln : wrapped) {
allLines.push_back(ln);
isMine.push_back(mine);
@@ -880,8 +889,11 @@ void handleNewMessage(const StoredMessage &sm, const meshtastic_MeshPacket &pack
screen->showSimpleBanner(banner, inThread ? 1000 : 3000);
}
// Always focus into the correct conversation thread when a message arrives
setThreadFor(sm, packet);
// Always focus into the correct conversation thread when a message with real text arrives
const char *msgText = MessageStore::getText(sm);
if (msgText && msgText[0] != '\0') {
setThreadFor(sm, packet);
}
// Reset scroll for a clean start
resetScrollState();
@@ -889,10 +901,12 @@ void handleNewMessage(const StoredMessage &sm, const meshtastic_MeshPacket &pack
void setThreadFor(const StoredMessage &sm, const meshtastic_MeshPacket &packet)
{
if (sm.type == MessageType::BROADCAST) {
if (sm.dest == NODENUM_BROADCAST || sm.type == MessageType::BROADCAST) {
// Broadcast
setThreadMode(ThreadMode::CHANNEL, sm.channelIndex);
} else if (sm.type == MessageType::DM_TO_US) {
uint32_t peer = (packet.from == 0) ? sm.dest : sm.sender;
} else {
// Direct message
uint32_t peer = (packet.from != 0) ? sm.sender : sm.dest;
setThreadMode(ThreadMode::DIRECT, -1, peer);
}
}

View File

@@ -28,7 +28,7 @@ int getThreadChannel();
// Getter for current peer (valid if mode == DIRECT)
uint32_t getThreadPeer();
// --- Registry accessors for menuHandler ---
// Registry accessors for menuHandler
const std::vector<int> &getSeenChannels();
const std::vector<uint32_t> &getSeenPeers();

View File

@@ -1062,8 +1062,9 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
sm.sender = nodeDB->getNodeNum(); // us
sm.channelIndex = channel;
strncpy(sm.text, message, MAX_MESSAGE_SIZE - 1);
sm.text[MAX_MESSAGE_SIZE - 1] = '\0';
size_t len = strnlen(message, MAX_MESSAGE_SIZE - 1);
sm.textOffset = MessageStore::storeText(message, len);
sm.textLength = len;
// Classify broadcast vs DM
if (dest == NODENUM_BROADCAST) {