Миграция с 2.2.0 на 2.2.1

Загрузчики снимков: gzip определяется автоматически, byteSizeHint удалён

Все standalone-загрузчики снимков — LoadWorldSnapshot / LoadClusterSnapshot / LoadChunkSnapshot / LoadEventsSnapshot / LoadResourcesSnapshot / LoadSystemsSnapshot / RestoreFromGIDStoreSnapshot — больше не принимают параметры gzip и byteSizeHint. Gzip определяется автоматически по магической последовательности 0x1F 0x8B (RFC 1952), а размер буфера читается из 10-байтового заголовка снимка (ushort версия формата + ulong размер payload). Все публичные перегрузки (ref BinaryPackWriter) / (BinaryPackReader) методов Create*Snapshot / Load*Snapshot / RestoreFromGIDStoreSnapshot (events / resources / systems / GID-store) теперь тоже пишут и читают этот заголовок — composite-сценарии, которые встраивают такие вызовы в свой writer, увидят +10 байт на блок. Embedded-сериализация внутри World snapshot по-прежнему идёт через internal header-less путь и не изменилась.

// Было (2.2.0)
W.Serializer.LoadWorldSnapshot(bytes, gzip: true, hardReset: true);
W.Serializer.LoadWorldSnapshot("world.bin", gzip: true, byteSizeHint: 1_000_000, hardReset: true);
W.Serializer.LoadClusterSnapshot(bytes, gzip: true, entitiesAsNew);
W.Serializer.LoadChunkSnapshot("chunk.bin", gzip: true, byteSizeHint: 0);
W.Serializer.LoadEventsSnapshot(bytes, gzip: true);
W.Serializer.LoadEventsSnapshot("events.bin", gzip: true, byteSizeHint: 4096);
W.Serializer.LoadResourcesSnapshot(bytes, gzip: true);
W.Serializer.LoadResourcesSnapshot("resources.bin", gzip: true, byteSizeHint: 4096);
W.Serializer.LoadSystemsSnapshot(bytes, gzip: true);
W.Serializer.LoadSystemsSnapshot("systems.bin", gzip: true, byteSizeHint: 4096);
W.Serializer.RestoreFromGIDStoreSnapshot(bytes, gzip: true, hardReset: true);
W.Serializer.RestoreFromGIDStoreSnapshot("gid.bin", gzip: true, byteSizeHint: 0, hardReset: true);

// Стало (2.2.1)
W.Serializer.LoadWorldSnapshot(bytes, hardReset: true);
W.Serializer.LoadWorldSnapshot("world.bin", hardReset: true);
W.Serializer.LoadClusterSnapshot(bytes, entitiesAsNew);
W.Serializer.LoadChunkSnapshot("chunk.bin");
W.Serializer.LoadEventsSnapshot(bytes);
W.Serializer.LoadEventsSnapshot("events.bin");
W.Serializer.LoadResourcesSnapshot(bytes);
W.Serializer.LoadResourcesSnapshot("resources.bin");
W.Serializer.LoadSystemsSnapshot(bytes);
W.Serializer.LoadSystemsSnapshot("systems.bin");
W.Serializer.RestoreFromGIDStoreSnapshot(bytes, hardReset: true);
W.Serializer.RestoreFromGIDStoreSnapshot("gid.bin", hardReset: true);

LoadClusterSnapshot проверяет целевые чанки

Загрузка снимка кластера с entitiesAsNew: false в чанки, уже содержащие активные сущности, теперь явно бросает StaticEcsException вместо тихой порчи данных. Перед загрузкой нужно выгрузить или уничтожить сущности в целевых чанках:

ReadOnlySpan<ushort> targetClusters = stackalloc ushort[] { clusterId };
W.Query().BatchUnload(EntityStatusType.Any, clusters: targetClusters);
W.Serializer.LoadClusterSnapshot(snapshot); // entitiesAsNew: false

Версия формата снимков повышена до 2

Изменился layout снимков мира / кластера / чанка (sparse-сериализация сегментов по UsedSegmentsMask, sparse-сериализация event-страниц по 64-битным маскам). SnapshotFormatVersion теперь равна 2. Снимки, созданные сборкой 2.2.0, не загружаются в 2.2.1 — их нужно однократно пересохранить сборкой 2.2.0 и затем загрузить в 2.2.1, либо перегенерировать из авторитетного состояния игры.

Зависимость StaticPack: 1.1.0 → 1.2.5

Версия пакета FFS.StaticPack поднята до 1.2.5. Если вы используете StaticPack напрямую в своих проектах, обновите версию. Новые API BinaryPackReader.RentAndFillFromBytes / RentAndFillFromFile / BinaryPackWriter.ForceWriteUnmanaged / BinaryPackReader.ForceReadUnmanaged лежат в основе упрощений выше.


Миграция с 2.1.x на 2.2.0

Disable/Enable теперь opt-in через IDisableable

В 2.2.x любой IComponent безусловно аллоцировал per-component disabled-битмаску (4 ulong на сегмент памяти) и открывал entity.Disable<T>()/Enable<T>()/HasDisabled<T>()/HasEnabled<T>() плюс *Disabled фильтры запросов для любого типа компонента. В 2.3.0 это становится opt-in через новый маркер-интерфейс IDisableable.

Breaking change: любой компонент, на котором вызывался Disable<T>()/Enable<T>(), использовался в *Disabled фильтрах или в HasDisabled<T>()/HasEnabled<T>(), теперь должен декларировать IDisableable. Без маркера эти места не компилируются.

// Было
public struct Health : IComponent { public float Value; }

// Стало (только если на этом типе реально используется Disable/Enable или *Disabled фильтры)
public struct Health : IComponent, IDisableable { public float Value; }

Методы Disable*/Enable*/Has*Disabled/Has*Enabled на сущности, инстанс-методы Components<T>.Disable/Enable/HasDisabled/HasEnabled и фильтры AllOnlyDisabled/AllWithDisabled/NoneWithDisabled/AnyOnlyDisabled/AnyWithDisabled имеют констрейнт T : struct, IComponent, IDisableable.

Встроенные компонент-типы — Multi<TValue>, Link<TLinkType>, Links<TLinkType> — уже реализуют IDisableable, поэтому код, переключающий отношения или multi-компоненты, продолжает работать без изменений.

Влияние на память и сериализацию

  • Компоненты без IDisableable больше не аллоцируют disabled-половину per-component mask-сегмента — Components<T>.EntitiesMaskSegments теперь аллоцирует 4 ulong на сегмент вместо 8 (минус 50% памяти масок для таких типов).
  • Per-entity сериализатор не выставляет старший DisabledBit в ushort размера компонента для не-IDisableable типов. Per-chunk сериализация не пишет disabled-маску ulong на каждый non-empty блок для таких типов.
  • Формат снапшота самоописывающий: WriteChunk пишет флаг HasDisable типа на момент записи, ReadChunk читает его из потока. Изменение членства IDisableable между записью и чтением снапшота безопасно — старые снапшоты не-IDisableable типа корректно загружаются в ставший IDisableable тип (все инстансы становятся enabled), и наоборот (disabled-маска прочитывается из потока, но игнорируется).

Встроенные opt-in маркеры

  • IDisableable появляется в 2.2.0 (этот раздел).
  • Существующие маркеры трекинга — ITrackableAdded, ITrackableDeleted, ITrackableChanged — уже работают по тому же паттерну; для них ничего не меняется.

Сужение семантики Flexible-режима запросов

В 2.1.x QueryMode.Flexible / EntitiesFlexible() через внутренний callback-механизм OnCacheUpdate патчил кэшированную битмаску снимка на лету при изменении фильтруемого типа на другой сущности из снимка — тем самым снимая те же блокеры, которые срабатывают в Strict (Delete<T>/Disable<T> для All<T>, Add<T>/Set<T>/Enable<T> для None<T> и т. д.). В 2.2.0 этот механизм удалён.

В 2.2.0 единственная свобода Flexible сверх Strict — это entity-уровневые Destroy, Disable, Enable других сущностей из снимка: они по-прежнему допускаются и корректно исключают такие сущности из оставшейся итерации за счёт обновления кэшированной битмаски. Все блокеры по фильтруемым типам, ранее снимавшиеся через OnCacheUpdate, теперь действуют и в Flexible — ассертятся в DEBUG так же, как и в Strict (точечно по типу фильтра, см. Запросы — QueryMode).

Кратко: Flexible = Strict + разрешённые entity-уровневые Destroy/Disable/Enable у других сущностей из снимка.

