Skip to content

Commit

Permalink
Merge pull request #751 from Emirlol/more-efficient-mayor-check
Browse files Browse the repository at this point in the history
Schedule mayor cache ticking to the next year rather than every 20 mins
  • Loading branch information
kevinthegreat1 authored Jul 8, 2024
2 parents c1b5447 + b6fec0a commit 438ff23
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 33 deletions.
21 changes: 8 additions & 13 deletions src/main/java/de/hysky/skyblocker/utils/Http.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class Http {
.followRedirects(Redirect.NORMAL)
.build();

private static ApiResponse sendCacheableGetRequest(String url, @Nullable String token) throws IOException, InterruptedException {
public static ApiResponse sendCacheableGetRequest(String url, @Nullable String token) throws IOException, InterruptedException {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.GET()
.header("Accept", "application/json")
Expand Down Expand Up @@ -66,9 +66,8 @@ public static InputStream downloadContent(String url) throws IOException, Interr
.build();

HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream());
InputStream decodedInputStream = getDecodedInputStream(response);

return decodedInputStream;
return getDecodedInputStream(response);
}

public static String sendGetRequest(String url) throws IOException, InterruptedException {
Expand Down Expand Up @@ -125,16 +124,12 @@ private static InputStream getDecodedInputStream(HttpResponse<InputStream> respo
String encoding = getContentEncoding(response.headers());

try {
switch (encoding) {
case "":
return response.body();
case "gzip":
return new GZIPInputStream(response.body());
case "deflate":
return new InflaterInputStream(response.body());
default:
throw new UnsupportedOperationException("The server sent content in an unexpected encoding: " + encoding);
}
return switch (encoding) {
case "" -> response.body();
case "gzip" -> new GZIPInputStream(response.body());
case "deflate" -> new InflaterInputStream(response.body());
default -> throw new UnsupportedOperationException("The server sent content in an unexpected encoding: " + encoding);
};
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ private SkyblockTime() {
public static void init() {
updateTime();
//ScheduleCyclic already runs the task upon scheduling, so there's no need to call updateTime() here
Scheduler.INSTANCE.schedule(() -> Scheduler.INSTANCE.scheduleCyclic(SkyblockTime::updateTime, 1200 * 24), (int) (1200000 - (getSkyblockMillis() % 1200000)) / 50);
Scheduler.INSTANCE.schedule(() -> Scheduler.INSTANCE.scheduleCyclic(SkyblockTime::updateTime, 1200 * 20), (int) (1200000 - (getSkyblockMillis() % 1200000)) / 50);
}

private static long getSkyblockMillis() {
public static long getSkyblockMillis() {
return System.currentTimeMillis() - SKYBLOCK_EPOCH;
}

Expand Down
61 changes: 43 additions & 18 deletions src/main/java/de/hysky/skyblocker/utils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mojang.util.UndashedUuid;

import de.hysky.skyblocker.events.SkyblockEvents;
import de.hysky.skyblocker.mixins.accessors.MessageHandlerAccessor;
import de.hysky.skyblocker.skyblock.item.MuseumItemCache;
Expand All @@ -22,11 +21,11 @@
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.apache.http.client.HttpResponseException;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -77,7 +76,8 @@ public class Utils {
private static boolean canSendLocRaw = false;
//This is required to prevent the location change event from being fired twice.
private static boolean locationChanged = true;

private static boolean mayorTickScheduled = false;
private static int mayorTickRetryAttempts = 0;
private static String mayor = "";

/**
Expand Down Expand Up @@ -194,11 +194,16 @@ public static String getMayor() {
}

public static void init() {
SkyblockEvents.JOIN.register(() -> tickMayorCache(false));
SkyblockEvents.JOIN.register(() -> {
if (!mayorTickScheduled) {
tickMayorCache();
scheduleMayorTick();
mayorTickScheduled = true;
}
});
ClientPlayConnectionEvents.JOIN.register(Utils::onClientWorldJoin);
ClientReceiveMessageEvents.ALLOW_GAME.register(Utils::onChatMessage);
ClientReceiveMessageEvents.GAME_CANCELED.register(Utils::onChatMessage); // Somehow this works even though onChatMessage returns a boolean
Scheduler.INSTANCE.scheduleCyclic(() -> tickMayorCache(true), 24_000, true); // Update every 20 minutes
}

/**
Expand Down Expand Up @@ -361,8 +366,8 @@ private static void updateScoreboard(MinecraftClient client) {
// TODO: Combine with `ChocolateFactorySolver.formatTime` and move into `SkyblockTime`.
public static Text getDurationText(int timeInSeconds) {
int seconds = timeInSeconds % 60;
int minutes = (timeInSeconds/60) % 60;
int hours = (timeInSeconds/3600);
int minutes = (timeInSeconds / 60) % 60;
int hours = (timeInSeconds / 3600);

MutableText time = Text.empty();
if (hours > 0) {
Expand Down Expand Up @@ -486,27 +491,47 @@ private static void resetLocRawInfo() {
location = Location.UNKNOWN;
}

private static void tickMayorCache(boolean refresh) {
if (!mayor.isEmpty() && !refresh) return;
private static void scheduleMayorTick() {
long currentYearMillis = SkyblockTime.getSkyblockMillis() % 446400000L; //446400000ms is 1 year, 105600000ms is the amount of time from early spring 1st to late spring 27th
// If current time is past late spring 27th, the next mayor change is at next year's spring 27th, otherwise it's at this year's spring 27th
long millisUntilNextMayorChange = currentYearMillis > 105600000L ? 446400000L - currentYearMillis + 105600000L : 105600000L - currentYearMillis;
Scheduler.INSTANCE.schedule(Utils::tickMayorCache, (int) (millisUntilNextMayorChange / 50) + 5 * 60 * 20); // 5 extra minutes to allow the cache to expire. This is a simpler than checking age and subtracting from max age and rescheduling again.
}

private static void tickMayorCache() {
CompletableFuture.supplyAsync(() -> {
try {
JsonObject json = JsonParser.parseString(Http.sendGetRequest("https://api.hypixel.net/v2/resources/skyblock/election")).getAsJsonObject();
if (json.get("success").getAsBoolean()) return json.get("mayor").getAsJsonObject().get("name").getAsString();
throw new IOException(json.get("cause").getAsString());
Http.ApiResponse response = Http.sendCacheableGetRequest("https://api.hypixel.net/v2/resources/skyblock/election", null); //Authentication is not required for this endpoint
if (!response.ok()) throw new HttpResponseException(response.statusCode(), response.content());
JsonObject json = JsonParser.parseString(response.content()).getAsJsonObject();
if (!json.get("success").getAsBoolean()) throw new RuntimeException("Request failed!"); //Can't find a more appropriate exception to throw here.
return json.get("mayor").getAsJsonObject().get("name").getAsString();
} catch (Exception e) {
LOGGER.error("[Skyblocker] Failed to get mayor status!", e);
throw new RuntimeException(e); //Wrap the exception to be handled by the exceptionally block
}
}).exceptionally(throwable -> {
LOGGER.error("[Skyblocker] Failed to get mayor status!", throwable.getCause());
if (mayorTickRetryAttempts < 5) {
int minutes = 5 * 1 << mayorTickRetryAttempts; //5, 10, 20, 40, 80 minutes
mayorTickRetryAttempts++;
LOGGER.warn("[Skyblocker] Retrying in {} minutes.", minutes);
Scheduler.INSTANCE.schedule(Utils::tickMayorCache, minutes * 60 * 20);
} else {
LOGGER.warn("[Skyblocker] Failed to get mayor status after 5 retries! Stopping further retries until next reboot.");
}
return ""; //Have to return a value for the thenAccept block.
}).thenAccept(result -> {
if (!result.isEmpty()) {
mayor = result;
LOGGER.info("[Skyblocker] Mayor set to {}.", mayor);
scheduleMayorTick(); //Ends up as a cyclic task with finer control over scheduled time
}
return "";
}).thenAccept(s -> {
if (!s.isEmpty()) mayor = s;
});

}

/**
* Used to avoid triggering things like chat rules or chat listeners infinitely, do not use otherwise.
*
* <p>
* Bypasses MessageHandler#onGameMessage
*/
public static void sendMessageToBypassEvents(Text message) {
Expand Down

0 comments on commit 438ff23

Please sign in to comment.