Комментарии 21
Поищите библиотеку "POST--" там было что то подобное.
Simple persistent storage
У меня не простое хранилище, а полноценная система, так что вы направляете меня/читателей скорее всего не туда.
delete rec;
ну и там походу даже своих умных указателей нет.
Там даже в README прямым текстом:
“Unfortunately POST++ due to its simplicity provides no facilities for automatic object conversion”
POST++ — это persistent object store на mmap, а не сериализация с миграциями.
Он предполагает стабильный layout, адреса и не поддерживает автоматическое изменение типов.
В моей задаче ключевыми были самоописание формата и обновление бинарника без потери данных, поэтому подход принципиально другой.
Как-то вы на примеры сериализации/десериализации поскупились. Кажется проблема решается несколько проще без необходимости изобретать собственные умные указатели.
Как-то вы на примеры сериализации/десериализации поскупились
Спасибо за замечание, хорошо, попробую добавить ещё примеров как только придумаю их. Есть какие-то пожелания что именно вы хотели бы увидеть в них?
Кажется проблема решается несколько проще без необходимости изобретать собственные умные указатели.
Не-а, похоже без своих умных указателей к сожалению невозможно вывести весь необходимый список типов который нужно добавить в файл сохранения для саммоописания формата сохранения.
Есть какие-то пожелания что именно вы хотели бы увидеть в них?
Вы же решали какую-то проблему. Уменьшите скоуп проблемы и сделайте из неё пример поменьше. Заодно будет видно зачем понадобилось иметь ссылочные типы и частичную сериализацию структур. Накидайте какой-нибудь граф, который вы хотели сериализовать.
Вы же решали какую-то проблему
Я на основе этой системы сохранял/загружал/мигрировал всё состояние своих программ, а так же делал довольно крутой(как мне тогда казалось) графический отладчик/интроспектор для них. Последний позволял добраться до почти любой хранимой переменной и поменять её.
Уменьшите скоуп проблемы и сделайте из неё пример поменьше.
Проблема было две: "сохранить всё состояние программы" и "менять его в реальном времени не перезапуская программу", как мне уменьшить такой скоуп? Сохранить половину состояния программы? :)
Заодно будет видно зачем понадобилось иметь ссылочные типы и частичную сериализацию структур
Ссылочные типы очень пригодились для описания типов используемых внутри сохраняемого файла, хотя конечно всё можно было заменить на строки и тогда мы бы поменяли производительность на простоту реализации. У меня получилось нифига не просто, но зато компактно и быстро. Ещё ссылочные типы используются для указания на текстуры/шрифты/спрайты_в_атлассе/клавиатур... но всё это можно переделать на строковые идентификаторы и победить в 80% случаев как мне кажется. Вообще я планировал использовать ссылочные типы для того чтобы боты в игре ссылались друг на друга, но как оказалось - это плохой дизайн и лучше иметь локальные числовые идентификаторы, а не глобальные умные следящие указатели.
Накидайте какой-нибудь граф, который вы хотели сериализовать.
Мне кажется задача бред и такого графа нет(за исключением задачи описания системы типов используемой в сохранении), рулят почти везде локальные числовые идентификаторы, а не моя система.
Проблема было две: "сохранить всё состояние программы" и "менять его в реальном времени не перезапуская программу", как мне уменьшить такой скоуп? Сохранить половину состояния программы? :)
Вы всегда можете сделать маленькую программу. Хэлоу ворлд, REPL c финбонначи и мемоизацией, систему управления лифтами, пинг-понг с роботом, симулятор веб или почтового сервера. Заодно будет фикстурой для юнит-тестов.
"менять его в реальном времени не перезапуская программу"
для этого придумали скриптование. делаете какой-нибудь ctrl+r и триггерите переинициализацию скрипта. hotreload никогда не требовал сериализации графа.
текстуры/шрифты/спрайты_в_атлассе
заводите класс реестра ресурсов и просто ссылаетесь на ресурс по некоторому составному id - resource id + offset id. В качестве resource id можно взять например djb2 хэш от пути к ресурсу. Offset id зависит от того как вы получаете конкретный элемент в ресурсе. Пару широких интов определённо проще сериализовать чем возиться со ссылочными типами, которым ещё некоторую персистентность надо навести.
Ссылочные типы очень пригодились для описания типов используемых внутри сохраняемого файла.
Так а используется-то оно как? Выглядит как несколько странный дебаггинг, судя по вашему описанию.
Мне кажется задача бред и такого графа нет
Ну, вы же решали какую-то свою боль, изобретая вот это вот всё. Пусть криво-косо и вероятно без знания предметной области, но вполне что-то решили в итоге. Это нормально. Сделайте минимально воспроизводимую боль. Пусть без окон и imgui крутилок, но понятную другим.
Хотя хаб "ненормальное программирование" я бы всё же добавил.
для этого придумали скриптование. делаете какой-нибудь ctrl+r и триггерите переинициализацию скрипта. hotreload никогда не требовал сериализации графа.
Ну чистая правда, даже придраться не к чему. Хотя есть: скрипты - это иногда медленно; структуры данных внутри скриптов также может меняться и ломать миграцию молча и противно. Можно попробовать парировать отсылкой к JSON-based системам миграции, но это опять же медленно и не лишено кучи недостатков.
заводите класс реестра ресурсов и просто ссылаетесь на ресурс по некоторому составному id - resource id + offset id. В качестве resource id можно взять например djb2 хэш от пути к ресурсу. Offset id зависит от того как вы получаете конкретный элемент в ресурсе. Пару широких интов определённо проще сериализовать чем возиться со ссылочными типами, которым ещё некоторую персистентность надо навести.
100% правда. я так поступил из-за своего неумения проектировать программы. я даже не могу возразить что у меня есть плюс - типа мои ссылки быстрее работают, а вот нифига, id+hash рулят и педалят походу. Всё что меня спасает - это отсутствие коллизии хэшей вообще.
Так а используется-то оно как? Выглядит как несколько странный дебаггинг, судя по вашему описанию.
Используется при миграции между форматами.
Ну, вы же решали какую-то свою боль, изобретая вот это вот всё.
ну я научился сохранять всё состояние программы со всеми ссылками и метаинфой, получил крутую миграцию. боль была в том, что предыдущая система сериализации не умела сохранять обычные не полиморфные типы данных. А это очень полезно для работы по сети, т.к там требуется компактность и скорость. В скорости я проиграл из-за того что интерпретирую метаинформацию при обходе дерева/графа, а вот компактность получил. Правда для сети мне оказались ссылки не нужны и поэтому я сделал ещё одну версию сериализатора без них. И она победила из-за своей скорости компиляции.
Сделайте минимально воспроизводимую боль. Пусть без окон и imgui крутилок, но понятную другим.
ИИ подсказывает что можно сделать демонстрацию профита от моей системы на примере системы из publisher-subscriber и их миграции в новый формат. Попробую сделать. Спасибо за то что навели на хорошую идею примера!
Сделал статью "что-то вроде минимального примера" как вы просили: https://habr.com/ru/articles/978216/
Пойдёт?
тоесть вы хотите раскрутить цикл знать его кадр и текущий стек?
а зачем это нужно именно в текстовой информации это тогда сериализация на уровне языка получается
тогда проще написать виртуальную машину, если есть требование такой отладки, там и стек и данные вы будете контролировать и приемлемый уровень сериализации всего стека если захотите, но ценой исполнения вм(фиббоначи(24 100000 итераций) в 10 раз медленнее например я тестил у себя)
возможно можно как раз jit код сериализировать, но тут вопросов больше будет
тоесть jit код на цикл, метим цикл и он уходит в таблицу функции файла такого-то
ллвм может умеет такое
байт-код это частный случай сериализации, но раскрутка и фреймы стека выделяются во время исполнения например
вот например таблица программы
Скрытый текст
0 // ALLOCATE_STACK 1 4
3 // LOAD 1
5 // PUSH 1
7 // CMPLT
8 // JUMP_IF_FALSE 14
10 // LOAD 1
12 // JUMP 0
14 // PUSH 0
16 // STORE 2
18 // PUSH 1
20 // STORE 3
22 // PUSH 0
24 // STORE 4
26 // LOAD 1
28 // PUSH 1
30 // CMPGT
31 // JUMP_IF_FALSE 52
33 // LOAD 2
35 // LOAD 3
37 // ADD
38 // STORE 4
40 // LOAD 3
42 // STORE 2
44 // LOAD 4
46 // STORE 3
48 // DEC 1
50 // JUMP 26
52 // LOAD 3
54 // RET
55 // ALLOCATE_STACK 0 4
58 // PUSH 0
60 // STORE 1
62 // PUSH 0
64 // STORE 2
66 // PUSH 100000
68 // STORE 3
70 // LOAD 2
72 // LOAD 3
74 // CMPLT
75 // JUMP_IF_FALSE 88
77 // PUSH 24
79 // CALL 0 1
82 // STORE 1
84 // INC 2
86 // JUMP 70
88 // LOAD 1
90 // PRINT
91 // LOAD 2
93 // PRINT
94 // PUSH 0
96 // HALT
тоесть возможно лучше создать тогда какой-то скриптовый язык, встроить его в С++ и ему скармливать стек наверно или обьекты
но проблема еще в том, что стек это не данные, получается лисп код это данные-код
Да, я хочу знать всё в своих программах :)
а зачем это нужно именно в текстовой информации
текстовый формат у меня чисто для отладки
тогда проще написать виртуальную машину, если есть требование такой отладки
виртуальная машина не решает проблемы миграции данных между разными(старой и усовершенствованной) системами типов, но зато позволяет менять код что очень круто, правда, как вы верно заметили, ценой замедления программы в 10-50 раз, что для меня не приемлемо.
если конвенции стандартные со скоростью может помочь трансляция кода из таблицы в машинный код просто, просто получится тогда как шейдерный язык(аля шейдерный но на процессоре, но это много кода требует конечно, тоесть виртуалка+ассемблирование)
и наверно придётся отслеживать очередь запущенных оттранслированных таблиц типо
подход с виртуальной машиной как вы заметили даёт возможность удобств каких-то, но некст уровень или альтернативный гнать из байткода в ассемблер
поидее можно создать живую вм и создать функционал обновления типа наверно не знаю пока
ну форт или лисп получается, ну clojure вроде нормально, совмещает обе концепции
мы здесь не сериализуем некоторые вещи связанные с выполнением программы, а именно:
не сохраняем стек вызовов
не сохраняем PC / позицию выполнения
не трогаем низкоуровневые детали вроде сетевых или GPU-состояний
мы тут сериализуем почти всё состояние данных, включая ссылки и типы, чтобы:
обновлять бинарь
мигрировать структуры
продолжать работу без VM
VM/JIT решают другую задачу - управление кодом, а не миграцию данных между версиями типов.
можно транслировать в бинарь, ладно я вас понял, извините, так состояние можно кидать на байткод и его патчить(в вашем случае есть 2 опции, посмотреть для отладки наверно или миграции или обновлении/транслировать в бинарь получается), а транслятором собираем бинарник, при этом виртуалка должна быть с реализованным языком(это я забыл указать, тоесть там получается вы не просто вм встраиваете в С++, а по-сути DSL как я понимаю с ништяками), получается некий велосипед если самому с анси си стартовать, но по итогу имеем комплекс отладка/патчинг/прогонка, там элегантно получается, ладно извините(про гпу я писал как пример, но кстати пример с гпу собирает тоже бинарь)
попросил ИИ перевести на понятный мне язык, вот его цитата:
«Я понял, что вы не сериализуете стек и выполнение.
Тогда, если уж есть описание состояния, его можно:
представить как байткод
патчить
при желании транслировать в нативный код
Но для этого придётся сделать язык + VM + транслятор,
то есть фактически DSL, а не просто сериализацию данных.
Это сложно, похоже на изобретение велосипеда,
но в итоге можно получить мощный комплекс отладки и патчинга.»
интерпретация от ИИ:
Он согласился с тобой, но:
не может выйти из парадигмы «всё = код»
поэтому постоянно тянет разговор к VM / DSL / байткоду
Ты же работаешь в другой оси:
данные эволюционируют
код остаётся нативным
Он этого ментально не различает.
вот что я думаю читая это:
что бы я сделал если бы нашёл кого-то кто про финансирует open-source разработку? - свой язык программирования который сразу без танцев с макросами и шаблонами понимает мою систему RTTI/STTI/указатели и при миграции между форматами генерирует настоящий нативный код для сохранения и загрузки состояния в этом новом формате, плюс ещё если к этому добавить safe_eval+unsafe_eval, то вообще было бы шикарно.
если элегантно воспользоваться деревом в сцене и деревом в ui, я не знаю какой проект у вас допустим 3д, есть 3д-обьект, 2д-обьект, есть таблица таблица это интерпретация функции некоего блюпринта, чтобы создать узел визуальный будь 2д-обьект прикрепленный к 3д-обьекту, надо создать функцию-таблицу, интерпретировать её, и эту связь положить на деревья обьектов(например персонаж с текстурой над ним - иконкой), значит узлы можно кодировать отдельно в текстовом формате, просто при запуске, мы билдим эти деревья учитывая таблицы.
как это по переносимости, так вот я вам описал как это можно сделать, можно языком в виде dsl, можно просто парсить, просто эта логика будет как отдельный елемент вашей реализации, а не как макрос, и обьекты в нодах сцены и нодах юи уже стандартизированы будут если это, например BVH ноды - а именно 2 разных корня под юи и текущую сцену
тоесть мы создаём 2 функции на 1 блюпринт в котором у нас связаны какието 2 обьекта, а именно 3добьект с 2д обьектом, из таблицы(принципиально это слово означает создание держателя ресурсов) ресурсов, которую при инициализации можно получить
в конкретном случае мы смотря от реализации либо остаёмся в изоляционных нодах, тогда парсер билдит связи по координатам, или генерируем взависимости от подхода новый уникальный узел, мне просто изоляция понравилась
ИИ подсказывает что вы хотите узнать зачем вам вообще мигрировать типы, если можно описывать всё через таблицы и каждый раз собирать сцену заново? То есть вы предлагаете перейти на декларативную сборку сцены тому кто занимается задачей "миграцией долгоживущего состояния сцены + эволюции типов"? Если так то спасибо за идею, но мне мой подход больше нравится. почему/зачем я так делаю/сделал? - потому что раньше мне казалось что это путь к идеальной архитектуре программ, а перестирать сцену каждый раз - это слишком простой/слабый/тупиковый путь. сейчас я менее категоричен и вижу плюсы обоих подходов и думаю что будущее за гибридным решением.
смотрите, есть такая ситуация, дополню то что я выше написал, 3 точки
инициализация ресурсов
инициализация 2х деревьев т.к. они приведены к абстрагированному обьекту (дерево 3д, дерево 2д)
состояние отрисовки(отдельно проход сцена с пасами, отдельно юи)
обновление
теперь представим такую ситуацию, код С++ как решето запускает только 3 точки, всё остальное работает как емакс,
вы имеете порядок связей подобно стеку вызовов
дерево поидее в конкретном узле найдя в стеке вызовов произведенную связь изменить в двух деревьях адресно 2 обьекта, удалив-занулив эти 2 узла, и либо оставить их так, либо на место нулей поставить другие обьекты, другую модельку или другую текстуру над моделькой
соотв всю ситуацию с ресурсами можно куда-то перенести - назовём это что-то окружением только для работы с этим, язык который я предложил в первом коментарии работает по виртуальному принципу стеквой логики, тоесть у нас обьекты становятся и таблица ресурсов обьектов обьектами языка или ими можно управлять в над С++ ье и получается С++ ничем не управляет, а только делает инит, инит деревьев,рисует и апдейт. Тоесть даёт в виртуалку сначала ресурсы, потом эти деревья, а языком мы связываем что нам нужно
На вас подействовало проклятье $mol'а :)
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md

Как сериализовать всё состояние C++-программы и пережить обновление бинарника