Примечание: в 2.2.0 ассерты strict / flexible ограничены снимком итерации — битмаской сущностей, прошедших фильтр на момент старта обхода. Сущности вне снимка — созданные во время итерации или не прошедшие фильтр — не блокируются. Код, который раньше был вынужден откладывать entity.Add<T>() / entity.Set<T>() на свежесозданной сущности до конца цикла, теперь может выполнять это прямо внутри.

Код, который в Flexible-итерации делал other.Delete<T>() / other.Add<T>() / other.Enable<T>() / other.Disable<T>() для фильтруемого T, необходимо переписать одним из способов:

  • уничтожать / переключать сущность целиком — other.Destroy() / other.Disable() / other.Enable() — если это соответствует задаче;
  • собрать нужные сущности в буфер во время цикла и применить компонентные мутации после foreach;
  • вынести логику в отдельный проход по миру.

Удалённые публичные API

  • IQueryFilter.PushQueryData<TWorld>(QueryData) — удалён
  • IQueryFilter.PopQueryData<TWorld>() — удалён
  • IQueryFilter.Assert<TWorld>() — удалён
  • делегат OnCacheUpdate — удалён
  • метод QueryData.BatchUpdate — удалён
  • поле QueryData.OnCacheUpdate — удалено

См. также: Запросы — QueryMode, Подводные камни.


Миграция с 1.2.x на 2.0.0

Версия 2.0.0 — полная реструктуризация фреймворка. Практически весь пользовательский код потребует изменений.


Обзор изменений

  • Сегментная модель хранения — новый уровень иерархии Chunk → Segment (256 entity) → Block → Entity
  • entityType (IEntityType) — логическая группировка сущностей для кэш-локальности через дженерик-параметр
  • Хуки в IComponent/IEvent — через default interface methods вместо отдельных Config-классов
  • Единый ISystem вместо IInitSystem/IUpdateSystem/IDestroySystem
  • Единый QueryQuery.Entities<>() и Query.For() объединены в Query<>()
  • Связи — IEntityLinkComponent → ILinkType + Link<T>/Links<T>
  • Context → ResourcesContext.Set/GetSetResource/GetResource
  • Теги унифицированы с компонентами — теги хранятся в Components<T> с флагом IsTag, используют те же фильтры запросов (All<>, None<>, Any<>)
  • Свойства вместо методов для состояний Entity и World
  • Переименование batch-операций запроса: DestroyAllEntities()BatchDestroy(), новый BatchUnload()
  • Удалено: UnloadCluster()/UnloadChunk() — используйте Query().BatchUnload() с фильтрацией по кластерам/чанкам

0. Переименование batch-операций запроса

DestroyAllEntities()BatchDestroy():

// Было:
W.Query<All<Health>, TagAll<IsDead>>().DestroyAllEntities();

// Стало:
W.Query<All<Health, IsDead>>().BatchDestroy();

Новое: BatchUnload() — массовая выгрузка сущностей по фильтру:

W.Query<All<Position>>().BatchUnload();

Удалено: UnloadCluster() / UnloadChunk() — используйте BatchUnload() с фильтрацией:

// Было:
W.UnloadCluster(clusterId);
W.UnloadChunk(chunkIdx);

// Стало:
ReadOnlySpan<ushort> clusters = stackalloc ushort[] { clusterId };
W.Query().BatchUnload(EntityStatusType.Any, clusters: clusters);

ReadOnlySpan<uint> chunks = stackalloc uint[] { chunkIdx };
W.Query().BatchUnload(EntityStatusType.Any, chunks);

1. World API

Подробнее: Мир

Методы → Свойства:

// Было:                              Стало:
W.IsInitialized()                    W.IsWorldInitialized
W.IsIndependent()                    W.IsIndependent
                                      W.Status  // новое (WorldStatus enum)

Создание сущностей:

Подробнее: Сущность

// Было:
var entity = W.Entity.New(clusterId);
var entity = W.Entity.New<Position>(new Position());
W.Entity.NewOnes(count, onCreate, clusterId);
bool ok = W.Entity.TryNew(out entity, clusterId);

// Стало:
var entity = W.NewEntity<Default>(clusterId: 0);
var entity = W.NewEntity<Default>(new Default(), clusterId: 0);
W.NewEntities<Default>(count: 100, clusterId: 0, onCreate: null);
bool ok = W.TryNewEntity<Default>(out entity, clusterId: 0);
var entity = W.NewEntity<Default>();  // тип сущности Default, clusterId=0

WorldConfig:

// Все поля теперь nullable — незаданные значения берутся из WorldConfig.Default()
// ParallelQueryType enum удалён → используйте ThreadCount (uint?)
//   0 = однопоточный (по умолчанию)
//   WorldConfig.MaxThreadCount = все доступные потоки CPU
//   N = конкретное количество потоков
// CustomThreadCount удалён → используйте ThreadCount напрямую

// Фабричные методы:
WorldConfig.Default()      // стандартные настройки
WorldConfig.MaxThreads()   // все доступные потоки

Типы конфигурации (ComponentTypeConfig, TagTypeConfig, EventTypeConfig):

// Все поля конфигурации теперь nullable — незаданные значения берутся из умолчаний
// Guid вычисляется автоматически из имени типа (не нужно задавать вручную)
// ReadWriteStrategy определяется автоматически (UnmanagedPackArrayStrategy для unmanaged типов)
// 3-уровневое слияние: пользовательский конфиг → статическое поле Config → встроенные умолчания

Удалено:

  • ParallelQueryType enum → WorldConfig.ThreadCount
  • WorldConfig.CustomThreadCountWorldConfig.ThreadCount
  • IWorld интерфейс → WorldHandle
  • WorldWrapper<W>WorldHandle
  • Worlds статический класс
  • BoxedEntity<W> / IEntity

2. Entity API

Подробнее: Сущность, Глобальный идентификатор

Методы → Свойства:

// Было:               Стало:
entity.Gid()          entity.GID
entity.GidCompact()   entity.GIDCompact
entity.IsNotDestroyed() entity.IsNotDestroyed  // метод → свойство
                         entity.IsDestroyed     // НОВОЕ свойство
entity.IsDisabled()   entity.IsDisabled
entity.IsEnabled()    entity.IsEnabled
entity.Version()      entity.Version
entity.ClusterId()    entity.ClusterId
entity.Chunk()        entity.ChunkID
entity.IsSelfOwned()  entity.IsSelfOwned

Новые свойства:

entity.EntityType   // byte — ID типа сущности (из регистрации IEntityType)
entity.ID           // raw slot index

Проверка наличия компонентов:

Подробнее: Компонент, Тег

// Было:                              Стало:
entity.HasAllOf<C>()                 entity.Has<C>()
entity.HasAllOf<C1, C2>()            entity.Has<C1, C2>()
entity.HasAnyOf<C1, C2>()            entity.HasAny<C1, C2>()
entity.HasDisabledAllOf<C>()         entity.HasDisabled<C>()
entity.HasEnabledAllOf<C>()          entity.HasEnabled<C>()

// Теги:
entity.HasAllOfTags<T>()             entity.Has<T>()
entity.HasAnyOfTags<T1, T2>()        entity.HasAny<T1, T2>()

Add — новая семантика:

Подробнее: Компонент — Добавление

// ═══ Было (v1.2.x) ═══
entity.Add<C>();                    // ASSERT что компонента нет
ref var c = ref entity.TryAdd<C>(); // идемпотентный
entity.Put(new Position(1, 2));     // upsert без хуков

// ═══ Стало (v2.0.0) ═══
ref var c = ref entity.Add<C>();              // идемпотентный (бывший TryAdd)
ref var c = ref entity.Add<C>(out bool isNew);// с флагом
entity.Set(new Position(1, 2));               // ВСЕГДА OnDelete→замена→OnAdd
Старый метод Новый эквивалент
entity.TryAdd<C>() entity.Add<C>()
entity.TryAdd<C>(out bool) entity.Add<C>(out bool isNew)
entity.Put<C>(value) entity.Set<C>(value) (но теперь с хуками)
entity.Add<C>() (старый, assert) Нет аналога

Delete/Disable/Enable — возвращают bool:

// Было:
entity.Delete<C>();               // void, assert
bool ok = entity.TryDelete<C>();  // bool

// Стало:
bool deleted = entity.Delete<C>();    // bool (бывший TryDelete)
ToggleResult disabled = entity.Disable<C>();  // ToggleResult: MissingComponent, Unchanged, Changed
ToggleResult enabled = entity.Enable<C>();    // ToggleResult: MissingComponent, Unchanged, Changed

Новые методы:

entity.Clone(clusterId);                       // клон в кластер
entity.MoveTo(clusterId);                      // перемещение в кластер

Удалено:

  • entity.Box() / BoxedEntity<W> / IEntity
  • entity.TryAdd<C>() → используйте entity.Add<C>()
  • entity.Put<C>(val) → используйте entity.Set<C>(val)
  • entity.TryDelete<C>() → используйте entity.Delete<C>()
  • entity.TryCopyComponentsTo<C>(target)entity.CopyTo<C>(target) (возвращает bool)
  • entity.TryMoveComponentsTo<C>(target)entity.MoveTo<C>(target) (возвращает bool)
  • Все Raw-методы (RawHasAllOf, RawAdd, RawGet, RawPut, etc.)
  • Entity.New(...) (все перегрузки) → W.NewEntity(...)
  • W.OnCreateEntity(callback) → используйте IEntityType.OnCreate хук или Created трекинг-фильтр

3. EntityGID API

Подробнее: Глобальный идентификатор

// ═══ Было (v1.2.x) ═══
bool ok = gid.IsActual<WT>();
bool loaded = gid.IsLoaded<WT>();
bool both = gid.IsLoadedAndActual<WT>();

// ═══ Стало (v2.0.0) ═══
GIDStatus status = gid.Status<WT>();
// GIDStatus.Active     — сущность существует, версия совпадает, загружена (бывший IsLoadedAndActual)
// GIDStatus.NotActual  — сущность не существует или версия/кластер не совпадает (бывший !IsActual)
// GIDStatus.NotLoaded  — сущность существует, версия совпадает, но выгружена (бывший IsActual && !IsLoaded)
Старый метод Новый эквивалент
gid.IsActual<WT>() gid.Status<WT>() != GIDStatus.NotActual
gid.IsLoaded<WT>() gid.Status<WT>() != GIDStatus.NotLoaded
gid.IsLoadedAndActual<WT>() gid.Status<WT>() == GIDStatus.Active

Создание сущности по GID:

// Было:
var entity = W.Entity.New(someGid);

// Стало:
var entity = W.NewEntityByGID<Default>(someGid);

4. Компоненты

Подробнее: Компонент

Хуки — в IComponent через default interface methods:

// ═══ Было (v1.2.x) ═══
struct Position : IComponent { public float X, Y; }

class PositionConfig : IComponentConfig<Position, WT> {
    public OnComponentHandler<Position> OnAdd() => (ref Position c, Entity e) => { };
    public OnComponentHandler<Position> OnDelete() => ...;
    public Guid Id() => ...;
    public BinaryWriter<Position> Writer() => ...;
    public BinaryReader<Position> Reader() => ...;
}
W.RegisterComponentType<Position>(new PositionConfig());

// ═══ Стало (v2.0.0) ═══
struct Position : IComponent {
    public float X, Y;

    public void OnAdd<TWorld>(World<TWorld>.Entity self)
        where TWorld : struct, IWorldType { }

    public void OnDelete<TWorld>(World<TWorld>.Entity self, HookReason reason)
        where TWorld : struct, IWorldType { }

    public void Write<TWorld>(ref BinaryPackWriter writer, World<TWorld>.Entity self)
        where TWorld : struct, IWorldType { writer.WriteFloat(X); writer.WriteFloat(Y); }

    public void Read<TWorld>(ref BinaryPackReader reader, World<TWorld>.Entity self, byte version, bool disabled)
        where TWorld : struct, IWorldType { X = reader.ReadFloat(); Y = reader.ReadFloat(); }
}

W.Types().Component<Position>(new ComponentTypeConfig<Position>(
    guid: new Guid("..."),
    version: 0,
    readWriteStrategy: new UnmanagedPackArrayStrategy<Position>()
));

Удалено:

  • IComponentConfig<T, W>, DefaultComponentConfig<T, W>, ValueComponentConfig<T, W>
  • OnComponentHandler<T>, OnCopyHandler<T> делегаты
  • Хук OnPut (в v2 Set(value) вызывает OnDelete+OnAdd)

Доступ к пулу:

// Было:                              Стало:
Components<T>.Value.Ref(entity)      Components<T>.Instance.Ref(entity)
Components<T>.Value.Has(entity)      Components<T>.Instance.Has(entity)
Components<T>.Value.IsRegistered()   Components<T>.Instance.IsRegistered  // свойство
Components<T>.Value.DynamicId()      Components<T>.Instance.DynamicId    // свойство

5. Теги

Подробнее: Тег

Теги унифицированы с компонентами. Теги теперь хранятся в Components<T> с флагом IsTag.

Методы сущности:

// Было:                              Стало:
entity.SetTag<T>()                   entity.Set<T>()
entity.HasTag<T>()                   entity.Has<T>()
entity.HasAnyTags<T1,T2>()           entity.HasAny<T1,T2>()
entity.DeleteTag<T>()                entity.Delete<T>()
entity.ToggleTag<T>()                entity.Toggle<T>()
entity.ApplyTag<T>(bool)             entity.Apply<T>(bool)
entity.CopyTagsTo<T>(target)         entity.CopyTo<T>(target)
entity.MoveTagsTo<T>(target)         entity.MoveTo<T>(target)

Доступ к пулу:

// Было:                              Стало:
Tags<T>.Value                        Components<T>.Instance (IsTag = true)

Фильтры запросов:

// Было:                              Стало:
TagAll<T>                            All<T>
TagNone<T>                           None<T>
TagAny<T1,T2>                        Any<T1,T2>

Удалено:

  • WorldConfig.BaseTagTypesCount → теги учитываются в BaseComponentTypesCount
  • DeleteTagsSystem<W, T> → используйте Query().BatchDelete<T>()

6. Запросы (Query)

Подробнее: Запросы

Единая точка входа:

// ═══ Было (v1.2.x) ═══
foreach (var entity in W.Query.Entities<All<Position>>()) { }

W.Query.For((ref Position pos) => {
    pos.X += 1;
});

// ═══ Стало (v2.0.0) ═══
foreach (var entity in W.Query<All<Position>>().Entities()) { }

W.Query().For(
    static (ref Position pos) => { pos.X += 1; }
);

Поиск сущности:

// Было:
W.Query.Entities<All<P>>().First(out entity);

// Стало:
W.Query<All<P>>().Any(out entity);

Batch-операции:

// Было (на QueryEntitiesIterator):
W.Query.Entities<All<P>>().AddForAll<Health>();
W.Query.Entities<All<P>>().DeleteForAll<Health>();
W.Query.Entities<All<P>>().SetTagForAll<Active>();

// Стало (на WorldQuery, с chaining):
W.Query<All<P>>().BatchAdd<Health>().BatchSet<Active>();
W.Query<All<P>>().BatchDelete<Health>();
W.Query<All<P>>().BatchDisable<Health>();   // новое
W.Query<All<P>>().BatchEnable<Health>();    // новое
W.Query<All<P>>().BatchDestroy();
W.Query<All<P>>().BatchUnload();    // новое

Фильтр-обёртка With → And:

// Было:
W.Query.Entities<With<All<Pos>, None<Name>>>();

// Стало:
W.Query<And<All<Pos>, None<Name>>>();
// Также доступен Or<> для дизъюнкции фильтров (новое)

Параллельная итерация:

// Было:
W.Query.Parallel.For(minChunkSize: 50000, (W.Entity ent, ref Position pos) => { });

// Стало:
W.Query().ForParallel(
    static (W.Entity ent, ref Position pos) => { },
    minEntitiesPerThread: 50000
);

QueryMode:

Подробнее: Производительность — QueryMode

// Было: runtime параметр при создании итератора
W.Query.Entities<All<P>>(queryMode: QueryMode.Flexible);

// Стало: по умолчанию Strict, для Flexible — отдельные методы
foreach (var entity in W.Query<All<P>>().EntitiesFlexible()) { }

W.Query().For(
    static (ref Position pos) => { },
    queryMode: QueryMode.Flexible
);

7. Связи (Relations)

Подробнее: Отношения

// ═══ Было (v1.2.x) ═══
struct Parent : IEntityLinkComponent<Parent> {
    public EntityGID Link;
    ref EntityGID IRefProvider<Parent, EntityGID>.RefValue(ref Parent c) => ref c.Link;
}
W.RegisterToOneRelationType<Parent>(config);

