Cheb's Home Page
 
 
 
Orphus system

Cheb's Home Page

Главная
Cheb's Game Engine Косметическая подтяжка Quake II
Штошник на ушах
 

 



Chepersy

Это система перзистентности, или объектная база данных (ODBMS) для Free Pascal. Разрабатывалась как основа для игрового движка, но годится для любых применний.

Перзистентность — свойство данных сохраняться между запусками программы. Соответственно, система перзистентности призвана обеспечивать полную сохраняемость объектной структуры данных, а также её совместимость между разными версиями программы.

Файлы - на SourceForge >>

 

Последняя условно-стабильная версия - 0.8.2
Последняя доступная версия - 0.8,99
Версия в разработке (на ноябрь 2014) - 0.9.00

Отличия от 0.8.2:
 - полностью заменён API
 - добавлена возможность обхода графа объектов
 - добавлен гибко настраиваемый сборщик мусора
 - теперь можно использовать массивы из записей и перечислимых массивов
 - изменена парадигма обработки ошибок
 - перестроена внутренняя архитектура.


Changelog 0.8.98 -> 0.9.00:
 1. stream format updated, with header md5 now saved before the header
 2. header and scenario caching, based on the md5 checksums. For each distinct checksum, header is loaded and parsed only once per program execution.
 3. support for headerless streams
 4. TManagedObject.Clone()
 5. Fixed the bug with doubling the field lists in the log.
 6. TManagedObject.Resurrect()
 7. new optional parameter OutputList in CpsLoad()
 8. Scrape made virtual
 9. Added 64-bit fields validation that raises an error if these are not aligned to a 64-bit boundary
 10. Added stubs for future support of the "fixed32" type.

TODO list for 0.9.01:
 1. Make sure the alignment prediction and validation does work without the forced packing with {​$packrecords 4}
 2. Mmake posssible registering types that are aliases of Double (e.g. TDateTime) 3. Implement the fixed32 type and its converters.

TODO list for future versions:
 1. As soon as FPC 2.8 is out, implement handling strings that have a variable code page
 2. As soon as FPC 2.8 is out, make sure {​$optimization noorderfields} works as intended

Подробное описание

Оверхед для программиста

Необходимо предоставить системе перзистентности информацию о всех классах, полях и их типах. Поскольку я не волшебник, а пока только учусь - это делается вручную, посредством специального API. Процесс называется «регистрацией». Чтобы подсластить пилюлю, регистратор снабжён валидатором и умеет подробно объяснять ошибки (см. раздел «регистрация»).

Оверхед для машины

- Дополнительное 64-битное поле в каждом классе — и это всё!

 

Это документация к промежуточной версии 0.8,99, которя является незаконченной 0.9. Часть функций может быть не реализована, также возможны нестыковки между API и документацией.


Плюсы

- Совместимость файлов данных «назад» и ограниченная «вперёд».

- Поддержка кольцевых графов и перекрёстных ссылок между объектами.

- Поддержка выборочного сохранения части объектов по маске

- Сохранение совместимости при изменении типов полей (integer / real или ansistring / widestring).

- Высокая скорость сохранения и загрузки структуры данных (при тестировании достигала миллиона объектов в секунду).

- Специальные виртуальные методы, позволяющие создавать свои обработчики «перед сохранением» и «после загрузки».

- API, позволяющий обходить дерево объектов: не совсем OQL, но всё равно мощно.

- Настраиваемый сборщик мусора, запускающийся в ручном или автоматическом режиме.

Минусы

- Плохая совместимость с многопоточностью

- Медленное развитие одним человеком. Моя главная цель - мой игровой движок, Chepersy - побочный продукт его разработки, не более, но и не менее (ибо это, всё-таки, ключевой компонент оного движка)

- Отсутствие поддержки для других платформ кроме intel-32 (технически, порт на PowerPC возможен, но очень маловероятен)

- Необходимость ручной регистрации классов

- Можно использовать только классы, являющиеся потомками TManagedObject. То есть, если вам понадобится TstringList - придётся писать собственный.

- Только FreePascal. Поддержка Turbo Delphi полностью прекращена начиная с 0.8.99.

- Потенциальная несовместимость с новыми версиями компилятора: система - один большой хак.

ПАРАДИГМА

(Каким образом оно должно использоваться)

- Объектная структура должна иметь один корневой объект.

- Все ваши классы должны быть потомками TManagedObject, обеспечивающего основную функциональность Chepersy.

