increase http api item stat parsing by a really really negligible amt

This commit is contained in:
Lucwousin
2019-10-15 09:40:52 +02:00
parent b711aa328d
commit a433b58fae
11 changed files with 264 additions and 65178 deletions

View File

@@ -9,6 +9,7 @@ dependencies {
compileOnly group: 'org.projectlombok', name: 'lombok', version: lombok compileOnly group: 'org.projectlombok', name: 'lombok', version: lombok
implementation group: 'com.google.code.gson', name: 'gson', version: gson implementation group: 'com.google.code.gson', name: 'gson', version: gson
implementation group: 'com.google.guava', name: 'guava', version: guava
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: okhttp3 implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: okhttp3
implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: rxjava implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: rxjava
implementation group: 'org.apache.commons', name: 'commons-csv', version: apacheCommonsCsv implementation group: 'org.apache.commons', name: 'commons-csv', version: apacheCommonsCsv

View File

@@ -26,6 +26,10 @@ package net.runelite.http.api;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import net.runelite.http.api.item.ItemEquipmentStats;
import net.runelite.http.api.item.ItemPrice;
import net.runelite.http.api.item.ItemStats;
import net.runelite.http.api.util.TypeAdapters;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.Interceptor; import okhttp3.Interceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@@ -56,7 +60,12 @@ public class RuneLiteAPI
public static final String RUNELITE_AUTH = "RUNELITE-AUTH"; public static final String RUNELITE_AUTH = "RUNELITE-AUTH";
public static final OkHttpClient CLIENT; public static final OkHttpClient CLIENT;
public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); public static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(ItemStats.class, TypeAdapters.ITEMSTATS)
.registerTypeAdapter(ItemEquipmentStats.class, TypeAdapters.EQUIPMENTSTATS)
.registerTypeAdapter(ItemPrice.class, TypeAdapters.ITEMPRICE)
.create();
private static final String BASE = "https://api.runelite.net"; private static final String BASE = "https://api.runelite.net";
private static final String WSBASE = "https://api.runelite.net/ws"; private static final String WSBASE = "https://api.runelite.net/ws";

View File

@@ -24,16 +24,15 @@
*/ */
package net.runelite.http.api.item; package net.runelite.http.api.item;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader;
import io.reactivex.Observable; import io.reactivex.Observable;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.inject.Inject; import javax.inject.Inject;
import net.runelite.http.api.RuneLiteAPI; import net.runelite.http.api.RuneLiteAPI;
@@ -225,41 +224,37 @@ public class ItemClient
}); });
} }
public Observable<Map<Integer, ItemStats>> getStats() public Observable<ImmutableMap<Integer, ItemStats>> getStats()
{ {
HttpUrl.Builder urlBuilder = RuneLiteAPI.getStaticBase().newBuilder() HttpUrl url = RuneLiteAPI.getStaticBase()
.newBuilder()
.addPathSegment("item") .addPathSegment("item")
// TODO: Change this to stats.min.json later after release is undeployed .addPathSegment("stats.ids.min.json")
.addPathSegment("stats.ids.min.json"); .build();
HttpUrl url = urlBuilder.build(); logger.debug("Built URI {}", url);
return Observable.fromCallable(() ->
logger.debug("Built URI: {}", url);
return Observable.defer(() ->
{ {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(url) .url(url)
.build(); .build();
try (Response response = client.newCall(request).execute()) try (JsonReader reader = new JsonReader(client.newCall(request).execute().body().charStream()))
{ {
if (!response.isSuccessful()) // This is the size the items are as I wrote this. the builder gets increased by 1 every time otherwise
ImmutableMap.Builder<Integer, ItemStats> builder = ImmutableMap.builderWithExpectedSize(7498);
reader.beginObject();
while (reader.hasNext())
{ {
logger.warn("Error looking up item stats: {}", response); builder.put(
return Observable.just(null); Integer.parseInt(reader.nextName()),
RuneLiteAPI.GSON.fromJson(reader, ItemStats.class)
);
} }
InputStream in = response.body().byteStream(); reader.endObject();
final Type typeToken = new TypeToken<Map<Integer, ItemStats>>() return builder.build();
{
}.getType();
return Observable.just(RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), typeToken));
}
catch (JsonParseException ex)
{
return Observable.error(ex);
} }
}); });
} }

View File

