Skip to content

Commit 980e6fd

Browse files
feat(radio): move inputNames to arena with sparse side-array indexing
Replace the fixed char inputNames[MAX_INPUTS][LEN_INPUT_NAME] array in ModelData with a sparse arena section (ARENA_INPUT_NAMES) indexed by a uint8_t inputNameIndex[MAX_INPUTS] side-array. Only named inputs consume arena space; 0xFF in the index means "no name". Accessor API: inputName() for O(1) read, inputNameAlloc() for alloc-on-demand, inputNameClear() with swap-with-last compaction. YAML serialization uses YAML_IDX_CUST so the custom index read/write maps between sparse arena slots and YAML input numbers directly — no expand/compact buffers needed. Saves 96 bytes (color) / 64 bytes (B&W) from ModelData for typical models that name only a few inputs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9744e95 commit 980e6fd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+420
-94
lines changed

radio/src/datastructs.h

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,35 +89,35 @@ static inline void check_struct()
8989
#endif
9090

9191
#if defined(RADIO_TPRO) || defined(RADIO_TPROV2) || defined(RADIO_BUMBLEBEE)
92-
CHKSIZE(ModelData, 1781);
92+
CHKSIZE(ModelData, 1717);
9393
#elif defined(RADIO_FAMILY_T20)
94-
CHKSIZE(ModelData, 1781);
94+
CHKSIZE(ModelData, 1717);
9595
#elif defined(RADIO_GX12)
96-
CHKSIZE(ModelData, 1845);
96+
CHKSIZE(ModelData, 1781);
9797
#elif defined(PCBX9E)
98-
CHKSIZE(ModelData, 2225);
98+
CHKSIZE(ModelData, 2129);
9999
#elif defined(PCBX9D) || defined(PCBX9DP)
100-
CHKSIZE(ModelData, 2224);
100+
CHKSIZE(ModelData, 2128);
101101
#elif defined(PCBX7) || defined(PCBXLITE) || defined(PCBX9LITE) || defined(RADIO_T14) || defined(RADIO_T12MAX)
102-
CHKSIZE(ModelData, 1755);
102+
CHKSIZE(ModelData, 1691);
103103
#elif defined(PCBPL18)
104104
#if defined(RADIO_NB4P) || defined(RADIO_NV14_FAMILY)
105-
CHKSIZE(ModelData, 2221);
105+
CHKSIZE(ModelData, 2125);
106106
#else
107-
CHKSIZE(ModelData, 2223);
107+
CHKSIZE(ModelData, 2127);
108108
#endif
109109
#elif defined(PCBST16) || defined(RADIO_T15PRO) || defined(RADIO_TX15)
110-
CHKSIZE(ModelData, 2837);
110+
CHKSIZE(ModelData, 2741);
111111
#elif defined(PCBPA01)
112-
CHKSIZE(ModelData, 2814);
112+
CHKSIZE(ModelData, 2718);
113113
#elif defined(RADIO_T15)
114-
CHKSIZE(ModelData, 2249);
114+
CHKSIZE(ModelData, 2153);
115115
#elif defined(RADIO_TX16SMK3)
116-
CHKSIZE(ModelData, 2838);
116+
CHKSIZE(ModelData, 2742);
117117
#elif defined(RADIO_H7RS)
118118
// CHKSIZE()
119119
#elif defined(PCBHORUS)
120-
CHKSIZE(ModelData, 2223);
120+
CHKSIZE(ModelData, 2127);
121121
#else
122122
#error CHKSIZE not set up
123123
#endif

radio/src/datastructs_private.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,14 @@ PACK(struct USBJoystickChData {
766766
#endif
767767
});
768768

