Skip to content

refactor(animate): формализовать поколение поверхности и owner lease в GroupRecord #253

Description

@lemone112

Контекст

Последние исправления реентрантности (#196, #207, #209, #210) довели текущую реализацию до корректного поведения при hostile host:

  • host-write может синхронно запустить successor;
  • successor подхватывает уже видимое значение и скорость;
  • старый owner после потери владения не публикует stale write-back;
  • settle-поколение несёт нулевую производную;
  • бросок host сохраняет последнее успешно rendered-поколение;
  • lifecycle не зависит от поздней подмены Promise/queueMicrotask.

Поведение уже защищено сильными differential и hostile-host тестами. Issue не про исправление известного пользовательского бага и не про смену публичной семантики. Это следующий архитектурный шаг: сделать существующий инвариант поколения явной моделью данных.

Проблема

Сейчас понятие «поколение поверхности» распределено между связанными полями и фазами выполнения:

  • live / applying value;
  • rendered value;
  • live / rendered velocity;
  • _tweenK / _renderedTweenK;
  • _writing;
  • owner / reservation / transition;
  • residual snapshot;
  • settle и successor paths.

Корректность достигнута, но часть закона выражена согласованностью нескольких mutable-полей. Новая реализация GroupOwner или изменение capture/write/settle может воспроизвести класс ошибок #196, если не повторит неявный протокол полностью.

JSDoc GroupOwner уже фиксирует это как временное состояние и указывает направление: перенести инвариант в generation/lease на GroupRecord.

Цель

Сделать SurfaceGeneration и OwnerLease явными внутренними сущностями и единым источником истины для capture, host-write, rendered snapshot, settle и supersede.

Пример концептуальной формы — не обязательный API:

interface SurfaceGeneration {
  readonly id: GenerationId
  readonly numeric: ReadonlyMap<string, ChannelSnapshot>
  readonly css?: CssSnapshot
  readonly phase: 'applying' | 'rendered' | 'settled'
}

interface OwnerLease {
  readonly generation: GenerationId
  readonly owner: GroupOwner
}

Фактическая структура должна быть выбрана по hot-path стоимости и size-бюджету. Не следует механически добавлять объекты/Map/строковые discriminant в runtime.

Инварианты

  1. Одна наблюдаемая поверхность — одно поколение. Значения и скорости всех каналов transform-группы публикуются атомарно.
  2. Applying уже наблюдаемо. Во время host setter реентрантный capture видит применяемое поколение, хотя rendered commit фиксируется только после успешного возврата host.
  3. Rendered означает успешную host-запись. При throw последнее rendered-поколение не меняется.
  4. Settled несёт скорость покоя. Все числовые velocity и CSS derivative равны нулю.
  5. Lease определяет право публикации. Owner после supersede не может выполнить write-back, release или terminal publication.
  6. Successor наследует единый snapshot. Нельзя смешать value текущего поколения с velocity предыдущего.
  7. rAF и WAAPI следуют одному контракту. Различаются механизмы host transaction, но не семантика поколения.
  8. Residual state не является вторым источником истины. Это производное представление подтверждённого поколения.

Требования к дизайну

  • сохранить публичный API и наблюдаемую семантику;
  • не ухудшить continuity при interruption/supersede;
  • не добавить аллокации на кадр;
  • не добавить Promise/deferred на unit;
  • не ухудшить Nano/Full size gates;
  • не переносить host-specific детали в общий semantic layer;
  • удалить _writing и параллельные live/rendered поля только если новая модель полностью заменяет их; не создавать третий параллельный канал;
  • один SSOT для generation identity и lease validation;
  • failure atomicity должна следовать структуре, а не порядку ручных присваиваний.

Предлагаемый порядок TDD

RED — модельные тесты

Сначала зафиксировать контракт через независимую модель поколений:

  • applying → reentrant capture;
  • applying → host throw;
  • applying → successor supersede;
  • settle → reentrant successor;
  • stale owner → write-back/release no-op;
  • numeric и CSS поколения не смешиваются;
  • rAF/WAAPI trace-equivalence для одинаковой поверхности.

GREEN — минимальный перенос

  1. Ввести generation/lease primitive без изменения поведения.
  2. Перевести capture на чтение поколения.
  3. Перевести host-write commit/rollback.
  4. Перевести write-back и settle.
  5. Перевести WAAPI reservation/release.
  6. Удалить заменённые параллельные поля и временные ветви.

REFACTOR — закрытие долга

  • удалить временный _writing-протокол;
  • сократить JSDoc с историей багов до устойчивых инвариантов;
  • проверить, что GroupRecord не хранит две независимо изменяемые версии одного состояния;
  • оставить mutation/property tests, запрещающие возврат к split-generation модели.

Приёмка

  • все существующие hostile-host и continuity тесты проходят без ослабления;
  • browser differential проходит на Chromium, Firefox и WebKit;
  • новый model/property corpus покрывает interleaving поколений и lease;
  • нет новых frame-path аллокаций;
  • bundle size не регрессирует сверх установленного бюджета;
  • benchmark start/frame/supersede не показывает значимой регрессии;
  • _writing либо удалён, либо доказано, почему он остаётся частью единой generation-модели;
  • в коде существует один явный ответ на вопросы:
    • какое поколение сейчас применимо;
    • какое последнее успешно rendered;
    • кто имеет право его опубликовать;
    • какое поколение должен получить successor.

Не входит

  • изменение публичного API animate;
  • новые animation features;
  • изменение spring/tween математики;
  • общий переписанный scheduler;
  • усложнение Nano ради внутренней эстетики.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions