Decrease memory footprint of the registry cache (#521)

This commit is contained in:
TheMode 2021-11-07 13:40:54 +01:00 committed by GitHub
parent 5e10876c2a
commit b298334d24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 81 additions and 62 deletions

View File

@ -110,7 +110,7 @@ dependencies {
api 'it.unimi.dsi:fastutil:8.5.6'
// https://mvnrepository.com/artifact/com.google.code.gson/gson
api 'com.google.code.gson:gson:2.8.8'
api 'com.google.code.gson:gson:2.8.9'
// Noise library for terrain generation
// https://jitpack.io/#Articdive/Jnoise

View File

@ -1,6 +1,5 @@
package net.minestom.server.gamedata.tags;
import com.google.gson.JsonObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.Nullable;
@ -40,12 +39,11 @@ public final class TagManager {
return Collections.unmodifiableMap(tagMap);
}
private Set<NamespaceID> getValues(JsonObject main, String value) {
JsonObject tagObject = main.getAsJsonObject(value);
final var tagValues = tagObject.getAsJsonArray("values");
private Set<NamespaceID> getValues(Map<String, Map<String, Object>> main, String value) {
Map<String, Object> tagObject = main.get(value);
final List<String> tagValues = (List<String>) tagObject.get("values");
Set<NamespaceID> result = new HashSet<>(tagValues.size());
tagValues.forEach(jsonElement -> {
final String tagString = jsonElement.getAsString();
tagValues.forEach(tagString -> {
if (tagString.startsWith("#")) {
result.addAll(getValues(main, tagString.substring(1)));
} else {

View File

@ -2,7 +2,6 @@ package net.minestom.server.instance.block;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.gson.JsonObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.tag.Tag;
import net.minestom.server.utils.ObjectArray;
@ -20,13 +19,13 @@ final class BlockImpl implements Block {
private static final ObjectArray<Block> BLOCK_STATE_MAP = new ObjectArray<>();
private static final Registry.Container<Block> CONTAINER = new Registry.Container<>(Registry.Resource.BLOCKS,
(container, namespace, object) -> {
final JsonObject stateObject = object.remove("states").getAsJsonObject();
final var stateObject = (Map<String, Object>) object.remove("states");
// Loop each state
var propertyEntry = new HashMap<Map<String, String>, Block>();
var unmodifiableEntries = Collections.unmodifiableMap(propertyEntry);
for (var stateEntry : stateObject.entrySet()) {
final String query = stateEntry.getKey();
JsonObject stateOverride = stateEntry.getValue().getAsJsonObject();
final var stateOverride = (Map<String, Object>) stateEntry.getValue();
final var propertyMap = BlockUtils.parseProperties(query);
final Block block = new BlockImpl(Registry.block(namespace, object, stateOverride),
unmodifiableEntries, propertyMap, null, null);
@ -34,7 +33,7 @@ final class BlockImpl implements Block {
propertyEntry.put(propertyMap, block);
}
// Register default state
final int defaultState = object.get("defaultStateId").getAsInt();
final int defaultState = ((Number) object.get("defaultStateId")).intValue();
container.register(getState(defaultState));
});
private static final Cache<NBTCompound, NBTCompound> NBT_CACHE = Caffeine.newBuilder()

View File

@ -9,7 +9,7 @@ import java.util.Collection;
final class ParticleImpl implements Particle {
private static final Registry.Container<Particle> CONTAINER = new Registry.Container<>(Registry.Resource.PARTICLES,
(loader, namespace, object) -> {
final int id = object.get("id").getAsInt();
final int id = ((Number) object.get("id")).intValue();
loader.register(new ParticleImpl(NamespaceID.from(namespace), id));
});

View File

@ -9,7 +9,7 @@ import java.util.Collection;
final class PotionTypeImpl implements PotionType {
private static final Registry.Container<PotionType> CONTAINER = new Registry.Container<>(Registry.Resource.POTION_TYPES,
(loader, namespace, object) -> {
final int id = object.get("id").getAsInt();
final int id = ((Number) object.get("id")).intValue();
loader.register(new PotionTypeImpl(NamespaceID.from(namespace), id));
});

View File

@ -1,9 +1,10 @@
package net.minestom.server.registry;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.ToNumberPolicy;
import com.google.gson.stream.JsonReader;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.EntitySpawnType;
import net.minestom.server.entity.EquipmentSlot;
import net.minestom.server.instance.block.Block;
@ -16,6 +17,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.function.Supplier;
@ -25,48 +27,45 @@ import java.util.function.Supplier;
* Use at your own risk.
*/
public final class Registry {
private static final Gson GSON = new Gson();
@ApiStatus.Internal
public static BlockEntry block(String namespace, @NotNull JsonObject jsonObject, JsonObject override) {
public static BlockEntry block(String namespace, @NotNull Map<String, Object> jsonObject, Map<String, Object> override) {
return new BlockEntry(namespace, jsonObject, override);
}
@ApiStatus.Internal
public static MaterialEntry material(String namespace, @NotNull JsonObject jsonObject, JsonObject override) {
public static MaterialEntry material(String namespace, @NotNull Map<String, Object> jsonObject, Map<String, Object> override) {
return new MaterialEntry(namespace, jsonObject, override);
}
@ApiStatus.Internal
public static EntityEntry entity(String namespace, @NotNull JsonObject jsonObject, JsonObject override) {
public static EntityEntry entity(String namespace, @NotNull Map<String, Object> jsonObject, Map<String, Object> override) {
return new EntityEntry(namespace, jsonObject, override);
}
@ApiStatus.Internal
public static EnchantmentEntry enchantment(String namespace, @NotNull JsonObject jsonObject, JsonObject override) {
public static EnchantmentEntry enchantment(String namespace, @NotNull Map<String, Object> jsonObject, Map<String, Object> override) {
return new EnchantmentEntry(namespace, jsonObject, override);
}
@ApiStatus.Internal
public static PotionEffectEntry potionEffect(String namespace, @NotNull JsonObject jsonObject, JsonObject override) {
public static PotionEffectEntry potionEffect(String namespace, @NotNull Map<String, Object> jsonObject, Map<String, Object> override) {
return new PotionEffectEntry(namespace, jsonObject, override);
}
@ApiStatus.Internal
public static JsonObject load(Resource resource) {
final var resourceStream = Registry.class.getClassLoader().getResourceAsStream(resource.name);
Check.notNull(resourceStream, "Resource {0} does not exist!", resource);
final var reader = new JsonReader(new InputStreamReader(resourceStream));
try {
return GSON.fromJson(reader, JsonObject.class);
} finally {
try {
resourceStream.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
public static Map<String, Map<String, Object>> load(Resource resource) {
Map<String, Map<String, Object>> map = new HashMap<>();
try (InputStream resourceStream = Registry.class.getClassLoader().getResourceAsStream(resource.name)) {
Check.notNull(resourceStream, "Resource {0} does not exist!", resource);
try (JsonReader reader = new JsonReader(new InputStreamReader(resourceStream))) {
reader.beginObject();
while (reader.hasNext()) map.put(reader.nextName(), (Map<String, Object>) readObject(reader));
reader.endObject();
}
} catch (IOException e) {
MinecraftServer.getExceptionManager().handleException(e);
}
return map;
}
public static class Container<T extends ProtocolObject> {
@ -83,10 +82,9 @@ public final class Registry {
@ApiStatus.Internal
public Container(Resource resource, Loader<T> loader) {
final JsonObject objects = Registry.load(resource);
for (var entry : objects.entrySet()) {
for (var entry : Registry.load(resource).entrySet()) {
final String namespace = entry.getKey();
final JsonObject object = entry.getValue().getAsJsonObject();
final Map<String, Object> object = entry.getValue();
loader.accept(this, namespace, object);
}
this.initialized = true;
@ -116,7 +114,7 @@ public final class Registry {
}
public interface Loader<T extends ProtocolObject> {
void accept(Container<T> container, String namespace, JsonObject object);
void accept(Container<T> container, String namespace, Map<String, Object> object);
}
}
@ -161,7 +159,7 @@ public final class Registry {
private final String blockEntity;
private final Supplier<Material> materialSupplier;
private BlockEntry(String namespace, JsonObject main, JsonObject override) {
private BlockEntry(String namespace, Map<String, Object> main, Map<String, Object> override) {
super(main, override);
this.namespace = NamespaceID.from(namespace);
this.id = getInt("id");
@ -253,7 +251,7 @@ public final class Registry {
private final Supplier<Block> blockSupplier;
private final EquipmentSlot equipmentSlot;
private MaterialEntry(String namespace, JsonObject main, JsonObject override) {
private MaterialEntry(String namespace, Map<String, Object> main, Map<String, Object> override) {
super(main, override);
this.namespace = NamespaceID.from(namespace);
this.id = getInt("id");
@ -267,9 +265,9 @@ public final class Registry {
}
{
final var armorProperties = element("armorProperties");
final Map<String, Object> armorProperties = element("armorProperties");
if (armorProperties != null) {
final String slot = armorProperties.getAsJsonObject().get("slot").getAsString();
final String slot = (String) armorProperties.get("slot");
switch (slot) {
case "feet" -> this.equipmentSlot = EquipmentSlot.BOOTS;
case "legs" -> this.equipmentSlot = EquipmentSlot.LEGGINGS;
@ -328,7 +326,7 @@ public final class Registry {
private final double height;
private final EntitySpawnType spawnType;
private EntityEntry(String namespace, JsonObject main, JsonObject override) {
private EntityEntry(String namespace, Map<String, Object> main, Map<String, Object> override) {
super(main, override);
this.namespace = NamespaceID.from(namespace);
this.id = getInt("id");
@ -373,7 +371,7 @@ public final class Registry {
private final boolean isTradeable;
private final boolean isTreasureOnly;
private EnchantmentEntry(String namespace, JsonObject main, JsonObject override) {
private EnchantmentEntry(String namespace, Map<String, Object> main, Map<String, Object> override) {
super(main, override);
this.namespace = NamespaceID.from(namespace);
this.id = getInt("id");
@ -425,7 +423,7 @@ public final class Registry {
private final int color;
private final boolean isInstantaneous;
private PotionEffectEntry(String namespace, JsonObject main, JsonObject override) {
private PotionEffectEntry(String namespace, Map<String, Object> main, Map<String, Object> override) {
super(main, override);
this.namespace = NamespaceID.from(namespace);
this.id = getInt("id");
@ -456,54 +454,78 @@ public final class Registry {
}
public static class Entry {
private final JsonObject main, override;
private final Map<String, Object> main, override;
private Entry(JsonObject main, JsonObject override) {
private Entry(Map<String, Object> main, Map<String, Object> override) {
this.main = main;
this.override = override;
}
public String getString(String name, String defaultValue) {
var element = element(name);
return element != null ? element.getAsString() : defaultValue;
return element != null ? (String) element : defaultValue;
}
public String getString(String name) {
return element(name).getAsString();
return element(name);
}
public double getDouble(String name, double defaultValue) {
var element = element(name);
return element != null ? element.getAsDouble() : defaultValue;
return element != null ? ((Number) element).doubleValue() : defaultValue;
}
public double getDouble(String name) {
return element(name).getAsDouble();
return ((Number) element(name)).doubleValue();
}
public int getInt(String name, int defaultValue) {
var element = element(name);
return element != null ? element.getAsInt() : defaultValue;
return element != null ? ((Number) element).intValue() : defaultValue;
}
public int getInt(String name) {
return element(name).getAsInt();
return ((Number) element(name)).intValue();
}
public boolean getBoolean(String name, boolean defaultValue) {
var element = element(name);
return element != null ? element.getAsBoolean() : defaultValue;
return element != null ? (boolean) element : defaultValue;
}
public boolean getBoolean(String name) {
return element(name).getAsBoolean();
return element(name);
}
protected JsonElement element(String name) {
if (override != null && override.has(name)) {
return override.get(name);
protected <T> T element(String name) {
Object result;
if (override != null && (result = override.get(name)) != null) {
return (T) result;
}
return main.get(name);
return (T) main.get(name);
}
}
private static Object readObject(JsonReader reader) throws IOException {
return switch (reader.peek()) {
case BEGIN_ARRAY -> {
ObjectArrayList<Object> list = new ObjectArrayList<>();
reader.beginArray();
while (reader.hasNext()) list.add(readObject(reader));
reader.endArray();
yield new ObjectArrayList<>(list);
}
case BEGIN_OBJECT -> {
Object2ObjectArrayMap<String, Object> map = new Object2ObjectArrayMap<>();
reader.beginObject();
while (reader.hasNext()) map.put(reader.nextName().intern(), readObject(reader));
reader.endObject();
yield new Object2ObjectArrayMap<>(map);
}
case STRING -> reader.nextString().intern();
case NUMBER -> ToNumberPolicy.LONG_OR_DOUBLE.readNumber(reader);
case BOOLEAN -> reader.nextBoolean();
default -> throw new IllegalStateException("Invalid peek: " + reader.peek());
};
}
}

View File

@ -9,7 +9,7 @@ import java.util.Collection;
final class SoundEventImpl implements SoundEvent {
private static final Registry.Container<SoundEvent> CONTAINER = new Registry.Container<>(Registry.Resource.SOUNDS,
(container, namespace, object) -> {
final int id = object.get("id").getAsInt();
final int id = ((Number) object.get("id")).intValue();
container.register(new SoundEventImpl(NamespaceID.from(namespace), id));
});

View File

@ -9,7 +9,7 @@ import java.util.Collection;
final class StatisticTypeImpl implements StatisticType {
private static final Registry.Container<StatisticType> CONTAINER = new Registry.Container<>(Registry.Resource.STATISTICS,
(container, namespace, object) -> {
final int id = object.get("id").getAsInt();
final int id = ((Number) object.get("id")).intValue();
container.register(new StatisticTypeImpl(NamespaceID.from(namespace), id));
});