Home > Software engineering >  Typescript for ECS: type of entities for autocomplete helps with components
Typescript for ECS: type of entities for autocomplete helps with components

Time:01-03

Is there a way to define type of Entities the way that autocomplete helps with retrieving information about components, correctly interfering types and keys of components?

I've tried the next solution, but it shows errors, because it can't infer correct type of the property from the property name (it requires to use as SpecificType to compile):

interface IComponent {
    type: string;
}

export const RenderComponentDefaultValue = {
    type: "render",
    renderValue: true
}
export type RenderComponent = typeof RenderComponentDefaultValue;

export const HealthComponentDefaultValue = {
    type: "health",
    healthValue: 100
}
export type HealthComponent = typeof HealthComponentDefaultValue;

interface IEntity<ComponentTypes extends IComponent = IComponent> {

    id: string;
    
    // I guess the magic should happen here, with complex typings of the components property
    components: {
        [key in ComponentTypes["type"]]: ComponentTypes
    }
}

// TEST
const testEntity: IEntity<RenderComponent | HealthComponent> = {
    // initialization code
};
//
testEntity.components.render.renderValue // SHOULD BE OK
testEntity.components.render.not_RenderValue // SHOULD BE ERROR
testEntity.components.health.healthValue // SHOULD BE OK
testEntity.components.health.not_healthValue // SHOULD BE ERROR

Link to playground

CodePudding user response:

We first need to declare the default values with as const.

export const RenderComponentDefaultValue = {
    type: "render",
    renderValue: true
} as const

export const HealthComponentDefaultValue = {
    type: "health",
    healthValue: 100
} as const

Otherwise, the inferred type of type will be just string. We need to let the compiler infer the literal type which can be done using as const.

The mapped type inside of IEntity needs to be changed a bit too.

interface IEntity<ComponentTypes extends IComponent = IComponent> {

    id: string;
    
    components: {
        [K in ComponentTypes as K["type"]]: K
    }
}

We map over the constituent in ComponentTypes which means that K is now the full object type. Since object types can't be used as key names, we remap the key to K["name"]. The type of each key will be K itself.

The type should now work as expected.

declare const testEntity: IEntity<RenderComponent | HealthComponent>

testEntity.components.render.renderValue // OK
testEntity.components.render.not_RenderValue // Error: 'not_RenderValue' does not exist on type
testEntity.components.health.healthValue // OK
testEntity.components.health.not_healthValue // Error: 'not_healthValue' does not exist on type

Playground

  • Related