Using Primary Key
, we can define separated Group
memory structure of Entities. When you use Primary Key
, you should think it as part of RDBMS primary key, that you can query partially. For example if you have primary key like this,
CREATE TABLE Character(
EntityID int NOT NULL,
PlayerID int NOT NULL,
CONSTRAINT PK_Character PRIMARY KEY (PlayerID, EntityID)
);
Then you can query all rows with specific PlayerID
with best performance. That is what happening in Schema extension as well. Some difference is that you can make any partial queries no matter how the column order is!
First we need to define IKeyComponent<TKey>
. It is special IEntityComponent
that holds the key value for Primary Key
or Index
. It only requires TKey key { get; set; }
property.
public struct IntKeyComponent : IKeyComponent<int>
{
public int key { get; set; }
}
Since TKey
will have to be IEquatable<TKey>
, we have special wrapper EnumKey<TEnum>
for enums.
public struct EnumKeyComponent : IKeyComponent<EnumKey<YourEnum>>
{
public EnumKey<YourEnum> key { get; set; }
}
First you need to add IPrimaryKeyRow<TComponent>
so we can tell which row has primary keys.
public class CharacterRow : DescriptorRow<CharacterRow>, IPrimaryKeyRow<PlayerComponent> { }
Then you can define PrimaryKey<TComponent>
in your Schema.
public class GameSchema : IEntitySchema
{
public readonly Table<CharacterRow> Character = new();
public readonly PrimaryKey<PlayerComponent> Player = new();
public GameSchema()
{
Character.AddPrimaryKeys(Player);
Player.SetPossibleKeys(Enumerable.Range(0, 10).ToArray());
}
}
You have to provide possible keys because Groups must be built static. If you need unlimited number of keys then you'll need to go for Index
instead.
You can add as much Primary Key
as you want for a Table
. But you don't wanna end up one entity per Group
, because if memory group is too fragmented you'll lose performance benetfit of ECS. You can consider some of Primary Keys
to Index
for secondary indexing.
public enum CharacterState { Alive, Dead }
public class GameSchema : IEntitySchema
{
public readonly Table<CharacterRow> Character = new();
public readonly Table<ItemRow> Item = new();
public readonly PrimaryKey<PlayerComponent> Player = new();
public readonly PrimaryKey<StateComponent> State = new();
public GameSchema()
{
Character.AddPrimaryKeys(Player, State);
Item.AddPrimaryKeys(Player);
Player.SetPossibleKeys(Enumerable.Range(0, 10).ToArray());
State.SetPossibleKeys(CharacterState.Alive, CharacterState.Dead);
}
}
You can use Where
query to find entities with specific Primary Key.
foreach (var result in indexedDB.Select<CharacterSet>()
.From(schema.Character)
.Where(schema.Player.Is(1)))
You can initialize the IKeyComponent
with EntityInitializer.Build
and your entity will automatically assigned to correct Group
.
But when you update Primary Key, it requries to call IndexedDB.Update
. This will assign key and mark your entity pending to change groups.
indexedDB.Update(ref result.set.player[i], result.egid[i], playerID);
To actually apply the changes, you need to call IndexedDB.Step()
. This should be called before any entity submission. You can treat IndexedDB
as a IStepEngine
and add to SortedEnginesGroup
or UnsortedEnginesGroup
.
indexedDB.Step();
submissionScheduler.SubmitEntities();
We saw how to define Primary Key
to split Table
. In Next Document, we'll see how to define Index
, and how to select Entities over it.