Миграция с 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
- Единый Query —
Query.Entities<>()иQuery.For()объединены вQuery<>() - Связи — IEntityLinkComponent → ILinkType + Link<T>/Links<T>
- Context → Resources —
Context.Set/Get→SetResource/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 → встроенные умолчания
Удалено:
ParallelQueryTypeenum →WorldConfig.ThreadCountWorldConfig.CustomThreadCount→WorldConfig.ThreadCountIWorldинтерфейс →WorldHandleWorldWrapper<W>→WorldHandleWorldsстатический класс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>/IEntityentity.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(в v2Set(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→ теги учитываются вBaseComponentTypesCountDeleteTagsSystem<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>+ хуки вIEventW.Events.XXX→W.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()удалёнRegisterMultiComponentType→W.Types().Multi<T>()(неComponent<T>!)Count→LengthIsEmpty()/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:) |