Здравствуйте, m2user, Вы писали:
M>А в чём проблема с конверторами?
Хочется с минимальной болью это делать. Пока всё через жопу.
M>Единственное неудобство, что новая версия любой внутренней структуры требует подъема версии структур ее включающих.
А вот тут не очень понятно, нафига поднимать версии включенных структур? А если включенная структура ещё и в других местах включается, то там тоже у включающей структуры надо версию поднимать?
M>Ну и все предыдущие версии структур должны присутсвовать в коде.
Да, так наверное и надо сделать. Сейчас все версии только в истории в гите лежат.
M>И я исхожу из того, что алгоритм конвертации в новою версию должен явным образом описываться разработчиком.
Здравствуйте, Marty, Вы писали:
M>>>Тут не в размере дело. В любом случае, у меня в девайсе 256 Кб вообще на всё, если не меньше.
Если экономия будет несколько килобайт по сравнению с текстовым форматом, оно того точно стоит?
IMHO ты больше провозишься с написанием своего формата, с около нулевым выхлопом по сравнению с банальным JSON.
Впрочем энергии тебе не занимать
Здравствуйте, bnk, Вы писали:
M>>>>Тут не в размере дело. В любом случае, у меня в девайсе 256 Кб вообще на всё, если не меньше.
bnk>Если экономия будет несколько килобайт по сравнению с текстовым форматом, оно того точно стоит? bnk>IMHO ты больше провозишься с написанием своего формата, с около нулевым выхлопом по сравнению с банальным JSON.
Текстовый формат в любом случае не подходит. Различные подсистемы хотят на старте получить указатель на свою двоичную структуру, чтобы потом во время работы туда напрямую писать.
И конфиг хранится не во флеше, а епроме, там всего 16Кб
M>А если включенная структура ещё и в других местах включается, то там тоже у включающей структуры надо версию поднимать?
Да, потому что это уже другой тип, т.к. набор/тип полей изменился
struct A1 {
B1 b;
}
struct A2 {
B2 b;
}
Но я делал так, что самая свежая версия была без номеров (при сериализации версия конфига конечно была), а исторические версии — номерные.
Чтобы вся эта нумерация была только в коде чтения/записи и конвертации.
M>>И я исхожу из того, что алгоритм конвертации в новою версию должен явным образом описываться разработчиком. M>Хотелось бы это сделать минимально просто
Есть логика агрейда, её автоматом никак не вывести. Но можно предоставить какие-то хелперы, для типовых ситуаций.
Здравствуйте, Marty, Вы писали:
M>Текстовый формат в любом случае не подходит. Различные подсистемы хотят на старте получить указатель на свою двоичную структуру, чтобы потом во время работы туда напрямую писать. M>И конфиг хранится не во флеше, а епроме, там всего 16Кб
Так это тогда ни разу не "конфиг". RIFF (TLV) нормальный вариант, это работает десятилетиями, и проще сложно что-то придумать IMHO.
В тэг можно тип struct-а писать (StructX structY, etc), и номер ее версии например (X1, Y1, ну и т.п.)
Чтобы проще парсить, я бы запретил изменения, только дополнения полей.
Т.е. "Y2" это "Y1" с дополнительными полями.
Тогда код код который парсит Y1 можно использовать для парсинга Y2 тоже.
А если нужно поменять поле в структуре, заводишь новое.
Но в принципе опционально, просто код который это парсит должен быть сложнее.
Длина блока там нужна чтобы перепрыгивать "неизвестные" блоки.
Так код, читающий конфиг должен читать только то что понимает , а что не понимает — пропускать до следующего заголовка (тэга).
Т.е. старый код сможет прочитать "новый" конфиг и не упасть.
Здравствуйте, m2user, Вы писали:
M>>А если включенная структура ещё и в других местах включается, то там тоже у включающей структуры надо версию поднимать?
M>Да, потому что это уже другой тип, т.к. набор/тип полей изменился
Как я понял, ты, если сделал v2 версию для A, зачем-то делаешь Inner_v2, и тогда надо лезть в B_v1, менять там на Inner_v2, и также надо поднимать версию и у B/
struct A_v2
{
//...char ch1;
Inner_v1 inner; // Тут не надо повышать версию вложенной структуры
//...
};
M>Но я делал так, что самая свежая версия была без номеров (при сериализации версия конфига конечно была), а исторические версии — номерные. M>Чтобы вся эта нумерация была только в коде чтения/записи и конвертации.
Ну, это решается организационно, либо последнюю версию перед изменением копируем и задаём ей номер, а изменения вносим в структуру без версии, либо сразу создаём новую структуру с новой версией, и делаем ей алиас без версии.
M>Есть логика агрейда, её автоматом никак не вывести.
Ну вот это как раз и хотелось бы сделать, чисто — сделал новую версию структуры, сделал описание layout'а новой версии, и оно само все делает
Здравствуйте, bnk, Вы писали:
M>>Текстовый формат в любом случае не подходит. Различные подсистемы хотят на старте получить указатель на свою двоичную структуру, чтобы потом во время работы туда напрямую писать. M>>И конфиг хранится не во флеше, а епроме, там всего 16Кб
bnk>Так это тогда ни разу не "конфиг". RIFF (TLV) нормальный вариант, это работает десятилетиями, и проще сложно что-то придумать IMHO.
Ну, в конторе это называют конфигом, хотя да, это live-слепок, скорее, он постоянно обновляется и там много чего разного живет, как и какие-то параметры настройки, которые редко меняются, и какие-то накапливаемые за всё время жизни прибора данные.
Да, мне уже подсказали, что надо копать в сторону TLV
bnk>В тэг можно тип struct-а писать (StructX structY, etc), и номер ее версии например (X1, Y1, ну и т.п.) bnk>Чтобы проще парсить, я бы запретил изменения, только дополнения полей.
Сейчас думаю, как именам давать на автомате короткие стабильные идентификаторы. Хранить имена — слишком жирно — 10 структур по 10 полей, каждое 10 символов — это килобайт только на имена одной версии
bnk>Т.е. "Y2" это "Y1" с дополнительными полями. bnk>Тогда код код который парсит Y1 можно использовать для парсинга Y2 тоже. bnk>А если нужно поменять поле в структуре, заводишь новое. bnk>Но в принципе опционально, просто код который это парсит должен быть сложнее.
Да по идее, удалять тоже не проблема, если в новой структуре нет поля, то из старой ничего не берётся просто.
Ну, и соответственно, менять местами тоже нет проблем.
Здравствуйте, Marty, Вы писали:
M>Здравствуйте!
M>Есть развесистые вложенные структуры. Хранится, допустим, просто в файле, как снимок памяти с корневой структурой. Всё структуры состоят из примитивных типов, вроде даже float'ов нет. Есть массивы, в тч и структур, исключительно фиксированного размера. M>В любой новой версии софтины могут быть добавлены поля в какую-нибудь вложенную структуру, и, соответственно, вся двоичная раскладка поедет. Также поля могу быть удалены, перемещены, или переименованы.
M>В общем, такое вот, историческое легаси.
M>Требуется придумать какой-то механизм, который позволяет вычитать старую версию конфига, всё, что можно — перенести в новый конфиг, если чего-то в старом конфиге нет — установить значение по умолчанию.
M>Вроде бы, protobuf что-то умеет в версионность, но: нам нужно уметь для каждого поля любой структуры получить его смещение относительно начала файла, чтобы писать непоредственно по этому смещению.
M>Как вам такое?
M>ЗЫ Задача со звёздочкой — сделать на чистейшей сишечке.
M>ЗЫЫ Внешние инструменты не очень приветствуются, но можно попробовать написать что-то на питоне, и к питону наверное даже можно доустановить каких-то библиотек, помимо того, что идёт в базе, если это не слишком гемморно.
Конфиг один — начальная версия. Используем скрипты миграции, на каком ни будь формально языке, которые переводят конфиг со старой версии на новую.
Надо сразу читать всю структуру в память?
Никак хитро не сделать. Читаешь первый флаг номера версии и в зависимости от него считываешь нужный размер в нужную структуру. А потом уже любую из этих структур конвертишь в последнюю версию с дефолтными значениями.
Какие еще «челенджи» тут могут быть?
Текстовые форматы типа джейсон для того и придумали, чтоб избежать этого гемора бинарных.
M>Как я понял, ты, если сделал v2 версию для A, зачем-то делаешь Inner_v2, и тогда надо лезть в B_v1, менять там на Inner_v2, и также надо поднимать версию и у B/
Нет, я говорил про сценарий, когда требуется новая версия для Inner. И в этом случае нужно повышать версии всех типов использующих Inner, напрямую (A и B) или опосредованно.
Объем необходимых изменений начинает зависеть от "развесистости" структур.
M>Ну, это решается организационно, либо последнюю версию перед изменением копируем и задаём ей номер, а изменения вносим в структуру без версии, либо сразу создаём новую структуру с новой версией, и делаем ей алиас без версии.
Да, как-то так, просто не во всех ЯП есть алиасы типа typedef.
M>Ну вот это как раз и хотелось бы сделать, чисто — сделал новую версию структуры, сделал описание layout'а новой версии, и оно само все делает
В моих задачах как правило новые поля выводились из старых по некоему нетривиальному алгоритму.
Например раньше значение было в виде строки, а теперь бинарный формат или новое значение нужно вывести из нескольких других полей (+ что-нибудь из окружения, типа текущей локали).
Терять старые данные было нельзя.
M>Потонете, хаха. Уже буль-буль. Этому легаси не первый год, и, возможно, не первый даже десяток лет. Уже два с лишним десятка версий, и для каждой вручный с болью написанный конвертер. Поэтому и есть желание это как-то сделать не так больно, но расхреначивать существующую системы и переделывать всё с нуля никто не даст. В том числе и потому, что сишечка. Если плюсы просто не скомпилировали бы, сишечка кушает и причмокивает.
Ну то есть раз в полгода-год пишется новый конвертер. И так уже 10-20 лет. Сдается мне, что проще и дешевле оставить все как есть и тратить 1-2 дня или неделю раз в полгода-год, чем мучиться сейчас писать "универсальный" конвертер, имея риск вообще все поломать, что уже много лет работает.
Здравствуйте, m2user, Вы писали:
M>>Как я понял, ты, если сделал v2 версию для A, зачем-то делаешь Inner_v2, и тогда надо лезть в B_v1, менять там на Inner_v2, и также надо поднимать версию и у B/
M>Нет, я говорил про сценарий, когда требуется новая версия для Inner. И в этом случае нужно повышать версии всех типов использующих Inner, напрямую (A и B) или опосредованно. M>Объем необходимых изменений начинает зависеть от "развесистости" структур.
Не так значит понял. Да, само собой, повышать надо версию везде, где используется вложенная структура
Здравствуйте, DiPaolo, Вы писали:
M>>Потонете, хаха. Уже буль-буль. Этому легаси не первый год, и, возможно, не первый даже десяток лет. Уже два с лишним десятка версий, и для каждой вручный с болью написанный конвертер. Поэтому и есть желание это как-то сделать не так больно, но расхреначивать существующую системы и переделывать всё с нуля никто не даст. В том числе и потому, что сишечка. Если плюсы просто не скомпилировали бы, сишечка кушает и причмокивает.
DP>Ну то есть раз в полгода-год пишется новый конвертер. И так уже 10-20 лет. Сдается мне, что проще и дешевле оставить все как есть и тратить 1-2 дня или неделю раз в полгода-год, чем мучиться сейчас писать "универсальный" конвертер, имея риск вообще все поломать, что уже много лет работает.
Ну не знаю, народ прям вот задолбался. И проблема не только написать конвертер, проблема — не накосячить при написании конвертера
Здравствуйте, DiPaolo, Вы писали: DP>Ну то есть раз в полгода-год пишется новый конвертер. И так уже 10-20 лет. Сдается мне, что проще и дешевле оставить все как есть и тратить 1-2 дня или неделю раз в полгода-год, чем мучиться сейчас писать "универсальный" конвертер, имея риск вообще все поломать, что уже много лет работает.
Похоже, про десятки лет я загнул
Новость про недавно выпущенную отладочную плату для МК К1986ВУ024, датирована декабрём 23го
Недавно — это, наверное, в 23ем году. Ну, ещё пару-тройку лет промка могла жить на нём без официальной дев-борды, итого, наверное, проц года 20го, примерно тогда наш девайс, скорее всего, и стартовал, хотя, может, легаси было с предыдущих моделей, но сам завод вроде недавно 7 лет отмечал, так что вряд ли сильно дольше. Хотя, может, девайс и старше, и делался на других чипах другими юр лицами.
M>Вроде бы, protobuf что-то умеет в версионность, но: нам нужно уметь для каждого поля любой структуры получить его смещение относительно начала файла, чтобы писать непоредственно по этому смещению.
Дикое требование, лучше от него попробовать избавиться.
Protobuf широко использует varint, и даже найдя смещение, не факт, что получится по нему записать новое число, так как для него при кодировании может понадобится больше места, что потребует сдвинуть память, идущую следом. Но можно в protobuf использовать только fixed-size типы — у них длина всегда одинакова.
Впрочем у protobuf есть достойный конкурент — FlatBuffers. В нём внутри всё построено на операции "дай смещение в памяти для этого поля". И писать по полученному смещению тоже можно, если делать аккуратно. Хотя и не рекомендуется, потому что при неаккуратном использовании можешь задеть и испортить какие-то структуры самого flatbuffers, например.
Здравствуйте, Marty, Вы писали:
M>Здравствуйте!
M>Есть развесистые вложенные структуры. Хранится, допустим, просто в файле, как снимок памяти с корневой структурой. Всё структуры состоят из примитивных типов, вроде даже float'ов нет. Есть массивы, в тч и структур, исключительно фиксированного размера.
M>В любой новой версии софтины могут быть добавлены поля в какую-нибудь вложенную структуру, и, соответственно, вся двоичная раскладка поедет. Также поля могу быть удалены, перемещены, или переименованы.
Из-за последнего пункта придется писать скрипты/описания конверсии из каждого предыдущего формата в нынешний (или цепочку последовательных конверсий), поскольку по мета информации описывающей текущею структуру данных невозможно отличить переименование от удаления+добавления.
Это было бы проблемой и для текстового конфига.
Нужно уметь версионность на самом контролере, или можно иметь скрипты обновления конфигов на десктопе?
Сколько у нас вообще есть памяти под хранение мета информации о предыдущих версиях?
Здравствуйте, Marty, Вы писали:
M>Здравствуйте!
M>Есть развесистые вложенные структуры. Хранится, допустим, просто в файле, как снимок памяти с корневой структурой. Всё структуры состоят из примитивных типов, вроде даже float'ов нет. Есть массивы, в тч и структур, исключительно фиксированного размера.
M>В любой новой версии софтины могут быть добавлены поля в какую-нибудь вложенную структуру, и, соответственно, вся двоичная раскладка поедет. Также поля могу быть удалены, перемещены, или переименованы.
M>В общем, такое вот, историческое легаси.
M>Требуется придумать какой-то механизм, который позволяет вычитать старую версию конфига, всё, что можно — перенести в новый конфиг, если чего-то в старом конфиге нет — установить значение по умолчанию.
M>Вроде бы, protobuf что-то умеет в версионность, но: нам нужно уметь для каждого поля любой структуры получить его смещение относительно начала файла, чтобы писать непоредственно по этому смещению.
M>Как вам такое?
хранить смещение поля и версию в constexpr справочниках. Дальше функцию трансформации написать с помощью ИИ
исходная структура
enum class Type {
b8, // bool
i8, // int8_t
u8, // uint8_t
u32, // uint32_t
f32 // float
};
struct Entry {
std::string_view name;
Type type;
};
constexpr size_t NumFields = 3;
constexpr size_t NumVersions = 2;
constexpr std::array<Entry, NumFields> fields = {
Entry{"id", Type::u8},
Entry{"timestamp", Type::u32},
Entry{"value", Type::f32}
};
constexpr std::array<std::array<std::optional<size_t>, NumVersions>, NumFields> offsets = {{
{0, 0}, // id
{std::nullopt, 4}, // timestamp (в старой версии поля не было, теперь появилось)
{4, 8} // value
}};
код от ИИ:
// 🔹 Получение размера типа
constexpr size_t typeSize(Type t) {
switch (t) {
case Type::b8: return 1;
case Type::i8: return 1;
case Type::u8: return 1;
case Type::u32: return 4;
case Type::f32: return 4;
}
return 0;
}
// 🔹 Получение дефолтного значенияvoid writeDefault(std::ostream& out, Type t) {
switch (t) {
case Type::b8: { bool v = false; out.write(reinterpret_cast<char*>(&v), sizeof(v)); break; }
case Type::i8: { int8_t v = 0; out.write(reinterpret_cast<char*>(&v), sizeof(v)); break; }
case Type::u8: { uint8_t v = 0; out.write(reinterpret_cast<char*>(&v), sizeof(v)); break; }
case Type::u32: { uint32_t v = 0; out.write(reinterpret_cast<char*>(&v), sizeof(v)); break; }
case Type::f32: { float v = 0.0f; out.write(reinterpret_cast<char*>(&v), sizeof(v)); break; }
}
}
// 🔹 Основная функция трансформацииvoid transformRecord(size_t sourceVersion, std::istream& in, size_t targetVersion, std::ostream& out) {
for (size_t i = 0; i < NumFields; ++i) {
const auto& entry = fields[i];
const auto& sourceOffset = offsets[i][sourceVersion];
const auto& targetOffset = offsets[i][targetVersion];
if (!targetOffset.has_value()) continue; // поле не входит в целевую версию
// Перемещаемся в целевой поток на нужное место
out.seekp(targetOffset.value(), std::ios::beg);
if (sourceOffset.has_value()) {
// Читаем из исходного потока
in.seekg(sourceOffset.value(), std::ios::beg);
size_t size = typeSize(entry.type);
char buffer[8] = {}; // максимум 8 байт
in.read(buffer, size);
if (!in) throw std::runtime_error("Failed to read field: " + std::string(entry.name));
out.write(buffer, size);
} else {
// Пишем дефолтное значение
writeDefault(out, entry.type);
}
}
}
Здравствуйте, Marty, Вы писали:
M>Здравствуйте!
M>Есть развесистые вложенные структуры. Хранится, допустим, просто в файле, как снимок памяти с корневой структурой. Всё структуры состоят из примитивных типов, вроде даже float'ов нет. Есть массивы, в тч и структур, исключительно фиксированного размера.
M>В любой новой версии софтины могут быть добавлены поля в какую-нибудь вложенную структуру, и, соответственно, вся двоичная раскладка поедет. Также поля могу быть удалены, перемещены, или переименованы.
M>В общем, такое вот, историческое легаси.
M>Требуется придумать какой-то механизм, который позволяет вычитать старую версию конфига, всё, что можно — перенести в новый конфиг, если чего-то в старом конфиге нет — установить значение по умолчанию.
M>Вроде бы, protobuf что-то умеет в версионность, но: нам нужно уметь для каждого поля любой структуры получить его смещение относительно начала файла, чтобы писать непоредственно по этому смещению.
M>Как вам такое?
M>ЗЫ Задача со звёздочкой — сделать на чистейшей сишечке.
M>ЗЫЫ Внешние инструменты не очень приветствуются, но можно попробовать написать что-то на питоне, и к питону наверное даже можно доустановить каких-то библиотек, помимо того, что идёт в базе, если это не слишком гемморно.
Делал когда-то что-то подобное, но там был структура была не очень очень развесистая, полей было мало, и массивов внутри не было если полей много то геморойно. Я вручную написал конвертор в json и обратно.
Конвертуруешь старой версией бинарный файл в и обратно новой версией. С удалением и добавлением полей прекрасно справлялось, с переименованием все посложнее, надо было вручную в сгенерированном json переименовывать.
Но вообще бинарныой конфиг это жуткий гемор. Я бы рекомендовал действовать радикально и разом переписать в какой-то текстовый формат.