// ═══ Стало (v2.0.0) ═══
struct ParentLink : ILinkType {
    public void OnAdd<TW>(World<TW>.Entity self, EntityGID link) where TW : struct, IWorldType { }
    public void OnDelete<TW>(World<TW>.Entity self, EntityGID link, HookReason reason) where TW : struct, IWorldType { }
}
W.Types()
    .Link<ParentLink>()       // связь "один"
    .Links<ParentLink>();     // связь "много"

// Использование:
entity.Set(new W.Link<ParentLink>(parentEntity));
ref var links = ref entity.Ref<W.Links<ChildLink>>();
Старый тип Новый эквивалент
IEntityLinkComponent<T> ILinkType + Link<T>
IEntityLinksComponent<T> ILinksType + Links<T>
RegisterToOneRelationType W.Types().Link<T>()
RegisterToManyRelationType W.Types().Links<T>()
RegisterOneToManyRelationType Раздельно: Link<T> + Links<T>

8. События (Events)

Подробнее: События

// ═══ Было (v1.2.x) ═══
struct MyEvent : IEvent { public int Data; }
class MyEventConfig : IEventConfig<MyEvent, WT> { ... }
W.Events.RegisterEventType<MyEvent>(new MyEventConfig());
W.Events.Send(new MyEvent { Data = 1 });
var receiver = W.Events.RegisterEventReceiver<MyEvent>();
W.Events.DeleteEventReceiver(ref receiver);

// ═══ Стало (v2.0.0) ═══
struct MyEvent : IEvent {
    public int Data;
    public void Write(ref BinaryPackWriter writer) { writer.WriteInt(Data); }
    public void Read(ref BinaryPackReader reader, byte version) { Data = reader.ReadInt(); }
}
W.Types().Event<MyEvent>(new EventTypeConfig<MyEvent>(guid: new Guid("...")));
W.SendEvent(new MyEvent { Data = 1 });
var receiver = W.RegisterEventReceiver<MyEvent>();
W.DeleteEventReceiver(ref receiver);
  • IEventConfig<T, W> удалён → EventTypeConfig<T> + хуки в IEvent
  • W.Events.XXXW.XXX (методы перенесены на World)
  • IsClearable()NoDataLifecycle (инвертированная логика, расширенная семантика — управляет инициализацией и очисткой)

9. Системы (Systems)

Подробнее: Системы

// ═══ Было (v1.2.x) ═══
struct MoveSystem : IUpdateSystem {
    public void Update() { }
}
Systems.AddUpdate(new MoveSystem());
Systems.AddCallOnce(new InitSystem());

// ═══ Стало (v2.0.0) ═══
struct MoveSystem : ISystem {
    public void Init() { }          // вместо IInitSystem
    public void Update() { }        // вместо IUpdateSystem
    public bool UpdateIsActive() => true; // вместо ISystemCondition
    public void Destroy() { }       // вместо IDestroySystem
}
GameSys.Add(new MoveSystem(), order: 0);
Старый тип Новый эквивалент
IInitSystem ISystem.Init()
IUpdateSystem ISystem.Update()
IDestroySystem ISystem.Destroy()
ISystemCondition ISystem.UpdateIsActive()
Systems.AddUpdate(system) Sys.Add(system, order)
Systems.AddCallOnce(system) Sys.Add(system, order) + Init()

10. Мульти-компоненты

Подробнее: Мульти-компонент

// ═══ Было (v1.2.x) ═══
struct Items : IMultiComponent<Items, int> {
    public Multi<int> Values;
    public ref Multi<int> RefValue(ref Items c) => ref c.Values;
}
W.RegisterMultiComponentType<Items, int>(4, config);

// ═══ Стало (v2.0.0) ═══
struct Items : IMultiComponent {  // маркерный интерфейс, без RefValue
    public Multi<int> Values;
}
W.Types().Multi<Items>();  // регистрация мульти-компонента
  • IMultiComponent<T, V>IMultiComponent (маркер)
  • RefValue() удалён
  • RegisterMultiComponentTypeW.Types().Multi<T>() (не Component<T>!)
  • CountLength
  • IsEmpty()/IsNotEmpty()/IsFull() → свойства

11. Сериализация

Подробнее: Сериализация