@@ -28,7 +28,7 @@ import lombok.Builder;
import lombok.Value; import lombok.Value;
@Value @Value
@Builder @Builder(builderClassName = "Builder")
public class ItemEquipmentStats public class ItemEquipmentStats
{ {
private int slot; private int slot;

View File

@@ -24,12 +24,15 @@
*/ */
package net.runelite.http.api.item; package net.runelite.http.api.item;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value; import lombok.Value;
@Value @Value
@AllArgsConstructor
@Builder(builderClassName = "Builder")
public class ItemStats public class ItemStats
{ {
private String name;
private boolean quest; private boolean quest;
private boolean equipable; private boolean equipable;
private double weight; private double weight;
@@ -51,9 +54,9 @@ public class ItemStats
{ {
final ItemEquipmentStats equipment = this.equipment != null final ItemEquipmentStats equipment = this.equipment != null
? this.equipment ? this.equipment
: new ItemEquipmentStats.ItemEquipmentStatsBuilder().build(); : new ItemEquipmentStats.Builder().build();
newEquipment = new ItemEquipmentStats.ItemEquipmentStatsBuilder() newEquipment = new ItemEquipmentStats.Builder()
.slot(equipment.getSlot()) .slot(equipment.getSlot())
.astab(equipment.getAstab() - other.equipment.getAstab()) .astab(equipment.getAstab() - other.equipment.getAstab())
.aslash(equipment.getAslash() - other.equipment.getAslash()) .aslash(equipment.getAslash() - other.equipment.getAslash())
@@ -77,7 +80,7 @@ public class ItemStats
newEquipment = equipment; newEquipment = equipment;
} }
return new ItemStats(name, quest, equipable, newWeight, newEquipment); return new ItemStats(quest, equipable, newWeight, newEquipment);
} }
} }

View File

@@ -0,0 +1,215 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/lucwousin>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.api.util;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.time.Instant;
import net.runelite.http.api.item.ItemEquipmentStats;
import net.runelite.http.api.item.ItemPrice;
import net.runelite.http.api.item.ItemStats;
/**
* A class to put GSON TypeAdapters. These make just as fast the first time you
* deserialize something as it would otherwise be after creating these itself.
* Kinda funny actually, cause the first time probably matters the most, especially
* for jsons that only get deserialized once.
*/
public class TypeAdapters
{
public static final TypeAdapter<ItemStats> ITEMSTATS = new TypeAdapter<ItemStats>()
{
@Deprecated
@Override
public void write(JsonWriter out, ItemStats value)
{
throw new UnsupportedOperationException("Not supported");
}
@Override
public ItemStats read(JsonReader in) throws IOException
{
in.beginObject();
boolean quest = false;
boolean equip = false;
double weight = 0;
ItemEquipmentStats stats = null;
while (in.peek() != JsonToken.END_OBJECT)
{
switch (in.nextName())
{
case "quest":
quest = in.nextBoolean();
break;
case "equipable":
equip = in.nextBoolean();
break;
case "weight":
weight = in.nextDouble();
break;
case "equipment":
stats = EQUIPMENTSTATS.read(in);
break;
}
}
in.endObject();
return new ItemStats(quest, equip, weight, stats);
}
};
public static final TypeAdapter<ItemEquipmentStats> EQUIPMENTSTATS = new TypeAdapter<ItemEquipmentStats>()
{
@Deprecated
@Override
public void write(JsonWriter out, ItemEquipmentStats value)
{
throw new UnsupportedOperationException("Not supported");
}
@Override
public ItemEquipmentStats read(JsonReader in) throws IOException
{
ItemEquipmentStats.Builder builder = ItemEquipmentStats.builder();
in.beginObject();
while (in.peek() != JsonToken.END_OBJECT)
{
String name = in.nextName();
int val = in.nextInt();
switch (name)
{
case "slot":
builder.slot(val);
break;
case "astab":
builder.astab(val);
break;
case "aslash":
builder.aslash(val);
break;
case "acrush":
builder.acrush(val);
break;
case "amagic":
builder.amagic(val);
break;
case "arange":
builder.arange(val);
break;
case "dstab":
builder.dstab(val);
break;
case "dslash":
builder.dslash(val);
break;
case "dcrush":
builder.dcrush(val);
break;
case "dmagic":
builder.dmagic(val);
break;
case "drange":
builder.drange(val);
break;
case "str":
builder.str(val);
break;
case "rstr":
builder.rstr(val);
break;
case "mdmg":
builder.mdmg(val);
break;
case "prayer":
builder.prayer(val);
break;
case "aspeed":
builder.aspeed(val);
break;
}
}
in.endObject();
return builder.build();
}
};
public static final TypeAdapter<ItemPrice> ITEMPRICE = new TypeAdapter<ItemPrice>()
{
@Override
public void write(JsonWriter out, ItemPrice value)
{
throw new UnsupportedOperationException("Not supported");
}
@Override
public ItemPrice read(JsonReader in) throws IOException
{
/*
* The ItemPrice json hosted by runelite is 'perfect'
* by that I mean every field always exists, even with value 0.
* This is why we can skip names and known-0 values
*/
ItemPrice ret = new ItemPrice();
in.beginObject();
// ID
in.skipValue();
ret.setId(in.nextInt());
// Name
in.skipValue();
ret.setName(in.nextString());
// Price
in.skipValue();
ret.setPrice(in.nextInt());
// Time
in.skipValue();
in.beginObject();
// Secs
in.skipValue();
ret.setTime(Instant.ofEpochSecond(in.nextLong()));
// Nanos
in.skipValue();
in.skipValue();
in.endObject();
in.endObject();
return ret;
}
};
}

