ModAPI.promisify

This commit is contained in:
ZXMushroom63 2024-10-04 13:59:53 +08:00
parent a969f35b9a
commit 541ac83f5f
6 changed files with 67 additions and 17 deletions

View File

@ -94,6 +94,9 @@ The ModAPI object has the following methods:
- Triggers a right click ingame.
- `getFPS() : int`
- Gets the frames per second of the game
- `promisify(asyncJavaMethod: Method | Constructor) : PromisifiedJavaRunner`
- Allows running java methods that are @Async/@Async dependent.
- More [PromisifyDocumentation](promisify.md)
## Handling strings, numbers and booleans to and from java.

26
docs/apidoc/promisify.md Normal file
View File

@ -0,0 +1,26 @@
## ModAPI.promisify()
Some methods in java are asynchronous, meaning that they don't return a value/modify state immediately. Calling them in an event or in a patch to a normal function will cause a stack implosion, characterised by the client/dedicated server hanging without any error messages.
In order to call them properly from javascript, you need to use the `ModAPI.promisify()` function.
For example, here we have a simple client-side command that will try to use the `PlatformRuntime` class to download data from a URI:
```javascript
ModAPI.addEventListener("sendchatmessage", function downloadSomething(e) {
if (e.message.toLowerCase().startsWith("/downloadtest")) {
var arraybuffer = ModAPI.hooks.methods.nlevi_PlatformRuntime_downloadRemoteURI(ModAPI.util.str("data:text/plain,hi"));
console.log(arraybuffer);
}
});
```
This will cause the client to hang. The correct way of calling this asynchronous method is like this:
```javascript
ModAPI.addEventListener("sendchatmessage", function downloadSomething(e) {
if (e.message.toLowerCase().startsWith("/downloadtest")) {
ModAPI.promisify(ModAPI.hooks.methods.nlevi_PlatformRuntime_downloadRemoteURI)(ModAPI.util.str("data:text/plain,hi")).then(arraybuffer => {
console.log(arraybuffer);
});
}
});
```
You can replace the argument with any other method or constructor, including non asynchronous ones.

View File

@ -19,6 +19,9 @@ Calling methods while the TeaVM thread is in a critical transition state (see `M
Update 22/09/2024:
See Asynchronous Code
Update 4/10/2024:
@Async issue solved, see [PromisifyDocumentation](apidoc/promisify.md)
#### TeaVM thread suspension/resumption
TeaVM allows for writing asynchronous callbacks, which eaglercraft uses for file operations and downloading from URIs. However, when a method that makes use of an async callback gets run from ModAPI, it triggers a stack implosion due to mismatches in value types upon return (as well as a whole other myriad of symptoms). Currently this is not supported by ModAPI, and it will take some time until it will be. In the meanwhile, avoid using constructors or methods that access a file or use other asynchronous apis. Examples:
- Constructing an EntityPlayerMP

View File

@ -22,12 +22,9 @@
// Get the EntityPlayerMP class to spawn the fake player
const EntityPlayerMPClass = ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP");
setTimeout(() => {
ModAPI.hooks._teavm.$rt_startThread(() => {
return EntityPlayerMPClass.constructors[0](ModAPI.server.getRef(), world.getRef(), fakeProfile, playerInteractionManager);
}, function (...args) {
console.log(this, args);
var fakePlayer = ModAPI.util.wrap(args[0]);
ModAPI.promisify(EntityPlayerMPClass.constructors[0])(ModAPI.server.getRef(), world.getRef(), fakeProfile, playerInteractionManager).then(result => {
console.log(result);
var fakePlayer = ModAPI.util.wrap(result);
// Set the fake player position to be near the command sender
console.log(senderPos);
@ -40,7 +37,6 @@
const ChatComponentTextClass = ModAPI.reflect.getClassById("net.minecraft.util.ChatComponentText");
event.sender.addChatMessage(ChatComponentTextClass.constructors[0](ModAPI.util.str("Fake Steve has been spawned!")));
});
}, 1);
// Prevent the command from executing further

View File

@ -3,6 +3,11 @@ ModAPI.hooks._teavm.$rt_startThread(() => {
return ModAPI.hooks.methods.nlevi_PlatformRuntime_downloadRemoteURI(ModAPI.util.str("data:text/plain,hi"))
}, function (...args) { console.log(this, args) })
// SUCCESS - Runs anywhere, anytime. Might work with async/await, but for now stick to .then()
ModAPI.promisify(ModAPI.hooks.methods.nlevi_PlatformRuntime_downloadRemoteURI)(ModAPI.util.str("data:text/plain,hi")).then(result => {
console.log(result); //Log arraybuffer
});
//WIP - Pausing and resuming client thread
globalThis.suspendTest = function (...args) {

View File

@ -543,6 +543,23 @@ globalThis.modapi_postinit = "(" + (() => {
ModAPI.hooks.freezeCallstack = false;
}
//Function used for running @Async / @Async-dependent TeaVM methods.
ModAPI.promisify = function promisify(fn) {
return function promisifiedJavaMethpd(...inArguments) {
return new Promise((res, rej) => {
Promise.resolve().then( //queue microtask
() => {
ModAPI.hooks._teavm.$rt_startThread(() => {
return fn(...inArguments);
}, function (out) {
res(out);
});
}
);
});
}
}
ModAPI.util.string = ModAPI.util.str = ModAPI.hooks._teavm.$rt_str;
ModAPI.util.setStringContent = function (jclString, string) {