- Одним из краеугольных камней являются перечислимые типы и та их особенность, что в Паскале все числовые значения константам назначает компилятор. Именно использование массивов, индексируемых перечислимыми типами, позволяет добиться наибольшей гибкости при сохранении совместимости между версиями программы, поскольку методы сериализации ориентируются на имена констант а не на их абсолютные значения.

БЕРЁМ И ПРИВИНЧИВАЕМ

(Что нужно программе, чтобы использовать Chepersy)

1. Добавьте {$include chepersy_defs.inc} в начало каждого своего модуля, до ключевого слова unit. Иначе ваша программа или просто не пойдёт (в лучшем случае) или будет генерировать запоротые файлы данных (в худшем) и грохнется потом, вместо того чтобы преобразовать их.

2. Добавьте в список uses модули typinfo, chepersy.

3. Зарегистрируйте свои типы и классы. Подробнее — в разделах «Регистрация типов» и «Регистрация классов».

4. После того, как все ваши типы и классы зарегистрированы, вы можете использовать ф-ии

function CpsStore(o: TObject; Target: TStream;
    XorMask: dword = $ffffffff; AndMask: dword = $ffffffff): longbool;
function CspLoad(Source: TSTream): TObject;

, где o — корневой объект вашей структуры данных.

Ответственность за создание и удаление потоков, а также установку их позиции в ноль, лежит на вас. Поддержка Seek() от потоков не требуется, что позволяет использовать потоки сжатия.

Два последних параметра относятся к выборочному сохранению объектов по маске, вы можете просто опустить их, если никогда до этого не использовали поле CpsMask. Подробнее - в разделе «Обход графа».

Первый же вызов любой из этих функций делает дальнейшую регистрацию невозможной.

5. (важный момент) Не забывайте: при загрузке из файла конструкторы *не вызываются*! Вместо этого объект создаётся напрямую, вызовом NewInstance(). Используйте виртуальный метод AfterLoading(), если ваш класс как-то связан с внешними ресурсами, не входящими в сохраняемую структуру данных (например, контекстами или именами текстур OpenGL).

РЕГИСТРАЦИЯ ТИПОВ

(с радостью переложил бы эту работу на плечи программы — если бы мог)

Для регистрации типов и полей требуется подключить стандартный модуль FPC, typinfo, в котором определены нужные функции. Практически все процедуры регистрации типов и полей принимают в качестве параметра результат вызова TypeInfo(). Ксожалению, RTTI несовершенна, и некоторые вещи приходится задавать вручную.

Замечание: попытка зарегистрировать уже зарегистрированный тип молча игнорируется.

Итак, знакомьтесь: ваш лучший друг, процедура RegType();

Целые и вещественные числа, строки: Всё, что из этого есть в Паскале, уже зарегистрировано.

Указатели: Не регистрируются. Несовместимы с парадигмой Chepersy. Класс может иметь поля таких типов, но они всегда опускаемые (см. раздел «Регистрация классов»).

Классы: см. раздел «Регистрация классов»

Метаклассы:

Считаются уже зарегистрированными для всех известных классов. Неизвестные метаклассы будут, так же как и классы, приведены к известным предкам, но никаких проверок при этом не делается (поскольку система считает их все базовым метаклассом CManagedObject).

Перечислимые:

RegType(TypeInfo(ВашТип));

Для этих типов вся нужная информация автоматически берётся из RTTI. Модуль, где определён перечислимый тип, должен быть откомпилирован с директивой {$MINENUMSIZE 4} (уже входит в состав chepersy_defs.inc). Перечислимый тип может быть подмножеством (subrange type) вроде 0..20, но его минимальным значением должен быть 0, а максимальное — не больше 255.

Перечислимые массивы (т.е. индексируемые перечислимыми типами):

RegType(TypeInfo(ВашТип), TypeInfo(БазовыйТип), TypeInfo(ИндексныйПеречислимыйТип));

RegType(TypeInfo(ВашТип), '*ИмяБазовогоТипа', TypeInfo(ИндексныйПеречислимыйТип));

RegType('*ИмяВашегоТипа', '*ИмяБазовогоТипа', TypeInfo(ИндексныйПеречислимыйТип));

При изменении порядка и состава констант индексного перечислимого типа, процедуры сериализации автоматически переставят элементы такого массива, удалят лишние, заполнят новые нулями.

Динамические массивы:

одномерные:

RegType(TypeInfo(ВашТип), TypeInfo(БазовыйТип));

