Skip to content

Commit 80ad1f0

Browse files
committed
API to navigate asynchronously
1 parent f673185 commit 80ad1f0

12 files changed

Lines changed: 245 additions & 36 deletions

File tree

src/client/java/com/hanprogramer/androids/client/screen/android/InventoryTab.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ public void build(List<Drawable> elements) {
4242
mc.interactionManager.clickButton(parent.handler.syncId, AndroidInventoryScreenHandler.SWITCH_BUTTON_ON);
4343
else
4444
mc.interactionManager.clickButton(parent.handler.syncId, AndroidInventoryScreenHandler.SWITCH_BUTTON_OFF);
45-
System.out.println(value);
4645
})));
4746
}
4847
}

src/client/java/com/hanprogramer/androids/client/screen/android/SettingsTab.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import com.hanprogramer.androids.client.screen.base.BaseScreenTab;
44
import com.hanprogramer.androids.client.screen.widgets.ColoredButtonWidget;
5-
import com.hanprogramer.androids.entities.android.scripting.BlockArea;
6-
import com.hanprogramer.androids.entities.android.util.AndroidProperty;
5+
import com.hanprogramer.androids.entities.android.util.BlockArea;
6+
import com.hanprogramer.androids.entities.android.scripting.AndroidProperty;
77
import com.hanprogramer.androids.network.AndroidC2SSetBlockAreaBeginPayload;
88
import com.hanprogramer.androids.network.AndroidC2SSetBlockPosBeginPayload;
99
import com.hanprogramer.androids.network.AndroidC2SSetPropertyPayload;

src/main/java/com/hanprogramer/androids/entities/android/AndroidEntity.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package com.hanprogramer.androids.entities.android;
22

33
import com.hanprogramer.androids.entities.android.inventory.TrackedInventory;
4-
import com.hanprogramer.androids.entities.android.scripting.BlockArea;
4+
import com.hanprogramer.androids.entities.android.util.BlockArea;
55
import com.hanprogramer.androids.entities.android.scripting.SelfBridge;
6-
import com.hanprogramer.androids.entities.android.scripting.Sleeper;
6+
import com.hanprogramer.androids.entities.android.util.Sleeper;
77
import com.hanprogramer.androids.entities.android.scripting.WorldBridge;
8-
import com.hanprogramer.androids.entities.android.util.AndroidProperty;
8+
import com.hanprogramer.androids.entities.android.scripting.AndroidProperty;
99
import com.hanprogramer.androids.entities.android.util.AndroidPropertyList;
1010
import com.hanprogramer.androids.items.BatteryItem;
1111
import com.hanprogramer.androids.items.RemoteItem;
@@ -629,7 +629,7 @@ public void updateProperties() {
629629
for (var u : unusedProps) {
630630
u.unused = true;
631631
}
632-
System.out.printf("%s unused vars, %s total vars%n",unusedProps.size(), appliedProps.size());
632+
// System.out.printf("%s unused vars, %s total vars%n",unusedProps.size(), appliedProps.size());
633633
appliedProps.addAll(unusedProps);
634634
dataTracker.set(properties, List.copyOf(appliedProps));
635635
}
@@ -692,7 +692,7 @@ public void applyProperty(String propId, Object value) throws Exception {
692692
}
693693

