My approach has been aligned with the current architecture, focusing on reducing complexity as much as possible.
I have strived for the best possible outcome, but encountered a single failed test along the way.
After three days of struggling, I'm close to giving up. However, if your expertise can provide a solution that guarantees the success of all tests, it would be truly remarkable!
Could getComponentFromEntity
be the key? Perhaps some kind of magical pattern is needed to solve this issue?!
Thank you for taking the time to help out.
https://i.sstatic.net/hl25Q.png β½playground link
//utils types
type Constructor<T> = { new(...args: any): T };
type ExtractComponentType<T> = T extends Entity<infer C> ? C : never;
type EntitiesCareMap<C extends Component> = Map<number, Entity<C>>
type ComponentType<T extends Component = Component> = Constructor<T>;
type TupleToInstances3<T extends readonly unknown[]> = {
[K in keyof T]: T[K] extends Constructor<infer U> ? U extends {} ? U : never : never;
}
type ExtractSystemComponents4<
S extends Rules,
K extends RULES
> = S[K] extends ComponentType[] ? S[K] extends never[] ? UnknowComponent : TupleToInstances3<S[K]>[number] : never;
interface SystemUpdate<S extends System = System, R extends Rules = S['rules']> {
entities: EntitiesCareMap<
ExtractSystemComponents4<R, RULES.hasAll>
>;
}
// issue
class Entity<
C extends Component = Component,
> {
declare public components: Set<C>;
get<T extends C>(componentClass: Constructor<T>): T {
return undefined as unknown as T;
}
has<T extends Component>( componentClass: Constructor<T> ): this is Entity<T> {
return false;
}
}
abstract class Component {
foo() { }
}
enum RULES {
hasAll,
}
type Rules = { readonly [K in RULES]?: ComponentType[] };
abstract class System {
abstract rules: Rules;
abstract onUpdate(t: SystemUpdate<System, Rules>): void;
}
export class UnknowComponent extends Component {
#component!: never;
}
export class AComponent extends Component {
#component!: never;
}
export class BComponent extends Component {
#component!: never;
}
export class CComponent extends Component {
#component!: never;
}
export class DComponent extends Component {
#component!: never;
}
class SystemA extends System {
public rules = {
[RULES.hasAll]: [AComponent, BComponent],
};
onUpdate({entities}: SystemUpdate<SystemA>) {
entities.forEach(( e ) => {
e.get(BComponent)// π’ this should pass.
e.get(AComponent)// π’ this should pass.
e.get(CComponent)// π΄ this should error
if (e.has(CComponent)) {
e.get(CComponent)// π’ this should pass.
e.get(DComponent)// π΄ this should error
if (e.has(DComponent)) {
e.get(DComponent)// π’ this should pass.
}
}
});
}
}
declare const ab: Entity<BComponent> | Entity<BComponent | CComponent>;
/** Get a components from entity */
function getComponentFromEntity<E extends Entity, C extends ExtractComponentType<E>>(entity: E, component: Constructor<C>): C {
return entity.get(component);
}
getComponentFromEntity(ab, BComponent) // π’ this should pass.
getComponentFromEntity(ab, AComponent) // π΄ this should error.
getComponentFromEntity(ab, CComponent) // π΄ this should error.
//^?
declare const a: Entity<BComponent | CComponent>;
a.get(BComponent)// π’ this should pass.
a.get(AComponent)// π΄ this should error