RegType(TypeInfo(ВашТип), '*ИмяБазовогоТипа');

N-мерные:

RegType(TypeInfo(ВашТип), N, TypeInfo(БазовыйТип));

RegType(TypeInfo(ВашТип), N, '*ИмяБазовогоТипа');

Уровень вложенности не ограничен, но в настоящеее время лишь одно-и двумерные массивы неизвестных типов могут быть "разрулены" системой, более сложные сделают файл данных нечитаемым, если их базовый тип неизвестен.

Если ваш многомерный массив состоит из одномерных, которые вы тоже регистрируете (напр. Type A = array of Z; B = array of A;) то *обязательно* регистрируйте B как одномерный массив с базовым типом A. Регистрация его как двумерного с базовым Z может вылиться боком когда вам потребуется обратнася совместимость.

 

Замечание по незавершённой 0.8,99: пока не доделано! МинИндекс должен быть 0, массив должен быть одномерным, конверсия размера при чтении не поддерживается!

Отличия от v0.8.2: Просто добавьте звёздочку к своему старому названию типа - и совместимость файлов данных сохранится. Внутреннее представление не изменилось, звёздочки нужны только для того, чтобы регистратор мог отличать имена полей в списках от имён типов!


Статические массивы:

RegType(TypeInfo(ВашТип), TypeInfo(БазовыйТип), [МинИндекс1, МаксИндекс1, ... МинИндексN, МаксИндексN]);

Строковое имя обязано начинаться со звёздочки.

Множества:

RegType(TypeInfo(ВашТип), TypeInfo(БазовыйПеречислимыйТип));

Позволяются только множества, построенные на перечислимых типах.

При изменении порядка и состава констант индексного перечислимого типа, процедуры сериализации автоматически перетасуют биты множества.

 

Отличие от v0.8.2: (где при опускании типа он брался от *предыдущего* поля): теперь более приближено к синтаксису Паскаля, опущенный тип берётся от первого последующего поля, для которого он указан!

Записи:

RegType(TypeInfo(ВашТип), SizeOf(ВашТип), [<описание полей>]);

Здесь вы даёте вашему типу какое-то имя (обязательно добавляя в начале звёздочку), которое потом используете при регистрации полей класса вместо TypeInfo(ВашТип). Естественно, никакие проверки при этом не производятся.

Упакованные записи распознаются от неупакованных по предоставленному вами размеру.

Формат описания полей: состоит из последовательности групп параметров. В каждой группе - два параметра: имя, тип. Второй параметр можно опускать в произвольном порядке, будет взят от следующего поля. По умолчанию - typeinfo(dword).

Имя - ansi строка. Минус в начале означает опускаемое поле: "-bunny" зарегистрирует поле с именем "bunny", которое будет игнорироваться при записи и заполняться нулями при чтении.

Тип может задаваться, как:

 а. typeinfo(ВашТип).

 б. строковое имя к которому в начале добавлена звёздочка.

 в. константа CPS_POINTER или строка "*pointer" - для указателей и указателеподобных опускаемых полей.

 г. опущено. Будет взято от следующего поля. По умолчанию dword.

Образцы:

RegType(TypeInfo(TMyRecord), sizeof(TMyRecord), ['a', 'b', typeinfo(integer), 'c', typeinfo(byte)]);

RegType('*TMyRecord', sizeof(TMyRecord), ['a', '-b', 'c', '*мой статический массив']);

Важное замечание: у регистратора нет *никакой* возможности проверить, правильно ли вы указали порядок полей записи - он может лишь проверить общий размер полученного безобразия. Кроме того, берегитесь директивы {$packrecords ...} — она может стать вашей погибелью.

РЕГИСТРИРУЕМ КЛАССЫ

(Я бы не меньше вашего хотел, чтобы это всё автоматом происходило. Если бы желания были рыбами...)

 

Важное замечание: в v0.8.95 я на короткое время добавил механизм для использования любых классов, а не только потомков TManagedObject.

Я удалил этот механизм в 0.8.96: он не только был неудобным и ненадёжным, но и содержал алгоритмическую чёрную дыру, разбираться с которой я не хочу - да и нет насущной необходимости. Поддержка произвольных классов убрана навсегда.

1. Унаследуйте свой класс от TManagedObject, переопределив виртуальный метод RegisterFields().

Именно в этом методе вы выписываете регистрацию всех полей класса. В идеальном мире достаточно было бы просто сделать все поля published — но увы, RTTI Паскаля слишком слаба.

