#Instantiate only inhabitant of a unit type
90 messages · Page 1 of 1 (latest)
No, types don't exist at runtime.
The type shouldn't be needed at runtime since the type is known at compile time
Is there a stage in the ts compiler where the type of the generic is known for a function such that a transformer can be written to instantiate the unit type
yeah, it should be possible with a transformer. this isn't exactly what you're after but see https://github.com/Hookyns/tst-reflect for an example of this sort of thing
however i wouldn't usually reach for a transformer as my first instinct. in TS it's better to derive types from values rather than the other way around, for example:
const unit = [1, 2, 3] as const
type Unit = typeof unit
// ^? - type Unit = readonly [1, 2, 3]
const a: Unit = unit
(that may or may not apply to your use case. if you can share more details maybe i'll have other ideas)
also you should be aware that this isn't really a unit type. it's actually hard to make a real unit type in TypeScript because everything can be subtyped (null, undefined, and unique symbol are probably as close as you can get). also since [1, 2, 3] is not a primitive there can be many different values of that type and they're distinguishable by reference equality, for example:
Preview:```ts
type Unit = [1, 2, 3]
const a: Unit = [1, 2, 3]
const b: Unit = [1, 2, 3]
console.log(a === b) // false (they're different references)
const withExtraProperty = <T extends {}>(
x: T
): T & {extraProperty: true} => {
return {...x, extraProperty: true}
}
const c: Unit = withExtraProperty(b) // c is still of type Unit
console.log(c)```
problem is that I want something akin to function t(q: Generic<[T1, T2, T3]>) and then to be able to derive some value from T1, T2, T3. In other languages I could use TypeId, Class[_], Class<?>
yes, the problem is that types simply do not exist in TS
in contrast to other languages
that said, it's not like most other languages even have unit types in the first place
perhaps it's possible in haskell and/or scala. definitely not most other languages though
i'm not even sure about haskell. i know it has () as a unit type, but that might be the only one? same deal in rust
haskell supports singleton types https://hackage.haskell.org/package/singletons
sure, but not your own singleton type (like 1)
is there any way to derive a hash from a generic type?
or is this just not possible in ts
ah yeah i guess you can do just about anything with template haskell
you mean like some kind of unique type ID? if so not really (though the type alias name serves the same purpose)
@muted kettle what's the bigger picture here? in your real code what will t do/be used for?
ecs in the style of bevy where the query type is where you specify what you want
since typescript has typed tuples which you can use as an hlist I thought it would viable
ah yeah, you won't be able to have exactly that same API because TS doesn't do type-directed emit. however you can use a value as your key into the registry (not sure if that's the correct terminology for ECS but hopefully you know what i mean)
my first instinct would be to have a unique symbol for each kind of thing you want to look up and use that
you could also just use classes instead
yeah or use the class value to look up instances of the class
a static member with Symbol() as value?
no, a unique symbol associated with a type
declare const TYPE_ID: unique symbol;
interface TypeId<T> {
[TYPE_ID]: T;
}
function makeTypeId<T>(name?: string): TypeId<T> {
return Symbol(name) as TypeId<T>;
}
idk
oh wait
you still need a way to filter instances
yeah a static member would work
a WeakMap<unknown, Set<symbol>> would work too
where the key is the object and the value is the set of components
wait no
🤷 idk how ecs works
i was thinking something like this (note i have never done ECS so i could be making faulty assumptions):
Preview:```ts
class Component {
baseComponentStuff = true
}
class Foo extends Component {
static id = Symbol("foo")
fooStuff = "whatever"
}
class Bar extends Component {
static id = Symbol("bar")
barStuff = 42
}
const registry = {
[Foo.id]: new Foo(
...```
a static id for each class would work if I can get an array from them from a generic array/typed tuple
!hb mapped types
this works for mapping arrays
this would be a very simple implenetation which also wouldn't type check the way I want it to, and would not be optimised for cache
You can just use a Map and the component class itself as the key for the registry
That removes the need for a unique id per component.
i suspect using objects at all would be not optimised for cache
(@rough drum)
You'd iterate over each entity in the naive implementation in each tick instead of only the entities with the required components which could be orders of magnitude slower
this would be a better type for lookup:
<ID extends keyof Registry>(id: ID, registry: Registry) => Registry[ID] | undefined
right
in that case WeakMap<unknown, symbol>
where the key is the component and the value is the type
like
on one hand a static id works
on the other hand that's just the same as using classes
if you're concerned about structural typing making typescript confused between class C1 {} and class C2 {} (both have the same shape) you can work around that by introducing a private member declare #_: unknown; to make the classes nominal instead of structural
yeah i mostly agree. if you really only ever need to store class instances (and you can guarantee that your classes have a 1:1 mapping of constructor => instance and that the constructors are structurally-distinct from one another), then i'd just use the constructor itself as the ID
Speaking of performance, a big part of what makes ECS very performant, is that components are flat arrays in memory so systems can process them extremely quickly due to cache locality.
In JS, if your components are objects, welp they are all over the heap already.
I think I'll try to just manually supply the "not so unit-type" unit-type
.
@muted kettle
What does the non naive implementation look like?
actually
archetypes/tables
I care more about the ergonomics of using ecs than the performance
I saw, but it would required me using a concrete type instead of just generic and it would make me lose the type information I want
Well, instead of query<Position, Velocity>() you could just do query(Position, Velocity).
you can just use a type assertion, no?
That way at runtime you still have values to perform the lookup.
you have to know the type to get the components in the first place, right?
it would make it unpleasant to implement filtering or change trackers
How so?
unless typescript can differentiate between two spread operators on arrays as function parameters which extend different classes
Yes
Two spread operators?
I guess I'll try something like query<T extends unknown[], F extends Filter<unknown>[]>(t: T, f: F)
class Component {
baseComponentStuff = true
}
function component<T>() {
const instances: T[] = [];
return class extends Component {
static instances = instances;
constructor() {
super();
instances.push(this as any);
}
} as typeof Component & { instances: T[]; }
}
class Foo extends component<Foo>() {
fooStuff = 'whatever'
}
class Bar extends component<Foo>() {
barStuff = 42
}
new Foo();
new Bar();
console.log(Foo.instances, Bar.instances);```
You can also do a method chaining style:
world.query(Foo, Bar, Baz)
.filter(Qux)
ah no this would want an instance and not the constructor
Or do an option object:
world.query({
contains: [Foo, Bar, Baz],
filters: [Qux],
})