К содержимому

9. Компиляция и генерация

A .sharq single-file component next to the plain C# class SharqFileImporter generates from it on save

Пайплайн

Saving .sharq to Assets/

    ▼ AssetPostprocessor.OnPostprocessAllAssets
    ├── SharqFileImporter.ProcessSharq()
    │   ├── SharqFileParser → SharqFileModel (template, script, style)
    │ ├── SharqSectionCache → incremental regeneration
    │ ├── SharqValidator → diagnostics (warnings)
    │   ├── BuildMethodGenerator → .g.cs
    │   ├── ScopedCssGenerator → _scoped.g.uss
    │   └── StyleParser → .g.uss (global)

    ▼ Result:
        Assets/SusUI/Generated/ComponentName.g.cs
        Assets/SusUI/Generated/ComponentName_scoped.g.uss
        Assets/SusUI/Generated/ComponentName.g.uss (global)

Инкрементальная компиляция

  • Content-hash: если .sharq не изменился — пропускается
  • Section-level: если изменился только <style> — перегенерируется только .uss (hot reload без C# domain reload)
  • SharqAutoRegenerate: при Domain Reload проверяется актуальность

SharqValidator — диагностика

ПроверкаУровень
v-for без :key (при StrictVForKey: true)Warning
Неиспользуемые поля в <script>Info

Сгенерированные файлы

Assets/SusUI/
├── Component.sharq ← source (you can edit here, in any subfolder)
├── Resources/SusRuntime/ ← author's runtime resources (in git): Icons/, Fonts/, theme-overrides
└── Generated/ ← auto-generated (.gitignored)
    ├── Component.g.cs
    ├── Component_scoped.g.uss
    ├── Component.g.uss
    ├── Component_static.g.uss
    ├── Component.sections.json
    ├── Component.sharq.hash
    ├── .gitignore
    └── Resources/SusRuntime/ ← synced USS components for runtime (auto)

Базовые тема/иконки/шрифты (_theme, _icon, _font, design-tokens) поставляются в самом пакете: sus-core/Runtime/Resources/SusRuntime/. SusRuntime — виртуальное пространство Resources (Resources.Load("SusRuntime/..")), а не "вывод компилятора"; Unity объединяет все папки Resources/SusRuntime/ (пакет + проект) в один путь.

Важно: Всегда редактируйте .sharq, а не Generated/* — сгенерированные файлы перезаписываются.

Очистка: при удалении .sharq все 6 сгенерированных файлов + .meta + копии в Resources удаляются автоматически. При переименовании старые артефакты удаляются, новые создаются.

Загрузка USS в рантайме

Компоненты загружают стили через Resources/SusRuntime/ (в билде). У редактора есть fallback — если файл не найден в Resources, он загружается напрямую из Generated/ через AssetDatabase.

Batch-контур (для библиотечных пакетов)

Помимо project-scoped импортёра (Assets/SusUI), есть декларативный batch-контур — для библиотек, которые поставляют уже сгенерированные .cs/.uss внутри самого пакета (опциональные downstream UI-пакеты).

Пакет декларирует, ЧТО генерировать, в дескрипторе sharq.gen.json (рядом с package.json):

json
{
  "displayName": "My UI Package",
  "sources":   ["Components"],
  "generated": "Runtime/Generated",
  "resources": "Runtime/Resources/SusRuntime",
  "watch": true,
  "namespace": "MyCompany.UI"
}

Опционально namespace: составной C#-идентификатор, оборачивающий каждый сгенерированный .g.cs-тип этого пакета. Пусто / опущено → глобальное пространство имён (legacy). Downstream UI-пакетам стоит задавать здесь свой корневой namespace пакета, чтобы короткие имена типов не конфликтовали между пакетами.

Опционально usings: массив строк с дополнительными C#-пространствами имён, эмитируемыми как using-директивы в каждом сгенерированном .g.cs (при композиции типов из другого UI-пакета).

Инфраструктура в sus-core/Editor/Packaging/:

  • SusPackageRegistry — находит дескрипторы для всех разрешённых пакетов; учитываются только мутируемые пакеты (file:-ссылки / embedded) — registry/git-пакеты уже поставляются готовыми артефактами.
  • SusPackageGenerator — меню Sharq/Generate All Packages (все пакеты разом) и окно Sharq/Generate Package… (по одному). Ядро — SharqBatchCompiler.CompileDirectory (sus-core/Editor/AssetPipeline/), тот же BuildMethodGenerator, что и у импортёра.
  • SusPackageAutoCompile — FileSystemWatcher для каждой исходной директории пакета с watch: true (пакеты вне Assets/ не видны AssetPostprocessor'у): сохранение .sharq → автоперегенерация. В Play: только-style / только-template → SharqBatchMode.HotReloadSafe (USS + интерпретатор, без .g.cs / domain reload); <script> → откладывается до выхода из Play, затем полный Generate. В Edit mode — полный Generate пакета как раньше.
  • Результат коммитится в пакет (в отличие от project-scoped Generated/, который в .gitignore).

Три контура генерации

КонтурТриггерОбластьКто
Projectсохранение .sharq в Assets/ (AssetPostprocessor) + Window → SUS → Sharq → Regenerate All Prototype Componentssus.config.jsonSharqDirectorySharqFileImporter
Packagesсохранение .sharq в пакете (watcher) + проверка актуальности при domain reload + менювсе пакеты с sharq.gen.jsonSusPackage* (см. выше)
CLIвручную / CIAssets/ без Unity (только .g.cs)SharqBootstrap

Потребитель пакета не может полагаться на runtime-Roslyn source generator — поэтому .cs генерируется заранее и поставляется в пакете как обычные скрипты.

Как генератор транслирует авторский .sharq → C#

BuildMethodGenerator конвертирует синтаксис DSL в валидный C#:

В .sharqВ .g.cs
[CreateProperty(default:0)] public Prop<int> Damage = new(0);[CreateProperty] (параметры DSL default:/validate: отбрасываются) + backing-поле public Prop<int> Damage = new(0); + companion-свойство public int damage { get => Damage.Value; set => Damage.Value = value; }
наличие [CreateProperty]автоматический using Unity.Properties;
Vue-кавычки в выражениях: v-if="Mode.Value != 'delete'", != ''C#-кавычки: Mode.Value != "delete", != ""
$using System.Linq;using System.Linq;

Всегда добавляются using System; и using System.Collections.Generic;.

Соглашения для компонента <script>

  • SusComponent : VisualElementкорень — сам компонент: используйте this.Q<…>(), this.style, this.schedule (не Element.…).
  • Базовые хуки жизненного цикла: Created(), BeforeMounted(), Mounted(), Updated(), BeforeUnmounted(), Unmounted() (все protected override void). Метода Init() нет — инициализацию элементов (this.Q<…>(), Watch(...)) делайте в Created().

Hot reload (Editor + DEVELOPMENT_BUILD)

USS (E1 / E4)

  • Изменение только <style> → регенерация .g.uss без domain reload → UssHotReloadService перезагружает сопутствующие таблицы стилей на живых компонентах (<1с в Editor Play).
  • Remote (E4): SusRuntimeHotReload.ApplyUss + Runtime MCP ui.hotreload.uss / Session MCP client.hotreload_uss. StyleSheet-from-text — через Editor StyleSheetImporterImpl (SusUssFromString). На standalone-плеере без фабрики USS-parse недоступен; template hot reload работает.
  • Editor push: RemoteHotReloadPushService (отключение: EditorPrefs Sharq.RemoteHotReload.Enabled = 0).

Templates (E3)

  • Изменение только <template>SharqCompileEvents.OnTemplateChangedTemplateHotReloadService / SharqTemplateInterpreter.TryApply.
  • Remote: ui.hotreload.template / client.hotreload_template.

Матрица поддержки интерпретатора шаблонов

ФичаСтатусПоведение
$MainElement / root-атрибуты → thisКак у BuildMethodGenerator: class / :class / name — на хост-компонент, дети — внутрь
class, :class (объект), name, :text
v-if / v-show (литерал, !, ==/!=, ||/&&, Prop.Value)Сложный C# (string.IsNullOrEmpty, вызовы) → fallback
Prop="lit" / :Prop="expr"Простые строки/числа/bool/enum
<slot> / именованный slotЧерез GetSlotContainer / BuildSlot
@click / @* события⚠️ пропускЛог [SharqInterp] Skipping unsupported event…; дерево применяется без обработчиков
transition= на v-if⚠️игнорАтрибут пропускается; анимация — после полной перекомпиляции
v-for❌ fallbackTryApply → false, нужен domain reload / полный Build
Вложенные произвольные C#-выражения❌fallback

Критерий: простые downstream UI-шаблоны (Divider / Badge / упрощённый Alert) — TryApply == true; заведомо сложные (v-for, тяжёлые выражения) — предсказуемый fallback + предупреждение.

State-preserving domain reload (E2b)

При редактировании <script> Unity делает domain reload. Чтобы UI не сбрасывался:

  1. Edit → Preferences → General → Script Changes While Playing = Recompile And Continue Playing (иначе Play остановится). Без этой настройки Unity останавливает Play при каждом изменении скрипта — снапшот бесполезен.
  2. HotReloadStatePreserveService снимает SusComponentSnapshot с деревьев Sus в UIDocument и EditorWindow перед reload и восстанавливает после delayCall.
  3. Сериализуются примитивы + enum в Prop<T>; остальные типы игнорируются.
  4. Опция в Assets/sus.config.json: "HotReloadStatePreserve": true (по умолчанию). Отключить: false.

Правило «не компилировать в бою» остаётся приоритетным для ручной PvP-сессии — E2b это Editor-удобство, а не замена этому правилу.