jakobhellermann/bevy_mod_js_scripting

Export TypeScript Definitions For Game Components

zicklag opened this issue · 11 comments

It should be possible for us to export TypeScript definitions for all of the types that we can find in the Bevy type registry. That would allow us to give type definitions for all the components you can interact with in scripts.

We may want to come up with a way that you could import the type definition for Vec3 for instance, and then it could contain the full type name in it so that you could use it to extract the ComponentInfo and ComponentId for that type.

This needs some thought, but I think we should be able to make a nice workflow.

Maybe they could be autogenerated using the static TypeInfo if available https://docs.rs/bevy_reflect/latest/bevy_reflect/enum.TypeInfo.html

Yeah, that was what I was thinking. And if anything is dynamic it would just map to any.

It's not possible to associate values with types in typescript, right?

It would be cool if

type Transform = {
  translation: Vec3;
  // ???
};

console.log(Transform.type_name) // the type name we can look up in the type registry 

would work somehow.

Like an associated constant in rust

Yeah, unfortunately the closest thing I can find so far is a class with static field:

class Velocity {
  static typeName: string = "breakout::Velocity";
  0: Vec3
}

class Vec3 {
  static typeName: string = "bevy::math::Vec3";
  x: number;
  y: number;
}

This works, but unfortunately because it's an actual class and not done at TypeScript compile time, it requires that we actually import the generated bindings. We can't put them in an "ambient context" like the rest of the types in lib.bevy.d.ts.

That might require that we find a way to mange manage imports in scripts, but I did want to do that anyway, and I don't think it's going to be incredibly difficult, now that I got export rewriting working in #11.

That alone can't make

world.getResource<Time>();

work, right?
Because we have no instance of the type available.

Maybe

function getResource<T>(ty: BevyType<T>) {}

interface Time {
  delta_seconds: number;
}
const Time: BevyType<Time> = { typeName: "..." };


let time = world.getResource(Time);
// Time type is inferred 

I don't know if typescripts type inference is good enough for that, and if it has separate type and value namespaces.

Update: typescripts inference is strong enough.
This works:

type BevyType<T> = { typeName: string; };

type Time = {
    seconds: number,
};
const Time: BevyType<Time> = { typeName: "bevy_core::time::Time" };

function getResource<T>(type: BevyType<T>): T {
    throw new Error();
}

let resource = getResource(Time);
resource.seconds;

Oh sweet! That's great.

Exporting all types in a quick and hacky way is pretty easy:
https://github.com/jakobhellermann/bevy_reflect_ts_type_export/blob/main/generated/types.ts

And working with typed APIs is super nice:

type Scoreboard = {
score: number;
};
const Scoreboard: BevyType<Scoreboard> = { typeName: "breakout::Scoreboard" };
function run() {
i++;
if (i % 60 == 0) {
let time = world.resource(Scoreboard);
time.score += 1;
info(time.score);
}

TS can just overload two method signatures, and the deno op can deal with either.

resource(componentId: ComponentId): Value | null;
resource<T>(type: BevyType<T>): T | null;

Great, now we just have to set it up so you can import { Vec3 } from "./types.ts in a script so that you don't have to copy-and-paste the components from types.ts.

I think I'll work on that after finishing #11, which is getting pretty close to working now.