#how allow tuple generic to union generic?

50 messages Β· Page 1 of 1 (latest)

ancient wraith
#

Hi how i can allow a class with tuple as generic to be pass to a function with a class with union generic ?
is it possible , is there a suggestion you can give me ?
thanks

Here the code , i want pass entityA to function useEntity(entityA ).
I want the function accept any Entities with components 1[]

type Component = number;
type EntityWith<C extends Component> = Entity<C[]>

abstract class Entity<C extends Component[]> {
  components!: () => C[number][]
  constructor(components: C) {
  }
}

// pass any entity with min component type 1
function useEntity(entity: Entity<1[]>) {}
//                   ^?

declare const entityA: Entity<[1, 3]>
useEntity(entityA); // how allow this because it have the min requirement [1]
//           ^?

// specialised class will look like this .
class EntityA extends Entity<[1,3]> {
  constructor() {
    super([1,3])
  }
}

ember joltBOT
#
jonlepage#0

Preview:```ts
type Component = number
type EntityWith<C extends Component> = Entity<C[]>

abstract class Entity<C extends Component[]> {
components!: () => C[number][]
constructor(components: C) {}
}

// pass any entity with min component type 1
function useEntity(entity: Entity<1[]>) {}
...```

regal wren
#

you might want this?

ember joltBOT
#
mkantor#0

Preview:ts ... function useEntity(entity: Entity<[1, ...number[]]>) {} ...

regal wren
#

that allows any array of numbers that has 1 in the first position

stray sluice
#

A tuple has length, and is ordered, which are both irrelevant properties to the thing you are trying to model (components of an entity)

#

Instead if you model the components as a union, it's much easier, eg Entity<1 | 3>.

#

Because it makes sense that the order of components doesn't matter, nor the amount of components an entity has.

#

With that, all you need to do next is to structure your Entity<T> in a way that Entity<A | B> is assignable to Entity<A>, which should be very trivial to do.

ember joltBOT
#
nonspicyburrito#0

Preview:```ts
declare class Position {
x: number
y: number
}

declare class Health {
value: number
}

declare class Entity<C> {
get<T extends C>(
component: new (...args: never) => T
): T
}

// Usage

function move(
entity: Entity<Position>,
dx: number,
dy: numb
...```

stray sluice
#

This is one implementation of the idea.

ancient wraith
#

ts look so broken

stray sluice
#

That can be easily fixed by just branding it.

ember joltBOT
#
nonspicyburrito#0

Preview:```ts
declare class Position {
x: number
y: number
}

declare class Health {
value: number
}

declare class Entity<C> {
private declare _c: (arg: C) => void

get<T extends C>(
component: new (...args: never) => T
): T
}

// Usage

function move(
entity: Entity<Position>,
dx: number,
dy: numb
...```

ancient wraith
stray sluice
#

You can use a brand to decide if you want co or contravariance.

ancient wraith
stray sluice
#

But this Entity<C> implementation has nothing but a get component method, in your actual code you probably will have more things that will already enforce the variance, so the brand wouldn't be necessary.

ancient wraith
ember joltBOT
#
jonlepage#0

Preview:```ts
declare class Position {
x: number
y: number
}

declare class Health {
value: number
}

declare class Entity<C> {
private declare _c: (arg: C) => void
get<T extends C>(
component: new (...args: never) => T
): T
constructor(c: C[])
}

class EntityA extends Entity<Posi
...```

ancient wraith
#

my current pattern look like this with tuple but it have the issue you explain me, really need constructor typeguard but also ability to do what your pattern allow πŸ€“

stray sluice
#

That's not called a type guard, type guard in TS refers to a very particular concept.

ancient wraith
#

ho oki sorry my ts vocabulary is limited πŸ™‚

stray sluice
#

All good

#

If you want to enforce constructor needing all components to present, there are a few ways to go about it, one could generate the constructor parameters from the generic

#

The other way would be to separate the get out into a separate type, so the Entity class can go back to using tuple.

ember joltBOT
#
nonspicyburrito#0

Preview:```ts
declare class Position {
x: number
y: number
}

declare class Health {
value: number
}

declare class Entity<C extends unknown[]> {
constructor(components: C)
get<T extends C[number]>(
component: new (...args: never) => T
): T
}

type EntityWith<C> = {
get
...```

stray sluice
#

Here's an implementation of the second idea.

#

Although going back to the tuple ways has some of the issues talked about above.

#

I guess a third option could be mixin.

ancient wraith
#

ho oki ya , this not allow union for EntityWith humm

stray sluice
#

Eh that's more so issue with the EntityWith type, the correct way would be to do a U2I rather than a generic.

#

The mixin approach with U2I:

ember joltBOT
#
nonspicyburrito#0

Preview:```ts
type Ctor<T> = abstract new (...args: never) => T

declare function Entity<C extends Ctor<object>[]>(
...components: C
): new (
...component: {[K in keyof C]: InstanceType<C[K]>}
) => Entity<InstanceType<C[number]>>

type Entity<C> = (
C extends C
? (u: {get(component: Ctor<C>): C}) => 0
: n
...```

ancient wraith
#

I'm so tired I'm not sure what I'm doing anymore

ancient wraith
#

Thank you very much for your time, it seems to be working well but I will push my tests further by tomorrow when I have a clearer mind.
I feel like I may have overengineered my pattern a bit for nothing.
Your time was been very helpful.
I will close the thread tomorrow if everything goes well πŸ™‚

ancient wraith
# ember jolt

Oki Dam!, this pattern was really interesting.
This gave me an insight and i just experimented with a similar pattern and to my surprise, TypeScript handles it very well.
Additionally, I can compose without using interfaces to build a solid and readable compositions of types generics !
am happy to ask here ! i just discover new thing πŸ™‚ !

#

So, I managed to cover 90% of my needs before to be able to migrate my current design.
However, I'm facing 2 points that I can't figure out.
If you think you might have an idea!?, since am in few hours on this and I find myself in a loop of recursive fix/new error/fix/new error....

There's one green point 🟒 and a red point πŸ”΄ that seem not to be working and i cant solve.
All others thing work very fine !
thanks again friend !

ancient wraith
#

this one was really weird because i assign default array in Compositor<CT extends ComponentType[]=[]>
But ts keep infer ComponentType[] instead of [] to SystemWithRule ! this make not sense for me !

stray sluice
# ancient wraith tsplayground: http://tinyurl.com/mt9uc6cf

I don't have time to dig into it, but from a quick glance:

  • The EntityWith<T> is using the generic solution I gave which is not exactly sound, you should switch to the U2I solution I gave later.
  • Your Entities<T> uses U2I but I'm not sure what purpose it's supposed to fill, from name and usage it just seems like Entities<Player | Player2> is supposed to just be Player | Player2?
  • Your ComponentA, ComponentB, etc are all structurally the same. You need to brand them and make them actually structurally different, eg declare private _: 'a', or declare private _a: never, otherwise they do have brands but they are all the same brand.
    These might not be the causes of why your code isn't working, but just some I can point out right away.
ancient wraith
#

bonus i can infinite extends if i wants and no extra type need ! this rock ! i can just focus on js layer