type TМойКласс = class (TManagedObject)
  а, b: TблаБла;
  c: integer;
public
  procedure RegisterFields(); override;
end;

 

Отличие от v0.8.2: (где при опускании типа он брался от *предыдущего* поля): теперь более приближено к синтаксису Паскаля, опущенный тип берётся от первого последующего поля, для которого он указан!

2. Зарегистрируйте поля

Эту операцию выполняет метод RegisterFields вашего класса, вызываемый автоматически из RegType()/RegClass() (см. ниже).

Сначала вы регистрируете типы полей своего класса(если они ещё не регистрировались).

Потом вы вызываете inherited, который регистрирует типы и поля предка.

Потом вы вызываете процедуру ListFields, которой даёте список полей, (обязательно в том порядке, в котором они объявлены) в следующем формате:

1. Имя. Не обязано совпадать с реальным именем поля в программе, назовите хоть '$@# ,,балалайка'. Но оно не должно начинаться со звёздочки, а если начинается с минуса - это означает, что поле опускаемое.

2. Адрес поля.

3. Тип. Это может быть TypeInfo(ВашТип), строковое имя с добавленной в начале звёздочкой, константа CPS_POINTER, строка "*pointer", константа CPS_METACLASS или строка "*metaclass". Тип можно опустить, тогда будет взят от следующего. По умолчанию - dword.

procedure TМойКласс.RegisterFields();
begin
 RegType(TypeInfo(БлаБлаБла), ...);
 inherited;
 ListFields([
  'a', @a, //TypeInfo можно опустить, будет взято от следующего поля.
  'b', @b, TypeInfo(БлаБлаБла),
  '-c', @c, TypeInfo(integer) //опускаемое поле
 ]);
end;

Валидатор проверяет, покрывают ли поля весь экземпляр класса, без разрывов (с учётом выравнивания, задаваемого в chepersy_defs.inc) и при наличии дыр или нахлёстов генерирует ошибку, с подробным описанием проблемы - оставляя единственной лазейкой для человеческого фактора указание неправильного типа поля. Кстати, это одна из главных причин, по которым я создал Chepersy, а не удовлетворился стандартным Паскалевским TPersistent: я бы при моей эпической рассеянности таких бы ляпов насажал, что от одной мысли кровь стынет в жилах..

3. Вызовите RegClass(TМойКласс) или RegType(typeinfo(TМойКласс))

При этом (без вызова конструктора) создаётся экземпляр класса, вызывается его метод RegisterFields(), и экземпляр изничтожается.

Я долго пытался найти обход чтобы не создавать экземпляр, но нужные функции без такового просто не работают. Увы.

Внимание: если в этот момент предок вашего класса ещё не зарегистрирован то будет выполнена его регистрация - и так у попа была собака пока не упрётся в сам TManagedObject..

4. Виртуальные методы BeforeSaving() и AfterLoading()

Позволяют вам настраивать всякие хитрые трюки и вручную разбираться с опускаемыми полями (вроде тех, где вы храните системные хэндлы или имена текстур OpenGL).

BeforeSaving (ПередСохранением) вызывается перед тем, как конкретный объект будет записан, по мере обхода дерева объектов (т.е. часть объектов может быть на этот момент уже записана ).

AfterLoading (ПослеЗагрузки) вызывается после того, как *все* объекты будут прочитаны из потока - т.е. любые объектные поля в этот момент действительны. Первым будет вызван у объекта, записанного последним. Благодаря чему в момент вызова этого метода, все объекты, на которые указывают его поля, уже инициализированы.

АВТОКОНВЕРСИЯ ТИПОВ

(Вы слишком поздно осознали, что вон то поле должно было быть GLfloat а не integer...)

Уже встроена и работает поддержка всех числовых типов Паская, как целых так и с плавающей запятой. Включая массивы любой размерности из оных.

Возможность расширения пользователем пока убрана, так как требует доступа к внутренней структуре данных Chepersy, а та пока не устоялась окончательно.

ОБХОД ГРАФА

(Обход и маркировка графа, получение от Chepersy информации о полях)

Это то, что возвышает Chepersy от обычной системы перзистентности до настоящей, хотя и не совсем полноценной, базы данных.

Всё просто, как мычание. Вы создаёте процедуру, соответствующую этому шаблону:

type TcustomWalkProc = procedure(o: TManagedObject);

затем вы вызываете

function CpsWalkGraph(o: TManagedObject; proc: TcustomWalkProc): boolean;

, передавая вашу процедуру в качестве параметра. Ваша процедура будет вызвана по одному разу на каждый объект, входящий в граф, растущий из указанного вами корневого объекта.

Уже вам решать, чем будет заниматься ваша процедура: отфильтровывать объекты по какому-то критерию, вызывать их методы, или раскрашивать их в полосочку.

Битовые маски

Начиная с v0.8.95, у класса TManagedObject появилось поле CpsMask: dword. Оно используется для выборочного сохранения объектов, предоставляя вам 32 независимых флага, выбираемых параметрами XorMask и AndMask функции CpsStore(). Алгоритм следующий: если ((<некий объект>.CpsMask xor XorMask) and AndMask) = 0, то такой объект сохранён не будет, а после загрузки на его месте окажется NIL. На размер массивов из объектов это не влияет, они просто будут содержать элементы, равные NIL. Метод AfterLoading вам в руки.

Таким образом, XorMask задаёт, какие биты маски инвертировать, а AndMask - какие после этого проверять. Объект будет сохранён, если хотя бы один проверяемый бит установлен.

Не забывайте: маска - часть объекта, она сохраняется и загружается вместе с ним! Однако,

function CpsMarkupGraph(o: TManagedObject; SetMaskBits, ClearMaskBits: dword): longbool;

- позволяет вам метить весь объектный граф разом. SetMaskBits - какие биты устанавливать, ClearMaskBits - какие очищать.

Начиная с v0.8.99, биты 30 и 31 зарезервированы за сборщиком мусора. Попытка изменить их через CpsmarkupGraph вызовет Assertion Error, но это не отменяет осторожности при ручной работе с полем маски.

Теперь Cpsmask - свойство, связанное с полем f_cpsmask. Когда задан кондишнл safeloading, при записи в него производится дополнительная проверка, не пытаетесь ли вы изменить зарезервированные биты. При провале - тот же Assertion Error. Это хороший пример гибкости: я изменил имя поля в коде программы, но оставил старое имя 'CpsMask' в процедуре регистрации, такм образом сохранив полную совместимость.

СБОРЩИК МУСОРА