// ═══ Было (v1.2.x) ═══
// Через Config-классы с BinaryWriter<T>/BinaryReader<T> делегатами
class PositionConfig : DefaultComponentConfig<Position, WT> {
    public override BinaryWriter<Position> Writer() => ...;
    public override BinaryReader<Position> Reader() => ...;
}

// События:
W.Events.CreateSnapshot();
W.Events.LoadSnapshot(snapshot);

// ═══ Стало (v2.0.0) ═══
// Хуки Write/Read прямо на IComponent/IEvent
struct Position : IComponent {
    public void Write<TWorld>(ref BinaryPackWriter writer, World<TWorld>.Entity self)
        where TWorld : struct, IWorldType { }
    public void Read<TWorld>(ref BinaryPackReader reader, World<TWorld>.Entity self, byte version, bool disabled)
        where TWorld : struct, IWorldType { }
}

// События:
W.Serializer.CreateEventsSnapshot();
W.Serializer.LoadEventsSnapshot(snapshot);

12. Context → Resources

Подробнее: Ресурсы

// ═══ Было (v1.2.x) ═══
W.Context.Set<GameTime>(new GameTime());
ref var time = ref W.Context.Get<GameTime>();
bool has = W.Context.Has<GameTime>();

// ═══ Стало (v2.0.0) ═══
W.SetResource(new GameTime());                // Set перезаписывает без ошибки (заменяет Replace)
ref var time = ref W.GetResource<GameTime>();
bool has = W.HasResource<GameTime>();
W.RemoveResource<GameTime>();

// Именованные ресурсы (новое):
W.SetResource("key", new GameConfig());
ref var cfg = ref W.GetResource<GameConfig>("key");

Краткая таблица переименований

Было (v1.2.x) Стало (v2.0.0)
W.Entity.New(...) W.NewEntity<TEntityType>(...) / W.NewEntity<Default>()
W.Entity.NewOnes(...) W.NewEntities<TEntityType>(count, ...)
W.IsInitialized() W.IsWorldInitialized
W.IsIndependent() W.IsIndependent
entity.Gid() entity.GID
entity.HasAllOf<C>() entity.Has<C>()
entity.HasAnyOf<C1,C2>() entity.HasAny<C1,C2>()
entity.HasAllOfTags<T>() entity.Has<T>()
entity.TryAdd<C>() entity.Add<C>()
entity.Put<C>(val) entity.Set<C>(val)
entity.TryDelete<C>() entity.Delete<C>() (bool)
Components<T>.Value Components<T>.Instance
Tags<T>.Value Components<T>.Instance
W.Query.Entities<F>() W.Query<F>()
W.Query.For(...) W.Query<F>().For(...)
AddForAll<C>() BatchAdd<C>()
DeleteForAll<C>() BatchDelete<C>()
SetTagForAll<T>() BatchSet<T>()
IInitSystem / IUpdateSystem ISystem
Systems.AddUpdate(sys) Sys.Add(sys, order)
IComponentConfig<T,W> ComponentTypeConfig<T> + хуки в IComponent
IEventConfig<T,W> EventTypeConfig<T> + хуки в IEvent
IEntityLinkComponent<T> ILinkType + Link<T>
IEntityLinksComponent<T> ILinksType + Links<T>
RegisterToOneRelationType W.Types().Link<T>()
IMultiComponent<T,V> IMultiComponent (маркер)
RegisterMultiComponentType W.Types().Multi<T>()
W.Context.Set/Get/Has W.SetResource/GetResource/HasResource
W.Context<T>.Replace(val) W.SetResource(val) (перезаписывает автоматически)
W.Events.Send(...) W.SendEvent(...)
W.Events.RegisterEventReceiver W.RegisterEventReceiver
W.Events.CreateSnapshot() W.Serializer.CreateEventsSnapshot()
gid.IsActual<WT>() gid.Status<WT>() != GIDStatus.NotActual
gid.IsLoadedAndActual<WT>() gid.Status<WT>() == GIDStatus.Active
W.Entity.New(gid) W.NewEntityByGID<TEntityType>(gid)
W.OnCreateEntity(callback) IEntityType.OnCreate / Created трекинг
With<F1, F2> And<F1, F2>
Query.Parallel.For(minChunkSize:) Query().ForParallel(minEntitiesPerThread:)

This site uses Just the Docs, a documentation theme for Jekyll.