769+
// YAML-visible struct for input name strings (adapts to LEN_INPUT_NAME).
770+
// CUST_IDX provides custom index read/write so the YAML index is the input
771+
// number while the arena stores names sparsely.
772+
PACK(struct InputNameStr {
773+
CUST_IDX(inputNames, r_input_name_idx, w_input_name_idx);
774+
char val[LEN_INPUT_NAME];
775+
});
776+
769777
PACK(struct ModelData {
770778
CUST_ATTR(semver,nullptr,w_semver);
771779
ModelHeader header;
@@ -834,7 +842,8 @@ PACK(struct ModelData {
834842

835843
SCRIPT_DATA
836844

837-
NOBACKUP(char inputNames[MAX_INPUTS][LEN_INPUT_NAME]);
845+
NOBACKUP(uint8_t inputNameIndex[MAX_INPUTS]) SKIP;
846+
CUST_EXTERN_ARRAY_NOIDX(inputNames, struct_InputNameStr, MAX_INPUTS, yaml_drv_input_names);
838847
NOBACKUP(potwarnen_t potsWarnEnabled);
839848
NOBACKUP(int8_t potsWarnPosition[MAX_POTS]);
840849

radio/src/expos.cpp

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
#include "tasks/mixer_task.h"
2424
#include "hal/adc_driver.h"
2525
#include "model_arena.h"
26+
#include "dataconstants.h"
2627

2728
#include "edgetx.h"
2829

30+
#include <string.h>
31+
2932
static uint8_t _nb_expo_lines;
3033

3134
ExpoData* expoAddress(uint8_t idx) {
@@ -81,7 +84,7 @@ void deleteExpo(uint8_t idx)
8184
g_modelArena.deleteFromSection(ARENA_EXPOS, idx, sizeof(ExpoData));
8285

8386
if (!isInputAvailable(input)) {
84-
memclear(&g_model.inputNames[input], LEN_INPUT_NAME);
87+
inputNameClear(input);
8588
}
8689
mixerTaskStart();
8790

@@ -174,3 +177,73 @@ void updateExpoCount()
174177
{
175178
_nb_expo_lines = _countExpoLines();
176179
}
180+
181+
// ---------------------------------------------------------------------------
182+
// Input name accessors (arena-backed, sparse via side-array index)
183+
// ---------------------------------------------------------------------------
184+
185+
#define INPUT_NAME_NO_SLOT 0xFF
186+
187+
void inputNameIndexReset()
188+
{
189+
memset(g_model.inputNameIndex, INPUT_NAME_NO_SLOT,
190+
sizeof(g_model.inputNameIndex));
191+
}
192+
193+
static inline char* _inputNameSlot(uint8_t slot)
194+
{
195+
return reinterpret_cast<char*>(
196+
g_modelArena.sectionBase(ARENA_INPUT_NAMES)) + slot * LEN_INPUT_NAME;
197+
}
198+
199+
const char* inputName(uint8_t input)
200+
{
201+
uint8_t slot = g_model.inputNameIndex[input];
202+
if (slot == INPUT_NAME_NO_SLOT)
203+
return nullptr;
204+
return _inputNameSlot(slot);
205+
}
206+
207+
bool hasInputName(uint8_t input)
208+
{
209+
return g_model.inputNameIndex[input] != INPUT_NAME_NO_SLOT;
210+
}
211+
212+
char* inputNameAlloc(uint8_t input)
213+
{
214+
uint8_t slot = g_model.inputNameIndex[input];
215+
if (slot != INPUT_NAME_NO_SLOT)
216+
return _inputNameSlot(slot);
217+
218+
// Append a new slot
219+
uint16_t count = g_modelArena.sectionCount(ARENA_INPUT_NAMES);
220+
if (!g_modelArena.ensureSectionCapacity(ARENA_INPUT_NAMES, count + 1))
221+
return nullptr;
222+
223+
g_model.inputNameIndex[input] = count;
224+
return _inputNameSlot(count);
225+
}
226+
227+
void inputNameClear(uint8_t input)
228+
{
229+
uint8_t slot = g_model.inputNameIndex[input];
230+
if (slot == INPUT_NAME_NO_SLOT)
231+
return;
232+
233+
uint16_t count = g_modelArena.sectionCount(ARENA_INPUT_NAMES);
234+
uint16_t lastSlot = count - 1;
235+
236+
if (slot != lastSlot) {
237+
// Find which input owns the last slot and swap
238+
for (uint8_t i = 0; i < MAX_INPUTS; i++) {
239+
if (g_model.inputNameIndex[i] == lastSlot) {
240+
memcpy(_inputNameSlot(slot), _inputNameSlot(lastSlot), LEN_INPUT_NAME);
241+
g_model.inputNameIndex[i] = slot;
242+
break;
243+
}
244+
}
245+
}
246+
247+
g_model.inputNameIndex[input] = INPUT_NAME_NO_SLOT;
248+
g_modelArena.trimSectionTo(ARENA_INPUT_NAMES, lastSlot);
249+
}

radio/src/expos.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,23 @@ uint8_t getExpoCount();
4949
// Should only be called from storage
5050
// right after a model has been loaded
5151
void updateExpoCount();
52+
53+
// --- Input name accessors (arena-backed, sparse) ---
54+
55+
// Read-only lookup. Returns pointer to name or nullptr if input has no name.
56+
const char* inputName(uint8_t input);
57+
58+
// Returns true if the input has a name allocated.
59+
bool hasInputName(uint8_t input);
60+
61+
// Ensure a name slot exists for 'input' and return a writable pointer.
62+
// Allocates a new arena slot if needed. Returns nullptr on arena exhaustion.
63+
char* inputNameAlloc(uint8_t input);
64+
65+
// Remove the name for 'input', freeing the arena slot (swap-with-last).
66+
void inputNameClear(uint8_t input);
67+
68+
// Clear the entire inputNameIndex (set all to 0xFF).
69+
// Called on model init / clear before loading names.
70+
void inputNameIndexReset();
71+

radio/src/gui/128x64/model_input_edit.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ void menuModelExpoOne(event_t event)
8888
switch (i) {
8989
case EXPO_FIELD_INPUT_NAME:
9090
editSingleName(EXPO_ONE_2ND_COLUMN, y,
91-
STR_INPUTNAME, g_model.inputNames[ed->chn],
91+
STR_INPUTNAME, inputNameAlloc(ed->chn),
9292
LEN_INPUT_NAME, event, (attr != 0), old_editMode);
9393
break;
9494

radio/src/gui/212x64/model_input_edit.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ void menuModelExpoOne(event_t event)
9090
switch (i) {
9191
case EXPO_FIELD_INPUT_NAME:
9292
editSingleName(EXPO_ONE_2ND_COLUMN, y, STR_INPUTNAME,
93-
g_model.inputNames[ed->chn], LEN_INPUT_NAME, event, attr,
93+
inputNameAlloc(ed->chn), LEN_INPUT_NAME, event, attr,
9494
old_editMode);
9595
break;
9696

radio/src/gui/colorlcd/model/input_edit.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ void InputEditWindow::buildBody(Window* form)
158158
// Input Name
159159
auto line = form->newLine(grid);
160160
new StaticText(line, rect_t{}, STR_INPUTNAME);
161-
new ModelTextEdit(line, rect_t{}, g_model.inputNames[input->chn],
161+
new ModelTextEdit(line, rect_t{}, inputNameAlloc(input->chn),
162162
LEN_INPUT_NAME,
163163
[=]() {
164164
setTitle();

radio/src/gui/common/stdlcd/draw_functions.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -561,9 +561,11 @@ void drawSource(coord_t x, coord_t y, const SourceRef& ref, LcdFlags att)
561561
break;
562562

563563
case SOURCE_TYPE_INPUT:
564+
{
565+
const char* iName = inputName(idx);
564566
if (att & RIGHT) {
565-
if (g_model.inputNames[idx][0])
566-
lcdDrawSizedText(x, y, g_model.inputNames[idx], LEN_INPUT_NAME, att);
567+
if (iName && iName[0])
568+
lcdDrawSizedText(x, y, iName, LEN_INPUT_NAME, att);
567569
else
568570
lcdDrawNumber(x, y, idx + 1, att|LEADING0, 2);
569571
x = lcdLastLeftPos - 5;
@@ -578,12 +580,13 @@ void drawSource(coord_t x, coord_t y, const SourceRef& ref, LcdFlags att)
578580
}
579581
lcdDrawChar(x+1, y+1, CHR_INPUT, TINSIZE);
580582
lcdDrawSolidFilledRect(x, y, 5, 7);
581-
if (g_model.inputNames[idx][0])
582-
lcdDrawSizedText(x+6, y, g_model.inputNames[idx], LEN_INPUT_NAME, att);
583+
if (iName && iName[0])
584+
lcdDrawSizedText(x+6, y, iName, LEN_INPUT_NAME, att);
583585
else
584586
lcdDrawNumber(x+6, y, idx + 1, att|LEADING0, 2);
585587
}
586588
break;
589+
}
587590

588591
#if defined(LUA_INPUTS)
589592
case SOURCE_TYPE_LUA:

radio/src/lua/api_model.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,8 @@ static int luaModelGetInput(lua_State *L)
654654
ExpoData * expo = expoAddress(first+idx);
655655
lua_newtable(L);
656656
lua_pushtablenstring(L, "name", expo->name);
657-
lua_pushtablenstring(L, "inputName", g_model.inputNames[chn]);
657+
const char* iName = inputName(chn);
658+
lua_pushtablenstring(L, "inputName", iName ? iName : "");
658659
lua_pushtableinteger(L, "source", expo->srcRaw.toUint32());
659660
lua_pushtableinteger(L, "scale", expo->scale);
660661
lua_pushtableinteger(L, "weight", valueOrSourceToLuaInt(expo->weight));
@@ -707,7 +708,11 @@ static int luaModelInsertInput(lua_State *L)
707708
}
708709
else if (!strcmp(key, "inputName")) {
709710
const char * name = luaL_checkstring(L, -1);
710-
strncpy(g_model.inputNames[chn], name, LEN_INPUT_NAME);
711+
if (name[0]) {
712+
strncpy(inputNameAlloc(chn), name, LEN_INPUT_NAME);
713+
} else {
714+
inputNameClear(chn);
715+
}
711716
}
712717
else if (!strcmp(key, "source")) {
713718
expo->srcRaw = SourceRef::fromUint32(luaL_checkinteger(L, -1));

radio/src/model_arena.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ static const uint8_t modelElemSizes[MODEL_ARENA_NUM_SECTIONS] = {
3939
sizeof(FlightModeData),
4040
sizeof(GVarData),
4141
sizeof(gvar_t),
42+
LEN_INPUT_NAME,
4243
};
4344

4445
const ArenaDesc modelArenaDesc = { MODEL_ARENA_NUM_SECTIONS, modelElemSizes };

0 commit comments

Comments
 (0)