(или «нам только C# не хватало»)

Сборщик мусора - мощнейшее средство обеспечения безопасности, не позволяющее окончательно удалить объект пока на него ссылаются другие объекты (пример: бодал баран козла. Но пока он разгонялся, козла удалили. И словили мы Access Violation при вызове метода Баран.Боднуть(Баран.Козёл). А со сборщиком мусора ничего не случится: дохлый козёл будет лежать на кладбище пока баран не забудет о нём, и будет выметен из памяти при очередной сборке мусора.

Но цена безопасности высока: периодически нужен полный обход графа, а он при большом количестве объектов может занять неприемлемо большое время.

Сборщик мусора лежит в глобальной переменной GarbageCollector (инициализируется автоматически при старту Chepersy).

По умолчанию сборщик ведает только теми объектами, у которых вы вызвали метод Scrape, и теми, что были опущены при чтении.

Если установить глобальную переменную CpsFullManagement в true (что происходит автоматом при включённом кондишнле safeloading), то деструктор TmanagedObject.Destroy будет вызывать Scrape вместо унаследованного Destroy - таким образом, ни один объект не будет удаляться, все будут поступать на кладбище сборщика мусора. Также деструктор никогда не работает у объектов, помеченных как Scraped (выброшенные на помойку) - удалить эти объекты может только сборщик мусора обойдя весь граф и убедившись, что они Orphaned (бесхозные) - т.е. что на них не ссылается ни один объект графа.

По умолчанию, сборщик мусора не запускается автоматически, и scraped объекты будут накапливаться пока у вас не кончится память. Существуют два пути:

1. Вручную вызывать GarbageCollector.Collect() когда вам удобно. В качестве единственного необязательного параметра этот метод принимает 64-битное целое, задающее ограничение по времени в тактах процессора, согласно формату ассемблерной команды RDTSC. Естественно, обход графа разделить на части невозможно, он займёт столько времени сколько займёт, независимо от ограничения по времени. Так что механизм ограничения времени полезен если у вас много объектов с тяжёлыми деструкторами (например, записывающие что-то в файл при удалении). Возможен вариант, когда вызов Collect() закончится одним обходом графа, а ни один объект удалён не будет: это действие будет отложено до следующего вызова Collect().

2. Установить GarbageCollector.Autorun.Enabled, a также следующие параметры записи Autorun:
NumObjectsToActivate - размер кладбища, при достижении которого включается автоматическая сборка мусора (при вызове метода Add() );
NumObjectsToIgnoreTimeConstraint - размер кладбища при превышении которого котором будет игнорироваться ограничение по времени;
TimeConstraint - ограничение по времени.

В любом случае вы должны указать сборщику мусора корневой объект своего графа в GarbageCollector.GraphRoot. Пока это поле равно nil, метод Collect() только дочищает объекты, ранее помеченные как бесхозные, а при отсутствии таковых - ничего не делает. Поле GraphRoot никогда не выставляется автоматом - вы должны сами заботиться, чтобы оно содержало корень вашего графа. Иначе будет беда.

Размер кладбища можно узнать посредством GarbageCollector.GraveyardCount.

ОБРАБОТКА ОШИБОК

(Реакция на нештатные ситуации)

Любые ошибки при регистрации поднимают исключение: работа системы невозможна, и программа должна аварийно завершиться.

Вы должны сами ловить исключение и запрашивать у Chepersy сообщение об ошибке (см. ниже).

Все ошибки и исключения при чтении, записи и обходе дерева объектов инкапсулируются. Поскольку соответствующие функции возвращают либо булево значение, либо объект, то в случае ошибки возвращаются соответственно false и nil. Для получения более подробной информации служат

CpsParserWarnings, //предупреждения, получаемые при чтении

CpsLog, //просто подробный лог всех событий

CpsError //заполняется при возникновении исключений и неразрешимых ситуаций.

  : TStringList;

, где кодировка сообщений: для FPC - всегда utf-8

Элементы CpsError следует выводить в обратном порядке, тогда вы получите внятное структурированное объяснение.

По возможности, при крахе производится откат с удалением уже прочитанных объектов - но нет 100% гарантии отсутствия утечек памяти и сохранения работоспособности программы. Система изначально разрабатывалась по схеме, когда любой сбой Chepersy приводил к аварийному завершению программы.

Объекты, пропущенные при чтении

Таковые автоматически заносятся на кладбище сборщика мусора. Подробнее см. в разделе «Сборщик мусора».

БЫСТРОДЕЙСТВИЕ

(Как добиться большей гибкости и скорости сериалицации)

1. Используйте перечислимые типы где только возможно

Перечислимые типы - ключ к гибкости. Они - самый подходящий способ описывать: части тела, школы магии, эффекты заклинаний, типы повреждений, типы ландшафта, типы монстров, режимы перемещения, виды спецэффектов, наборы опций и бесконечное множество других вещей, где конкретный список вариантов склонен меняться в ходе разработки.

2. Ускоренные типы

Старайтесь по возможности ограничиться следующими типами: longbool, longint, dword, int64, qword, single, double и любыми перечислимыми. Процедура сериализации умеет "склеивать" поля этих типов, трактуя их как единый бинарный блок, что заметно ускоряет сохранение и загрузку.

Все остальные типы (включая строки, множества, классы, массивы, Byte, ShortInt, Word, SmallInt и Extended) требуют как минимум один (чаще два) вызова процедуры на каждое поле. Исключением здесь служат лишь записи и массивы, индексируемые перечислимыми типами — поля этих типов при регистрации класса разлагаются на составляющие компоненты, внедряемые в класс как отдельные поля с составнями именами. Эти поля будут ускорены, если удовлетворяют вышеописанным требованиям и они выровнены (на 32 бита по дефолту).

Динамические массивы ускоряемых типов хоть и потребляют один вызов процедуры, но отрабатывает она быстрее, чем у массивов типов неускоряемых (тут один слив всей туши как бинарного блока, там - по вызову на каждый элемент массива).

Естественно, когда списки полей класса в текущей версии программы и загружаемом файле не совпадают, ускорение становится невозможным, и класс преобразуется поле за полем, замедляя загрузку раза в 3-4. Но это решается на поклассовом уровне, неизменившиеся классы будут по прежнему грузиться с полным ускорением.

СОВМЕСТИМОСТЬ

(С какими компиляторами тестировалось и отлаживалось)

(Только intel-32!)

Free Pascal 2.2.0: совместима.

Free Pascal 2.2.2rc1: совместима.

Free Pascal 2.2.2: совместима

Free Pascal 2.4.0: совместима, на нём идёт вся разработка и тестирование.

Любые версии Delphi: Несовместима и не планируется.