功能优化
This commit is contained in:
@ -8,14 +8,7 @@
|
||||
// Internal helper to get node pointer from ID
|
||||
MenuNode* menu_core_get_node(MenuCoreCtx* ctx, MenuNodeId id) {
|
||||
if (!ctx || id == 0) return NULL;
|
||||
// Simple linear scan for now.
|
||||
// Optimization: Could use hash table if implemented to return index/pointer
|
||||
for (int i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
|
||||
if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == id) {
|
||||
return &ctx->nodes[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
return menu_hash_find(ctx, id);
|
||||
}
|
||||
|
||||
MenuErrCode menu_core_init(MenuCoreCtx* ctx) {
|
||||
@ -86,15 +79,91 @@ MenuErrCode menu_core_register_node(MenuCoreCtx* ctx, const MenuNode* node, Menu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add to hash
|
||||
menu_hash_insert(ctx, new_node->id);
|
||||
menu_hash_insert(ctx, new_node->id, slot);
|
||||
|
||||
if (out_id) *out_id = new_node->id;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_core_unregister_node(MenuCoreCtx* ctx, MenuNodeId id) {
|
||||
if (!ctx || id == 0) return MENU_ERR_INVALID_PARAM;
|
||||
|
||||
MenuNode* node = menu_core_get_node(ctx, id);
|
||||
if (!node) return MENU_ERR_NOT_FOUND;
|
||||
|
||||
// Find slot index
|
||||
int slot = -1;
|
||||
for (int i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
|
||||
if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == id) {
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot == -1) return MENU_ERR_NOT_FOUND;
|
||||
|
||||
// Update parent-child relationship
|
||||
if (node->parent_id != 0) {
|
||||
MenuNode* parent = menu_core_get_node(ctx, node->parent_id);
|
||||
if (parent) {
|
||||
// Check if this is the first child
|
||||
if (parent->first_child_id == id) {
|
||||
parent->first_child_id = node->next_sibling_id;
|
||||
} else {
|
||||
// Find previous sibling to update links
|
||||
MenuNode* prev_sibling = menu_core_get_node(ctx, node->prev_sibling_id);
|
||||
if (prev_sibling) {
|
||||
prev_sibling->next_sibling_id = node->next_sibling_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update next sibling's prev_sibling_id
|
||||
if (node->next_sibling_id != 0) {
|
||||
MenuNode* next_sibling = menu_core_get_node(ctx, node->next_sibling_id);
|
||||
if (next_sibling) {
|
||||
next_sibling->prev_sibling_id = node->prev_sibling_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear node data
|
||||
ctx->nodes[slot].flags.is_registered = false;
|
||||
ctx->nodes[slot].id = 0;
|
||||
ctx->nodes[slot].parent_id = 0;
|
||||
ctx->nodes[slot].first_child_id = 0;
|
||||
ctx->nodes[slot].next_sibling_id = 0;
|
||||
ctx->nodes[slot].prev_sibling_id = 0;
|
||||
ctx->nodes[slot].hash_next_id = 0;
|
||||
ctx->nodes[slot].flags.is_selected = false;
|
||||
ctx->nodes[slot].flags.is_visible = false;
|
||||
ctx->nodes[slot].flags.is_enabled = false;
|
||||
ctx->nodes[slot].permission_level = 0;
|
||||
ctx->nodes[slot].param_id = 0;
|
||||
ctx->nodes[slot].user_data = NULL;
|
||||
ctx->nodes[slot].name = NULL;
|
||||
ctx->nodes[slot].enter_cb = NULL;
|
||||
ctx->nodes[slot].exit_cb = NULL;
|
||||
ctx->nodes[slot].render_cb = NULL;
|
||||
|
||||
// Update counts
|
||||
ctx->node_count--;
|
||||
ctx->free_node_count++;
|
||||
|
||||
// Rebuild hash table since we can't easily remove from it
|
||||
menu_hash_init(ctx);
|
||||
for (int i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
|
||||
if (ctx->nodes[i].flags.is_registered) {
|
||||
menu_hash_insert(ctx, ctx->nodes[i].id, i);
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_core_enter_node(MenuCoreCtx* ctx, MenuNodeId node_id) {
|
||||
if (!ctx) return MENU_ERR_INVALID_PARAM;
|
||||
|
||||
@ -103,7 +172,16 @@ MenuErrCode menu_core_enter_node(MenuCoreCtx* ctx, MenuNodeId node_id) {
|
||||
|
||||
// Permission check
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
// TODO: Check permission
|
||||
MenuRole* role_node = NULL;
|
||||
for (uint8_t i = 0; i < ctx->permission.role_count; i++) {
|
||||
if (ctx->permission.roles[i].id == ctx->permission.current_role_id) {
|
||||
role_node = &ctx->permission.roles[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!role_node || role_node->level < node->permission_level) {
|
||||
return MENU_ERR_PERMISSION_DENIED;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Call enter callback
|
||||
@ -295,6 +373,46 @@ MenuErrCode menu_core_handle_event(MenuCoreCtx* ctx, const MenuEvent* event) {
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
static uint8_t calculate_checksum(const MenuPersistenceData* data) {
|
||||
uint8_t checksum = 0;
|
||||
const uint8_t* bytes = (const uint8_t*)data;
|
||||
size_t len = sizeof(MenuPersistenceData) - sizeof(data->checksum);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
checksum ^= bytes[i];
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
MenuErrCode menu_core_save_state(MenuCoreCtx* ctx) {
|
||||
if (!ctx) return MENU_ERR_INVALID_PARAM;
|
||||
|
||||
MenuPersistenceData data = {
|
||||
.current_node_id = ctx->current_node_id,
|
||||
.current_state = ctx->current_state,
|
||||
.nav_path = ctx->nav_path,
|
||||
.stack = ctx->stack,
|
||||
.timestamp = 0,
|
||||
.checksum = 0
|
||||
};
|
||||
|
||||
data.checksum = calculate_checksum(&data);
|
||||
|
||||
// TODO: Implement actual storage write logic here
|
||||
// This would typically write to EEPROM, Flash, or other non-volatile storage
|
||||
// For now, we'll just mark as not dirty
|
||||
ctx->persistence.dirty = false;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_core_load_state(MenuCoreCtx* ctx) {
|
||||
if (!ctx) return MENU_ERR_INVALID_PARAM;
|
||||
|
||||
// TODO: Implement actual storage read logic here
|
||||
// This would typically read from EEPROM, Flash, or other non-volatile storage
|
||||
// For now, we'll just return OK
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
void menu_core_loop(MenuCoreCtx* ctx, uint32_t tick) {
|
||||
if (!ctx || !ctx->is_initialized) return;
|
||||
|
||||
@ -303,14 +421,20 @@ void menu_core_loop(MenuCoreCtx* ctx, uint32_t tick) {
|
||||
MenuEvent evt;
|
||||
if (menu_event_get(&ctx->event_queue, &evt) == MENU_ERR_OK) {
|
||||
menu_core_handle_event(ctx, &evt);
|
||||
ctx->persistence.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Render
|
||||
// Optimization: Add a render callback to the context or port
|
||||
// For now we rely on the current node's render_cb
|
||||
MenuNode* current = menu_core_get_node(ctx, ctx->current_node_id);
|
||||
if (current && current->render_cb) {
|
||||
current->render_cb(current->id, ctx);
|
||||
}
|
||||
|
||||
// Auto-save if dirty
|
||||
#if MENU_CONFIG_ENABLE_PERSISTENCE
|
||||
if (ctx->persistence.dirty) {
|
||||
menu_core_save_state(ctx);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ MenuErrCode menu_core_init(MenuCoreCtx* ctx);
|
||||
// Node management
|
||||
MenuErrCode menu_core_register_node(MenuCoreCtx* ctx, const MenuNode* node, MenuNodeId* out_id);
|
||||
MenuNode* menu_core_get_node(MenuCoreCtx* ctx, MenuNodeId id);
|
||||
MenuErrCode menu_core_unregister_node(MenuCoreCtx* ctx, MenuNodeId id);
|
||||
|
||||
// Main loop
|
||||
void menu_core_loop(MenuCoreCtx* ctx, uint32_t tick);
|
||||
|
||||
@ -8,64 +8,26 @@ MenuErrCode menu_hash_init(MenuCoreCtx* ctx) {
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_hash_insert(MenuCoreCtx* ctx, MenuNodeId id) {
|
||||
MenuErrCode menu_hash_insert(MenuCoreCtx* ctx, MenuNodeId id, uint16_t index) {
|
||||
if (!ctx) return MENU_ERR_INVALID_PARAM;
|
||||
|
||||
uint16_t index = id % MENU_CONFIG_HASH_TABLE_SIZE;
|
||||
MenuNodeId current = ctx->hash_table[index];
|
||||
uint16_t hash_index = id % MENU_CONFIG_HASH_TABLE_SIZE;
|
||||
uint16_t current = ctx->hash_table[hash_index];
|
||||
|
||||
if (current == 0) {
|
||||
ctx->hash_table[index] = id;
|
||||
ctx->hash_table[hash_index] = index + 1;
|
||||
} else {
|
||||
// Collision handling: append to the end of the chain
|
||||
// Note: 0 is used as invalid ID/null pointer in this static array context
|
||||
// This implies valid node IDs must start from 1.
|
||||
|
||||
// Find the node structure for 'current'
|
||||
// This requires access to the node array which is in ctx
|
||||
// but we need to find the node index from the ID if ID != index
|
||||
// In this system, we usually assume ID corresponds to index if allocated sequentially,
|
||||
// but since we support arbitrary IDs (maybe?), we need to be careful.
|
||||
// HOWEVER, the design says "Static node pool", so usually we just iterate.
|
||||
// BUT wait, if we are building a hash table, we want O(1) access.
|
||||
// Let's assume ID is NOT the index in the array, but a unique handle.
|
||||
// But we need to find the MenuNode* from ID to set hash_next_id.
|
||||
|
||||
// Since we are inside menu_hash_insert, we probably just added the node
|
||||
// so we might know where it is, but this function takes ID.
|
||||
// We need a way to look up the MenuNode* from ID to set the next pointer.
|
||||
// If we can't find it efficiently, this hash insert is slow.
|
||||
|
||||
// Actually, looking at `MenuCoreCtx`, `nodes` is an array.
|
||||
// Does `nodes[i].id` == i?
|
||||
// If the user provides arbitrary IDs, we need to scan the array to find the node
|
||||
// to set its `hash_next_id`.
|
||||
// That defeats the purpose of the hash table if insertion is O(N).
|
||||
// But insertion happens only at init. Lookup is frequent.
|
||||
|
||||
// Let's implement finding the node by scanning for insertion.
|
||||
MenuNode* prev_node = NULL;
|
||||
// Find the head of the chain
|
||||
for (int i = 0; i < ctx->node_count; i++) {
|
||||
if (ctx->nodes[i].id == current) {
|
||||
prev_node = &ctx->nodes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!prev_node) return MENU_ERR_NOT_FOUND; // Should not happen
|
||||
|
||||
MenuNode* prev_node = &ctx->nodes[current - 1];
|
||||
while (prev_node->hash_next_id != 0) {
|
||||
MenuNodeId next_id = prev_node->hash_next_id;
|
||||
bool found = false;
|
||||
for (int i = 0; i < ctx->node_count; i++) {
|
||||
if (ctx->nodes[i].id == next_id) {
|
||||
prev_node = &ctx->nodes[i];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return MENU_ERR_FAIL; // Broken chain
|
||||
bool found = false;
|
||||
for (int i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
|
||||
if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == prev_node->hash_next_id) {
|
||||
prev_node = &ctx->nodes[i];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return MENU_ERR_FAIL;
|
||||
}
|
||||
|
||||
prev_node->hash_next_id = id;
|
||||
@ -74,26 +36,34 @@ MenuErrCode menu_hash_insert(MenuCoreCtx* ctx, MenuNodeId id) {
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuNodeId menu_hash_find(MenuCoreCtx* ctx, MenuNodeId id) {
|
||||
if (!ctx) return 0;
|
||||
MenuNode* menu_hash_find(MenuCoreCtx* ctx, MenuNodeId id) {
|
||||
if (!ctx) return NULL;
|
||||
|
||||
uint16_t index = id % MENU_CONFIG_HASH_TABLE_SIZE;
|
||||
MenuNodeId current = ctx->hash_table[index];
|
||||
uint16_t hash_index = id % MENU_CONFIG_HASH_TABLE_SIZE;
|
||||
uint16_t current = ctx->hash_table[hash_index];
|
||||
|
||||
while (current != 0) {
|
||||
if (current == id) return current;
|
||||
uint16_t node_index = current - 1;
|
||||
MenuNode* node = &ctx->nodes[node_index];
|
||||
if (node->flags.is_registered && node->id == id) {
|
||||
return node;
|
||||
}
|
||||
|
||||
// Move to next
|
||||
bool found = false;
|
||||
for (int i = 0; i < ctx->node_count; i++) {
|
||||
if (ctx->nodes[i].id == current) {
|
||||
current = ctx->nodes[i].hash_next_id;
|
||||
found = true;
|
||||
for (int i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
|
||||
if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == node->hash_next_id) {
|
||||
for (int j = 0; j < MENU_CONFIG_MAX_NODES; j++) {
|
||||
if (ctx->nodes[j].flags.is_registered && ctx->nodes[j].id == node->hash_next_id) {
|
||||
current = j + 1;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) break;
|
||||
if (!found) break;
|
||||
}
|
||||
|
||||
return 0; // Not found
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@ -8,11 +8,11 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
MenuErrCode menu_hash_init(MenuCoreCtx* ctx);
|
||||
MenuErrCode menu_hash_insert(MenuCoreCtx* ctx, MenuNodeId id);
|
||||
MenuNodeId menu_hash_find(MenuCoreCtx* ctx, MenuNodeId id);
|
||||
MenuErrCode menu_hash_insert(MenuCoreCtx* ctx, MenuNodeId id, uint16_t index);
|
||||
MenuNode* menu_hash_find(MenuCoreCtx* ctx, MenuNodeId id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MENU_HASH_H
|
||||
#endif // MENU_HASH_H
|
||||
@ -17,6 +17,13 @@ struct MenuCoreCtx;
|
||||
typedef uint16_t MenuNodeId;
|
||||
typedef uint8_t MenuPermissionLevel;
|
||||
|
||||
// Role structure
|
||||
typedef struct {
|
||||
uint8_t id;
|
||||
const char* name;
|
||||
MenuPermissionLevel level;
|
||||
} MenuRole;
|
||||
|
||||
// Error codes
|
||||
typedef enum {
|
||||
MENU_ERR_OK = 0,
|
||||
@ -110,10 +117,19 @@ typedef struct {
|
||||
uint8_t depth;
|
||||
} MenuNavPath;
|
||||
|
||||
typedef struct MenuPersistenceData {
|
||||
MenuNodeId current_node_id;
|
||||
MenuState current_state;
|
||||
MenuNavPath nav_path;
|
||||
MenuStack stack;
|
||||
uint32_t timestamp;
|
||||
uint8_t checksum;
|
||||
} MenuPersistenceData;
|
||||
|
||||
// Core Context
|
||||
typedef struct MenuCoreCtx {
|
||||
MenuNode nodes[MENU_CONFIG_MAX_NODES];
|
||||
MenuNodeId hash_table[MENU_CONFIG_HASH_TABLE_SIZE];
|
||||
uint16_t hash_table[MENU_CONFIG_HASH_TABLE_SIZE];
|
||||
MenuStack stack;
|
||||
MenuNavPath nav_path;
|
||||
MenuEventQueue event_queue;
|
||||
@ -132,6 +148,8 @@ typedef struct MenuCoreCtx {
|
||||
// Permission module data
|
||||
struct {
|
||||
uint8_t current_role_id;
|
||||
MenuRole roles[8];
|
||||
uint8_t role_count;
|
||||
} permission;
|
||||
|
||||
// Persistence module data
|
||||
@ -143,7 +161,7 @@ typedef struct MenuCoreCtx {
|
||||
struct {
|
||||
uint8_t current_lang_id;
|
||||
} lang;
|
||||
|
||||
|
||||
} MenuCoreCtx;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
Reference in New Issue
Block a user