johnfn / ts2gd

💥 Compile TypeScript to GDScript for Godot
210 stars 14 forks source link

Idea for adding support for multiple classes in one file. #59

Open adamuso opened 2 years ago

adamuso commented 2 years ago

In readme there is a task in Road to superior development saying:

would be nice to declare multiple classes in the same .ts file and have the compiler sort it out

I have an idea. Godot for every .gd file has one main class and can have subclasses defined by class keyword. This really nicely match typescript export default and export. This way ts2gd will have an easy way of telling if a class in godot should be generated as class_name SomeClass or class SomeClass:. @johnfn what do you think about it?

See #61 for proof of concept.

johnfn commented 2 years ago

Oh no - you're going to be annoyed with me for this but I've actually already implemented multiple classes in a single file! That TODO list is sadly out of date (I was maintaining it back when no one was actually using this project.... lol).

Just so you know, the most up-to-date list of things to do on this project can be found here: https://github.com/johnfn/ts2gd/blob/main/main.ts#L3

I suppose I should update the README to have this information in it. :)

johnfn commented 2 years ago

Two quick comments about your implementation though:

  1. I currently don't create subclasses in the Godot style at all. Every class is its own proper thing in its own file. I like this because it means they way we codegen classes is uniform.
  2. A bit more of a personal preference, but I don't like export default that much because it doesn't work well with autocomplete imports.
adamuso commented 2 years ago

Nah, don't worry. I suspected that it is already implemented by looking at the code. I also do not prefer export default because of lack of good support for auto imports and auto completion, but I've checked recently on VS Code and actually it really has been improved to a point that is quite usable.

My thought was just that default classes are very similiar to godot main class in .gd file. Also it would be very natural for people using default export to think that this is a main class.

adamuso commented 2 years ago

One thing that I do not like about current solution is that one .ts file can correspond to multiple .gd files. That is a bit counter intuitive for me.

johnfn commented 2 years ago

Wow I didn't know that about default imports, thats awesome :)

Yeah it makes sense for people to think the default class is the main one, but how would that affect the TS code? I feel like all the TS code would be the same with either implementation - except I guess we could allow import Blah from file...

johnfn commented 2 years ago

Ah yeah, I hear you on one ts -> multiple gd being counterintuitive, but the thing is, how else are you supposed to add two classes from the same script to different scenes? At least thats what I was imagining it would be used for...

adamuso commented 2 years ago

For an app like ts2gd I think it should not limit the developer from using a specific gdscript functionality. Currently godot allows inner classes and we cannot make use of that feature through TypeScript.

Mainly I think that everything that is possible to do in gdscript should be possible to be represented in TypeScript. In a easy way that TS developers understand of course. Creating multiple classes in one file is very frequent for someone using TS, but then we generate multiple files when we could just create inner classes.

From TypeScript point currently we would only add default keyword and update imports, but this way we create a opening for new feature that is intuitive from TypeScript perspective.

adamuso commented 2 years ago

Here is an example what it could look like:

export default class MyNode {
  a: int
}

export class HelperClass {
   b: string
}

// Class that is not exported have no sense in gdscript, but in TS we can
// use this to limit someone from using that class through an import.
// This is important when we are writing a library for example
// Both cases with export and no export would generate same code for gdscript.
class NotExportedClass {
  c: boolean
}

Example generated code

class_name MyNode

var a: int

class HelperClass:
  var b: int

class NotExportedClass 
  var c: boolean

When you look at both codes you can clearly see exactly which part of gdscript is generated from which lines of TypeScript.

adamuso commented 2 years ago

Ah yeah, I hear you on one ts -> multiple gd being counterintuitive, but the thing is, how else are you supposed to add two classes from the same script to different scenes? At least thats what I was imagining it would be used for...

Godot separates inner classes from the main ones, because inner classes cannot be used to create a node and they also cannot be used to attach a script. We should also tell people that export default will give you clearly a class in gdscript that is visible to Godot and export or no export will give you a helper class that can be used only in other code. At least that is what i'm thinking :)

johnfn commented 2 years ago

Mainly I think that everything that is possible to do in gdscript should be possible to be represented in TypeScript.

Hmm, I don't understand why the goal would be to generate all possible gdscript? In my mind the goal has always been for people using ts2gd to write their entire project in TS, without ever having to worry what the generated GD looks like. So, in my mind, we should target all possible TS, but not worry about targeting all possible GD.

Unless you're thinking of some TS-GD interoperability? But I can't think of how not having inner classes would hamper interoperability...

Open to be convinced the other way though!

adamuso commented 2 years ago

If you want to make ts2gd popular and usable you need to take account what kind of people can use it and what would they like in such tool. I see at least thee kinds of people:

I really like this project, because I myself do not like python syntax style. What I would really like is that the code I write in TypeScript gets converted to GDScript in a natural way which should not need a lot of additional explanations. I hope my opinion does not scare you ;)

Unless you're thinking of some TS-GD interoperability? But I can't think of how not having inner classes would hamper interoperability...

No i do not think right now about interoperability, more likely about reflection of TS code in GDScript code and vice versa for easier learning curve. It would be so nice if this project could attract more TS developers to create games in godot :D

johnfn commented 2 years ago

By all means keep sending me more scary opinions! :D

Preface: the way I've been thinking of the use cases around ts2gd converting TS to GD is in the same way that I think about how TypeScript converts TS to JS. E.g. most people these days just write TS and don't worry too much about the JS output. In fact, when I write TS I basically never look at the JS at all.

However I see that maybe the core issue here is in inspecting the output and making sure it's sane. I kinda noticed that with everyone else as well, that they wanted to inspect the output to make sure it makes sense. I definitely wasn't thinking about that case before, but it seems pretty important. It's kinda hard to do this all the time, I mean even right now we emit a ton of typeof checks when checking equality because of some godot quirkiness, and that makes things hard to read. Maybe we could improve that too...

I think the thing I don't understand is if inner classes are really making the code less readable? I suppose that it's a bit less intuitive if the class ends up in a separate file - they could be confused wondering where it ended up.

ksjogo commented 2 years ago

One thing I would add as that we should consider the debugging experience within Godot. One great advantage of the transpilation approach is that you don't break core godot functionality. Instead I can still use the embedded debugger. For TS->JS readability there is not as important, as the sourcemap will make TS show up in the debugger. But afaik Godot debugger doesn't have sourcemaps, so the user will debug in GDScript. For that it is massively helpful if the structure corresponds to the original file, so the user can know what the change.

adamuso commented 2 years ago

Preface: the way I've been thinking of the use cases around ts2gd converting TS to GD is in the same way that I think about how TypeScript converts TS to JS. E.g. most people these days just write TS and don't worry too much about the JS output. In fact, when I write TS I basically never look at the JS at all.

The thing is that TypeScript has been around for a long time now. There is no need to check if it generates good JS code because with such maturity a lot of people can just blindly trust it. ts2gd however is just starting and most of the time people will be in doubt that this project is actually generating good quality gdscript. We need to ensure people that this project actually tries to generate efficient and understandable code so we can earn their trust.

Through my long experience with TS I had few situations where it generated not valid or the output in JS was really different from what I expected. Of course with next updates TS usually was fixed, but still it is not always good to fully trust a program without good understand what is underneath in its implementation.

adamuso commented 2 years ago

@johnfn I've implemented most of needed features for this change in PR #61 you can review the code and decide what direction you want to take. If you don't like it then we can try to figure out some other solution. Also don't worry I won't cry if you reject all my code in this PR ;) I just wanted to show to you how in my opinion multiple classes should be handled. It does not need to be this way if you feel like it doesn't match your plans.