694694
public void logMessage(String message) {
695-
_log += message;
695+
_log += message + "\n";
696696
if (_log.length() > 1024) {
697697
_log = _log.substring(_log.length() - 1024);
698698
}
@@ -712,4 +712,8 @@ public void clearLog() {
712712
public String getLog() {
713713
return dataTracker.get(log);
714714
}
715+
716+
public Context getJsCtx() {
717+
return jsCtx;
718+
}
715719
}

src/main/java/com/hanprogramer/androids/entities/android/util/AndroidProperty.java renamed to src/main/java/com/hanprogramer/androids/entities/android/scripting/AndroidProperty.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
package com.hanprogramer.androids.entities.android.util;
1+
package com.hanprogramer.androids.entities.android.scripting;
22

3-
import com.hanprogramer.androids.entities.android.scripting.BlockArea;
3+
import com.hanprogramer.androids.entities.android.util.BlockArea;
44
import com.hanprogramer.androids.util.CodecPacketBridge;
55
import com.mojang.datafixers.util.Pair;
66
import com.mojang.serialization.*;

src/main/java/com/hanprogramer/androids/entities/android/scripting/SelfBridge.java

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.hanprogramer.androids.entities.android.scripting;
22

33
import com.hanprogramer.androids.entities.android.AndroidEntity;
4-
import com.hanprogramer.androids.entities.android.util.ContainerExtractor;
5-
import com.hanprogramer.androids.entities.android.util.Seeder;
4+
import com.hanprogramer.androids.entities.android.util.*;
5+
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
66
import net.minecraft.block.BlockState;
77
import net.minecraft.block.entity.BlockEntity;
88
import net.minecraft.entity.Entity;
@@ -13,23 +13,28 @@
1313
import net.minecraft.item.ItemStack;
1414
import net.minecraft.item.Items;
1515
import net.minecraft.registry.Registries;
16+
import net.minecraft.server.MinecraftServer;
1617
import net.minecraft.server.world.ServerWorld;
1718
import net.minecraft.text.Text;
1819
import net.minecraft.util.Identifier;
1920
import net.minecraft.util.math.BlockPos;
2021
import net.minecraft.util.math.Box;
2122
import net.minecraft.util.math.Vec3d;
2223
import net.minecraft.world.World;
24+
import org.graalvm.polyglot.Context;
2325
import org.graalvm.polyglot.HostAccess;
2426
import org.graalvm.polyglot.Value;
2527
import org.graalvm.polyglot.proxy.ProxyArray;
28+
import org.graalvm.polyglot.proxy.ProxyExecutable;
2629
import org.jetbrains.annotations.NotNull;
2730
import org.jetbrains.annotations.Nullable;
2831

2932
import java.lang.ref.WeakReference;
3033
import java.util.ArrayList;
3134
import java.util.List;
3235
import java.util.Optional;
36+
import java.util.concurrent.CompletableFuture;
37+
import java.util.concurrent.atomic.AtomicInteger;
3338
import java.util.function.Predicate;
3439

3540
// Minimal bridges with explicit exposure
@@ -112,6 +117,131 @@ public void navigateTo(int @Nullable [] pos) throws Exception {
112117
e.getNavigation().startMovingAlong(e.getNavigation().findPathTo(pos[0] + 0.5, pos[1], pos[2] + 0.5, 0), (double) 0.5F);
113118
}
114119
}
120+
@HostAccess.Export
121+
public Value navigateToAsync(int[] pos, int timeoutTicks, double acceptDistance) {
122+
AndroidEntity e = get();
123+
if (e == null) { return null; }
124+
125+
var jsCtx = e.getJsCtx();
126+
MinecraftServer server = ((ServerWorld)e.getWorld()).getServer();
127+
// call your original CF-returning method
128+
CompletableFuture<Boolean> cf = navigateToAsyncCF(pos, timeoutTicks, acceptDistance);
129+
// bridge to a JS Promise so 'await' works
130+
return JsInterop.toPromise(jsCtx, server, cf);
131+
}
132+
public CompletableFuture<Boolean> navigateToAsyncCF(int[] pos, int timeoutTicks, double acceptDistance) {
133+
AndroidEntity e = get();
134+
if (e == null) return CompletableFuture.completedFuture(false);
135+
if (pos == null || pos.length < 3) {
136+
CompletableFuture<Boolean> f = new CompletableFuture<>();
137+
f.completeExceptionally(new IllegalArgumentException("pos must be length >= 3"));
138+
return f;
139+
}
140+
if (e.getWorld().isClient()) return CompletableFuture.completedFuture(false);
141+
142+
ServerWorld serverWorld = (ServerWorld) e.getWorld();
143+
MinecraftServer server = serverWorld.getServer();
144+
145+
final double speed = 0.5;
146+
final double tx = pos[0] + 0.5;
147+
final double ty = pos[1] + 1.0;
148+
final double tz = pos[2] + 0.5;
149+
final Vec3d target = new Vec3d(tx, ty, tz);
150+
151+
CompletableFuture<Boolean> result = new CompletableFuture<>();
152+
153+
// Start navigation on the server thread
154+
server.execute(() -> {
155+
try {
156+
e.getNavigation().startMovingTo(tx, ty, tz, speed);
157+
} catch (Throwable ex) {
158+
e.logMessage("Can't start navigation: " + ex.getMessage());
159+
result.complete(false);
160+
return;
161+
}
162+
163+
// Begin polling loop using Sleeper to wait ticks between checks
164+
// Obtain your Sleeper instance; adapt as needed (injected, singleton, etc.)
165+
Sleeper sleeper = getSleeperInstance(server); // <-- implement this to return your Sleeper
166+
final double acceptSq = acceptDistance * acceptDistance;
167+
final AtomicInteger ticksLeft = new AtomicInteger(Math.max(1, timeoutTicks));
168+
final int retryEvery = 5;
169+
final AtomicInteger ticksSinceRetry = new AtomicInteger(0);
170+
171+
// Polling function: checks conditions and either completes or waits one tick and re-checks
172+
Runnable[] pollRef = new Runnable[1];
173+
pollRef[0] = () -> {
174+
try {
175+
// If future already completed, stop
176+
if (result.isDone()) return;
177+
178+
if (!e.isAlive() || serverWorld.isClient()) {
179+
result.complete(false);
180+
return;
181+
}
182+
183+
double sqDist = e.getPos().squaredDistanceTo(target);
184+
boolean closeEnough = sqDist <= acceptSq;
185+
boolean navIdle;
186+
try {
187+
navIdle = e.getNavigation().isIdle();
188+
} catch (Throwable ex) {
189+
navIdle = true;
190+
}
191+
192+
if (closeEnough) {
193+
result.complete(true);
194+
return;
195+
}
196+
197+
if (!navIdle) {
198+
if (ticksLeft.decrementAndGet() <= 0) {
199+
e.logMessage("Can't reach target block, timeout");
200+
result.complete(false);
201+
return;
202+
}
203+
// still navigating: wait 1 tick then poll again
204+
sleeper.sleepTicksCF(1).whenComplete((v, err) -> {
205+
if (err != null) {
206+
result.completeExceptionally(err);
207+
} else {
208+
// schedule next poll on server thread to ensure world-safety
209+
server.execute(pollRef[0]);
210+
}
211+
});
212+
return;
213+
}
214+
215+
// navIdle && not close enough -> cancel and log
216+
e.logMessage("Can't reach target block, navigation idle and not close enough");
217+
result.complete(false);
218+
} catch (Throwable t) {
219+
result.completeExceptionally(t);
220+
}
221+
};
222+
223+
// schedule the first poll on the next tick so nav has time to begin
224+
sleeper.sleepTicksCF(1).whenComplete((v, err) -> {
225+
if (err != null) {
226+
result.completeExceptionally(err);
227+
} else {
228+
server.execute(pollRef[0]);
229+
}
230+
});
231+
});
232+
233+
return result;
234+
}
235+
236+
private static Sleeper sleeper;
237+
private Sleeper getSleeperInstance(MinecraftServer server) {
238+
if(sleeper == null)
239+
sleeper = new Sleeper(server);
240+
return sleeper;
241+
}
242+
243+
244+
115245

