I'm writing some abstract entity system for fun where I have entities with traits. Traits have some fields, including dynamic data field:
enum TraitId {
Movable = 'Movable', Rotatable = 'Rotatable', Scalable = 'Scalable', Collidable = 'Collidable'
}
interface TraitDataMovable {
x: number;
y: number;
}
type TraitDataMap = {
[TraitId.Movable]: TraitDataMovable
, [TraitId.Rotatable]: number // angle
, [TraitId.Scalable]: number // scale
, [TraitId.Collidable]: boolean // collides or not
}
interface TraitData<ID extends TraitId> {
id: ID;
data: TraitDataMap[ID];
disabled?: boolean;
}
type EntityTraits = {
[TID in TraitId]: TraitData<TID>
}
class Entity {
id: string;
traits: Partial<EntityTraits> = {};
}
So far I achieved correct behavior with manual assignment:
const ent = new Entity();
ent.traits.Rotatable = {
id: TraitId.Rotatable, // id can only be Rotatable
data: 100 // data can only be number
};
ent.traits.Collidable = {
id: TraitId.Collidable, // id can only be Collidable
data: true // data can only be boolean
}
const hasCollision = ent.traits.Collidable.data; // correctly typed as boolean
And now I'm trying to write function that adds any trait to the entity:
function addTraitToEntity(entity: Entity, traitData: TraitData<TraitId>) {
entity.traits[traitData.id] = traitData;
// Type 'TraitData<TraitId>' is not assignable to type 'undefined'.
}
function addTraitToEntity2<TID extends TraitId>(entity: Entity, traitData: TraitData<TID>) {
entity.traits[traitData.id] = traitData;
// Type 'TraitData<TID>' is not assignable to type 'Partial<EntityTraits>[TID]'.
// Type 'TraitData<TID>' is not assignable to type 'undefined'
}
They work with // @ts-ignore, however I'd like to get rid of it and do it right. And understand how to have such system typed correctly.