Skip to content

Instantly share code, notes, and snippets.

@frosch123
Created April 15, 2023 14:50
Show Gist options
  • Save frosch123/e1ede5dff26ef35459a336d33314a693 to your computer and use it in GitHub Desktop.
Save frosch123/e1ede5dff26ef35459a336d33314a693 to your computer and use it in GitHub Desktop.
WASM API Compatibilty

Scenario

  • Replicate callback 39 "cargo profit" into WASM.
  • Later extend it with more more parameters or adjusted mechanics.

Variant 0

As a start, just to give the obvious: Keep the old bit-stuffed parameters, and just pass them to WASM.

int ComputeCargoIncome(int var10, int var18)

Yeah, noone wants that :)

Variant 1

Take todays callback and convert it 1:1 into a function prototype, like you would do it in C.

int ComputeCargoIncome(int distance_tiles, int amount, int cargo_age);

AndyPP extends it to pass a "luxury class".

int ComputeCargoIncome(int distance_tiles, int amount, int cargo_age, int luxury_class);

This already causes problems: there is no function overloading. When calling from C++, one could check the WASM prototype and then pass different parameters. But it's not a nice solution. Maybe a different name instead?

int ComputeCargoIncome_v2(int distance_tiles, int amount, int cargo_age, int luxury_class);

TooTallPP changes the cargo-aging mechanic. Cargo-Age is no longer measured in "cargo aging intervals", but in "economy days". It is decided to not change the API, but rather call the old method with some converted/adjusted value.

int ComputeCargoIncome_v2(int distance_tiles, int amount, int cargo_age_legacy_units, int luxury_class);

But this leaves Add-On developers sad, since they cannot use the new precision and mechanics.

Actually Add-On developers were mad before, because when AndyPP extended the API, this was in conflict with an extension from JGRPP, which added a "vehicle type" instead:

int ComputeCargoIncome_JGR(int distance_tiles, int amount, int cargo_age, int vehicle_type);

The creators of ExcelPP are also sad. Returning a single "cost" is unrealistic. The proper callback result should be a tuple with:

  • Local cargo price.
  • Worker wage for unloading the cargo.
  • Local sales and customs tax.

Only with this information you can simulate a proper business.

Everyone is sad.

Variant 2

Have a single prototype for the "feature", but do not commit on any parameters or results, and the order and format of them.

void ComputeCargoIncome()

Instead have individual Getters and Setters for parameters and results.

int ComputeCargoIncome_GetDistance_Tiles();
int ComputeCargoIncome_GetCargoAmount_Units();
int ComputeCargoIncome_GetCargoAge_Intervals();
void ComputeCargoIncome_SetPayment(int money);

The various PP now add their additions:

  • AndyPP and JGRPP add new getters for new parameters:
int ComputeCargoIncome_GetLuxuryClass();
int ComputeCargoIncome_GetVehicleType();
  • TooTallPP adds a new getter for an existing var, but with a new unit:
int ComputeCargoIncome_GetCargoAge_EconomyDays();
  • ExcelPP adds new setters for more results:
void ComputeCargoIncome_SetStationServiceCost(int money);
void ComputeCargoIncome_SetTaxes(int money);

The various PP can detect which Getters/Setters the Add-Ons want to link, and thus know whether they are compatible or not. There is no need for a linear versioning of the API.

Variant 3

In variant 2 parameters and results are essentially passed via global state. This may make it harder to use add-on libraries and generalise things.

Instead parameters and results could be passed as polymorphic objects. Since WASM has no pointers, we have to use handles to identify objects.

int ComputeCargoIncome(int H_ComputeCargoIncome_Param);

Both the parameter and the result are object handles. The handle H_ComputeCargoIncome_Param must be passed to all getters:

int ComputeCargoIncome_GetDistance_Tiles(int H_ComputeCargoIncome_Param);

At some point the callback has to create the result object:

int ComputeCargoIncome_Result_Create();

This handle can be passed around, and you can call Getters and Setters on it:

void ComputeCargoIncome_Result_SetPayment(int H_ComputeCargoIncome_Result, int money);
int ComputeCargoIncome_Result_GetPayment(int H_ComputeCargoIncome_Result);

Add-ons can import add-on libraries, pass handles to library methods, and let the library get/set things. Add-ons can also first call some clone method and pass a new instance to the library, and later only copy selected results to the original instance.

@michicc
Copy link

michicc commented Apr 15, 2023

Variant 3b: There's just one handle object that also takes the results back.

@frosch123
Copy link
Author

Variant 3b is interesting:

  • Results are set in the context of the parameters. So they may reference things defined by the parameters. Imagine input parameters not being simple types, but handles themself, which may have more getters and setters.
  • The result object is already created by OpenTTD. Meaning OpenTTD can pass-in "default result values", instead of filling-in "unassigned values" after return.

@michicc
Copy link

michicc commented Apr 15, 2023

Yeah, so for the cargo income example, the object could already contain the results (and parameters) of the normal OTTD income calculation beforehand. And a script can then either overwrite the result or just change some of the parameter and call OTTD again to re-perform the default calcs with the altered parameters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment