adventuregamestudio / ags

AGS editor and engine source code
Other
700 stars 160 forks source link

Method chaining-based script API #2015

Open edmundito opened 1 year ago

edmundito commented 1 year ago

Describe the problem Context: This is a proposal aimed at AGS 4. I recall the transition from AGS 2 to 3 introduced a more object-oriented script API. We have a lot more information now about how to improve the scripting for AGS 4 because we have open-sourced projects, and we can see how developers make their games through streaming.

The problem that I'm trying to solve is to eliminate repitition while writing scripts and make the environment more simple. At the same time, take advantage of the new compiler features.

Suggested change The idea is to extend the scripting API object-oriented implementation to always return the object so that developers can method chain their calls.

Here's an example:

// AGS 3
player.Say("Hang on.....");
player.Animate(4, 1);
player.Say("This door is locked?!");

// AGS 4
player
  .Say("Hang on...")
  .Animate(4, 1)
  .Say("This door is locked?!")
ivan-mogilko commented 1 year ago

The serious issue with this is that some methods may be wanted to return other objects as their result.

For example, this is the proposal I added only few days ago: #2010 , which among other things suggests Animate() function to return Animation struct.

Another example, existing function SayBackground already returns Overlay as a result.

This all has to be thought about very thoroughly.

EDIT: I have a sudden crazy idea of supporting a comma-separating syntax instead:

player.
  Say("Hang on..."),
  Animate(4, 1),
  Say("This door is locked?!");
fernewelten commented 1 year ago
player
  .Say("Hang on...")
  .Animate(4, 1)
  .Say("This door is locked?!");

I think I remember that the old compiler doesn't like chaining method calls. Also, I think that the old compiler has a maximal length for expressions, so call chains would be limited that way.

The new compiler can do this, however, although I believe that this hasn't been stress-tested yet.

As far as Ivan's edit is concerned, ABAP (SAP's proprietary language) sports a syntax of the form

A: B, C, D.

This gets expanded at the preprocessing stage into

A B.
A C.
A D.

(The part in front of the colon is repeated, the comma seperated items after the colons are each expanded into separate statements. The full stop is ABAP's semicolon.)

So something like that has been done and we could have it if we want to, probably handling it in the preprocessor.

We could introduce a compound statement such as

with (player) 
{
    Say("Hang on...");
    Animate(4, 1);
    Say("This door is locked?!");
}

and make the compiler compile this into

player.Say("Hang on...");
player.Animate(4, 1);
player.Say("This door is locked?!");

This would work no matter what the individual methods return and perhaps be a bit more C#-ish.

messengerbag commented 1 year ago

I think I remember that the old compiler doesn't like chaining method calls.

Yeah, it won't let you do things like myString = myString.Substring(0,trimLength).Append("\n"); which would be very convenient.

ericoporto commented 1 year ago

Yeah, it won't let you do things like myString = myString.Substring(0,trimLength).Append("\n"); which would be very convenient.

Just to be clear, the new compiler already supports this, but the autocomplete hasn't been updated for such.

About the suggestion of using with keyword, it usually can take a factory like method that will create the reference (that is anonymous) and then the reference goes out of scope afterwards - so if you take a method that creates a file reference at the end it gets closed when the } is reached.

I am not sure how this works in C#, if there's a special handling or if its simply methods that return the reference. :/ (SO says its just methods that return this, but the power there apparently comes from generics).

AlanDrake commented 1 year ago

Hmm, I see no big problem in letting users chain methods that currently return nothing.

ivan-mogilko commented 1 year ago

Hmm, I see no big problem in letting users chain methods that currently return nothing.

  1. This will be inconsistent;
  2. This may cause issues later if we would need to return something else from these methods.
AlanDrake commented 1 year ago

Inconsistency has never stopped javascript framework developers, but perhaps I've gotten too accustomed to the horrors of the web.

ivan-mogilko commented 1 year ago

I think, I found another argument against relying on same object being returned from a method for the purpose of chaining.

Suppose we have a class hierarchy, and the parent class declares "virtual" common methods shared among its children. Obviously, these methods must return parent class's pointer. This means that if this method will be met in the chain of child's calls, you cannot continue the chain afterwards.

For an abstract example:

ListBox* myListBox;

myListBox
    .AddItem("text") // ListBox's method AddItem returns ListBox*
    .SendToBack()   // GUIControl's method SendToBack() returns GUIControl*
    .ScrollUp();   // but GUIControl does not have a method ScrollUp? = script error
ericoporto commented 1 year ago

Had an idea for the with @fernewelten propposed

with (player) 
{
    Say("Hang on...");
    Animate(4, 1);
    Say("This door is locked?!");
}

What if with keyword overrides this to mean whatever is the object in parentheses? Because in the new compiler the this can be hidden, it would instead mean a different thing. Of course this would mean we could not nest with calls, but that looks like an ok limitation.