116246
@HostAccess.Export
117247
public Value exportBlockPos(String key, String displayName, int @Nullable [] defaultVal) throws Exception {
@@ -316,7 +446,7 @@ public boolean destroyBlock(int[] _targetPos) throws Exception {
316446
@HostAccess.Export
317447
public boolean plantSeed(int[] targetPos, String seedId) {
318448
var entity = get();
319-
if(entity == null) return false;
449+
if (entity == null) return false;
320450
var pos = new BlockPos(targetPos[0], targetPos[1], targetPos[2]);
321451
return Seeder.plantSeedById(entity, entity.getWorld(), pos, seedId);
322452
}

src/main/java/com/hanprogramer/androids/entities/android/util/AndroidPropertyList.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.hanprogramer.androids.entities.android.util;
22

3+
import com.hanprogramer.androids.entities.android.scripting.AndroidProperty;
34
import com.hanprogramer.androids.util.CodecPacketBridge;
45
import com.mojang.serialization.Codec;
56
import net.minecraft.entity.data.TrackedDataHandler;

src/main/java/com/hanprogramer/androids/entities/android/scripting/BlockArea.java renamed to src/main/java/com/hanprogramer/androids/entities/android/util/BlockArea.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.hanprogramer.androids.entities.android.scripting;
1+
package com.hanprogramer.androids.entities.android.util;
22

33
import com.mojang.serialization.Codec;
44
import com.mojang.serialization.codecs.RecordCodecBuilder;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.hanprogramer.androids.entities.android.util;
2+
3+
import net.minecraft.server.MinecraftServer;
4+
import org.graalvm.polyglot.Context;
5+
import org.graalvm.polyglot.Value;
6+
import org.graalvm.polyglot.proxy.ProxyExecutable;
7+
8+
import java.util.concurrent.CompletableFuture;
9+
10+
public final class JsInterop {
11+
public static <T> Value toPromise(Context jsCtx, MinecraftServer server, CompletableFuture<T> future) {
12+
Value PromiseCtor = jsCtx.eval("js", "Promise");
13+
return PromiseCtor.newInstance((ProxyExecutable) (Value... args) -> {
14+
Value resolve = args[0];
15+
Value reject = args[1];
16+
future.whenComplete((val, err) -> {
17+
// Ensure the handlers execute on the server thread for world safety
18+
server.execute(() -> {
19+
try {
20+
if (err != null) {
21+
reject.executeVoid(String.valueOf(err));
22+
} else {
23+
// Boolean, number, string, etc. are fine to pass directly
24+
resolve.executeVoid(val);
25+
}
26+
} catch (Throwable ignored) {
27+
// swallow to avoid crashing the tick
28+
}
29+
});
30+
});
31+
return null;
32+
});
33+
}
34+
}
35+

0 commit comments

Comments
 (0)