View File

@@ -28,14 +28,9 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.awt.Color; import java.awt.Color;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -137,12 +132,11 @@ public class ItemManager
private final Client client; private final Client client;
private final ClientThread clientThread; private final ClientThread clientThread;
private final ItemClient itemClient; private final ItemClient itemClient;
private final ImmutableMap<Integer, ItemStats> itemStatMap;
private final LoadingCache<ImageKey, AsyncBufferedImage> itemImages; private final LoadingCache<ImageKey, AsyncBufferedImage> itemImages;
private final LoadingCache<Integer, ItemDefinition> itemDefinitions; private final LoadingCache<Integer, ItemDefinition> itemDefinitions;
private final LoadingCache<OutlineKey, BufferedImage> itemOutlines; private final LoadingCache<OutlineKey, BufferedImage> itemOutlines;
private Map<Integer, ItemPrice> itemPrices = Collections.emptyMap(); private Map<Integer, ItemPrice> itemPrices = Collections.emptyMap();
private Map<Integer, ItemStats> itemStats = Collections.emptyMap(); private ImmutableMap<Integer, ItemStats> itemStats = ImmutableMap.of();
@Inject @Inject
public ItemManager( public ItemManager(
@@ -196,16 +190,6 @@ public class ItemManager
} }
}); });
final Gson gson = new Gson();
final Type typeToken = new TypeToken<Map<Integer, ItemStats>>()
{
}.getType();
final InputStream statsFile = getClass().getResourceAsStream("/item_stats.json");
final Map<Integer, ItemStats> stats = gson.fromJson(new InputStreamReader(statsFile), typeToken);
itemStatMap = ImmutableMap.copyOf(stats);
eventbus.subscribe(GameStateChanged.class, this, this::onGameStateChanged); eventbus.subscribe(GameStateChanged.class, this, this::onGameStateChanged);
eventbus.subscribe(PostItemDefinition.class, this, this::onPostItemDefinition); eventbus.subscribe(PostItemDefinition.class, this, this::onPostItemDefinition);
} }
@@ -238,16 +222,9 @@ public class ItemManager
itemClient.getStats() itemClient.getStats()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe( .subscribe(
(stats) -> m -> itemStats = m,
{ e -> log.warn("Error fetching stats", e),
if (stats != null) () -> log.debug("Loaded {} stats", itemStats.size())
{
itemStats = ImmutableMap.copyOf(stats);
}
log.debug("Loaded {} stats", itemStats.size());
},
(e) -> log.warn("error loading stats!", e)
); );
} }
@@ -376,12 +353,12 @@ public class ItemManager
{ {
ItemDefinition itemDefinition = getItemDefinition(itemId); ItemDefinition itemDefinition = getItemDefinition(itemId);
if (itemDefinition.getName() == null || !allowNote && itemDefinition.getNote() != -1) if (!allowNote && itemDefinition.getNote() != -1)
{ {
return null; return null;
} }
return itemStatMap.get(canonicalize(itemId)); return itemStats.get(canonicalize(itemId));
} }
/** /**

View File

@@ -1,27 +0,0 @@
package net.runelite.client.game;
import lombok.Value;
@Value
public class ItemStat
{
private int slot;
private int astab;
private int aslash;
private int acrush;
private int amagic;
private int arange;
private int dstab;
private int dslash;
private int dcrush;
private int dmagic;
private int drange;
private int str;
private int rstr;
private int mdmg;
private int prayer;
private int aspeed;
}

View File

@@ -48,7 +48,7 @@ import net.runelite.http.api.item.ItemStats;
public class ItemStatOverlay extends Overlay public class ItemStatOverlay extends Overlay
{ {
// Unarmed attack speed is 6 // Unarmed attack speed is 6
private static final ItemStats UNARMED = new ItemStats("", false, true, 0, private static final ItemStats UNARMED = new ItemStats(false, true, 0,
ItemEquipmentStats.builder() ItemEquipmentStats.builder()
.aspeed(6) .aspeed(6)
.build()); .build());

View File

@@ -551,6 +551,7 @@ public class PlayerScouter extends Plugin
} }
ItemStats item = itemManager.getItemStats(gear, false); ItemStats item = itemManager.getItemStats(gear, false);
String name = itemManager.getItemDefinition(gear).getName();
if (item == null) if (item == null)
{ {
@@ -559,7 +560,7 @@ public class PlayerScouter extends Plugin
} }
fieldList.add(FieldEmbed.builder() fieldList.add(FieldEmbed.builder()
.name(item.getName()) .name(name)
.value("Value: " + StackFormatter.quantityToRSDecimalStack(value)) .value("Value: " + StackFormatter.quantityToRSDecimalStack(value))
.inline(true) .inline(true)
.build()); .build());

File diff suppressed because it is too large Load Diff