Files
hytale-server/generated/com/hypixel/hytale/assetstore/AssetStore.java
2026-01-25 21:28:40 +00:00

1762 lines
72 KiB
Java

package com.hypixel.hytale.assetstore;
import com.hypixel.hytale.assetstore.codec.AssetCodec;
import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec;
import com.hypixel.hytale.assetstore.event.GenerateAssetsEvent;
import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent;
import com.hypixel.hytale.assetstore.event.RemovedAssetsEvent;
import com.hypixel.hytale.assetstore.map.JsonAssetWithMap;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.ExtraInfo;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.codec.builder.BuilderField;
import com.hypixel.hytale.codec.exception.CodecException;
import com.hypixel.hytale.codec.exception.CodecValidationException;
import com.hypixel.hytale.codec.util.RawJsonReader;
import com.hypixel.hytale.codec.validation.ValidationResults;
import com.hypixel.hytale.codec.validation.Validator;
import com.hypixel.hytale.codec.validation.validator.ArrayValidator;
import com.hypixel.hytale.codec.validation.validator.MapKeyValidator;
import com.hypixel.hytale.codec.validation.validator.MapValueValidator;
import com.hypixel.hytale.common.util.FormatUtil;
import com.hypixel.hytale.common.util.StringUtil;
import com.hypixel.hytale.event.IEventBus;
import com.hypixel.hytale.event.IEventDispatcher;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.logger.backend.HytaleLoggerBackend;
import com.hypixel.hytale.logger.sentry.SkipSentryException;
import com.hypixel.hytale.logger.util.GithubMessageUtil;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.bson.BsonValue;
public abstract class AssetStore<K, T extends JsonAssetWithMap<K, M>, M extends AssetMap<K, T>> {
public static boolean DISABLE_ASSET_COMPARE = true;
@Nonnull
protected final HytaleLogger logger;
@Nonnull
protected final Class<K> kClass;
@Nonnull
protected final Class<T> tClass;
protected final String path;
@Nonnull
protected final String extension;
protected final AssetCodec<K, T> codec;
protected final Function<T, K> keyFunction;
@Nonnull
protected final Set<Class<? extends JsonAsset<?>>> loadsAfter;
@Nonnull
protected final Set<Class<? extends JsonAsset<?>>> unmodifiableLoadsAfter;
@Nonnull
protected final Set<Class<? extends JsonAsset<?>>> loadsBefore;
protected final M assetMap;
protected final Function<K, T> replaceOnRemove;
@Nonnull
protected final Predicate<T> isUnknown;
protected final boolean unmodifiable;
protected final List<T> preAddedAssets;
protected final Class<? extends JsonAsset<?>> idProvider;
protected final Map<Class<? extends JsonAssetWithMap<?, ?>>, Map<K, Set<Object>>> childAssetsMap = new ConcurrentHashMap<>();
@Nonnull
protected Set<Class<? extends JsonAssetWithMap>> loadedContainedAssetsFor = new HashSet<>();
public static boolean DISABLE_DYNAMIC_DEPENDENCIES = false;
public AssetStore(@Nonnull AssetStore.Builder<K, T, M, ?> builder) {
this.kClass = builder.kClass;
this.tClass = builder.tClass;
this.logger = HytaleLogger.get("AssetStore|" + this.tClass.getSimpleName());
this.path = builder.path;
this.extension = builder.extension;
this.codec = builder.codec;
this.keyFunction = builder.keyFunction;
this.isUnknown = builder.isUnknown == null ? v -> false : builder.isUnknown;
this.loadsAfter = builder.loadsAfter;
this.unmodifiableLoadsAfter = Collections.unmodifiableSet(builder.loadsAfter);
this.loadsBefore = Collections.unmodifiableSet(builder.loadsBefore);
this.assetMap = builder.assetMap;
this.replaceOnRemove = builder.replaceOnRemove;
this.unmodifiable = builder.unmodifiable;
this.preAddedAssets = builder.preAddedAssets;
this.idProvider = builder.idProvider;
if (builder.replaceOnRemove == null && this.assetMap.requireReplaceOnRemove()) {
throw new IllegalArgumentException(
"AssetStore for "
+ this.tClass.getSimpleName()
+ " using an AssetMap of "
+ this.assetMap.getClass().getSimpleName()
+ " must use #setReplaceOnRemove"
);
}
}
protected abstract IEventBus getEventBus();
public abstract void addFileMonitor(@Nonnull String var1, Path var2);
public abstract void removeFileMonitor(Path var1);
protected abstract void handleRemoveOrUpdate(Set<K> var1, Map<K, T> var2, @Nonnull AssetUpdateQuery var3);
@Nonnull
public Class<K> getKeyClass() {
return this.kClass;
}
@Nonnull
public Class<T> getAssetClass() {
return this.tClass;
}
public String getPath() {
return this.path;
}
@Nonnull
public String getExtension() {
return this.extension;
}
public AssetCodec<K, T> getCodec() {
return this.codec;
}
public Function<T, K> getKeyFunction() {
return this.keyFunction;
}
@Nonnull
public Set<Class<? extends JsonAsset<?>>> getLoadsAfter() {
return this.unmodifiableLoadsAfter;
}
public M getAssetMap() {
return this.assetMap;
}
public Function<K, T> getReplaceOnRemove() {
return this.replaceOnRemove;
}
public boolean isUnmodifiable() {
return this.unmodifiable;
}
public List<T> getPreAddedAssets() {
return this.preAddedAssets;
}
public <X extends JsonAssetWithMap> boolean hasLoadedContainedAssetsFor(Class<X> x) {
return this.loadedContainedAssetsFor.contains(x);
}
public Class<? extends JsonAsset<?>> getIdProvider() {
return this.idProvider;
}
@Nonnull
public HytaleLogger getLogger() {
return this.logger;
}
public void simplifyLoadBeforeDependencies() {
for (Class<? extends JsonAsset<?>> aClass : this.loadsBefore) {
AssetRegistry.getAssetStore((Class<T>)aClass).loadsAfter.add(this.tClass);
}
}
@Deprecated
public <D extends JsonAsset<?>> void injectLoadsAfter(Class<D> aClass) {
if (DISABLE_DYNAMIC_DEPENDENCIES) {
throw new IllegalArgumentException("Asset stores have already loaded! Injecting a dependency is now pointless.");
} else {
this.loadsAfter.add(aClass);
}
}
@Nullable
public K decodeFilePathKey(@Nonnull Path path) {
String fileName = path.getFileName().toString();
return this.decodeStringKey(fileName.substring(0, fileName.length() - this.extension.length()));
}
@Nullable
public K decodeStringKey(String key) {
return (K)(this.codec.getKeyCodec().getChildCodec() == Codec.STRING ? key : this.codec.getKeyCodec().getChildCodec().decode(new BsonString(key)));
}
@Nullable
public K transformKey(@Nullable Object o) {
if (o == null) {
return null;
} else {
return (K)(o.getClass().equals(this.kClass) ? o : this.decodeStringKey(o.toString()));
}
}
public void validate(@Nullable K key, @Nonnull ValidationResults results, ExtraInfo extraInfo) {
if (key != null) {
if (this.assetMap.getAsset(key) == null) {
if (extraInfo instanceof AssetExtraInfo) {
for (AssetExtraInfo.Data data = ((AssetExtraInfo)extraInfo).getData(); data != null; data = data.getContainerData()) {
if (data.containsAsset(this.tClass, key)) {
return;
}
}
}
results.fail("Asset '" + key + "' of type " + this.tClass.getName() + " doesn't exist!");
}
}
}
public void validateCodecDefaults() {
ExtraInfo extraInfo = new ExtraInfo(Integer.MAX_VALUE, AssetValidationResults::new);
this.codec.validateDefaults(extraInfo, new HashSet<>());
extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.logger, "Default Asset Validation Failed!\n");
}
public void logDependencies() {
ExtraInfo extraInfo = new ExtraInfo(Integer.MAX_VALUE, AssetValidationResults::new);
HashSet<Codec<?>> tested = new HashSet<>();
this.codec.validateDefaults(extraInfo, tested);
Set<Class<? extends JsonAsset<?>>> assetClasses = new HashSet<>();
Set<Class<? extends JsonAsset<?>>> maybeLateAssetClasses = new HashSet<>();
for (Codec<?> other : tested) {
if (other instanceof BuilderCodec) {
for (BuilderCodec<?> builderCodec = (BuilderCodec<?>)other; builderCodec != null; builderCodec = builderCodec.getParent()) {
for (List<? extends BuilderField<?, ?>> value : builderCodec.getEntries().values()) {
for (BuilderField<?, ?> field : value) {
if (field.supportsVersion(extraInfo.getVersion())) {
List<Validator<?>> validators = field.getValidators();
if (validators != null) {
for (Validator<?> validator : validators) {
if (validator instanceof ArrayValidator<?> arrayValidator) {
validator = arrayValidator.getValidator();
} else if (validator instanceof MapKeyValidator<?> arrayValidator) {
validator = arrayValidator.getKeyValidator();
} else if (validator instanceof MapValueValidator<?> arrayValidator) {
validator = arrayValidator.getValueValidator();
}
if (validator instanceof AssetKeyValidator assetKeyValidator) {
assetClasses.add(assetKeyValidator.getStore().getAssetClass());
}
}
}
}
}
}
}
} else if (other instanceof ContainedAssetCodec<?, ?, ?> containedAssetCodec) {
maybeLateAssetClasses.add((Class<? extends JsonAsset<?>>)containedAssetCodec.getAssetClass());
}
}
HashSet<Object> missing = new HashSet<>();
HashSet<Object> unused = new HashSet<>();
for (Class<? extends JsonAsset<?>> assetClass : assetClasses) {
if (!this.loadsAfter.contains(assetClass)) {
missing.add(assetClass);
}
}
for (Class<? extends JsonAsset<?>> aClass : this.loadsAfter) {
if (!assetClasses.contains(aClass) && !maybeLateAssetClasses.contains(aClass)) {
unused.add(aClass);
}
}
if (!missing.isEmpty()) {
this.logger.at(Level.WARNING).log("\nMissing Dependencies:" + missing.stream().map(Object::toString).collect(Collectors.joining("\n- ", "\n- ", "")));
}
if (!unused.isEmpty()) {
this.logger.at(Level.WARNING).log("\nUnused Dependencies:" + unused.stream().map(Object::toString).collect(Collectors.joining("\n- ", "\n- ", "")));
}
}
@Nonnull
public AssetLoadResult<K, T> loadAssetsFromDirectory(@Nonnull String packKey, @Nonnull Path assetsPath) throws IOException {
if (this.unmodifiable) {
throw new UnsupportedOperationException("AssetStore is unmodifiable!");
} else {
Objects.requireNonNull(assetsPath, "assetsPath can't be null");
final ArrayList<Path> files = new ArrayList<>();
Set<FileVisitOption> optionsSet = Set.of();
Files.walkFileTree(assetsPath, optionsSet, Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
@Nonnull
public FileVisitResult visitFile(@Nonnull Path file, @Nonnull BasicFileAttributes attrs) throws IOException {
if (attrs.isRegularFile() && file.toString().endsWith(AssetStore.this.extension)) {
files.add(file);
}
return FileVisitResult.CONTINUE;
}
});
return this.loadAssetsFromPaths(packKey, files);
}
}
@Nonnull
public AssetLoadResult<K, T> loadAssetsFromPaths(@Nonnull String packKey, @Nonnull List<Path> paths) {
return this.loadAssetsFromPaths(packKey, paths, AssetUpdateQuery.DEFAULT);
}
@Nonnull
public AssetLoadResult<K, T> loadAssetsFromPaths(@Nonnull String packKey, @Nonnull Collection<Path> paths, @Nonnull AssetUpdateQuery query) {
return this.loadAssetsFromPaths(packKey, paths, query, false);
}
@Nonnull
public AssetLoadResult<K, T> loadAssetsFromPaths(
@Nonnull String packKey, @Nonnull Collection<Path> paths, @Nonnull AssetUpdateQuery query, boolean forceLoadAll
) {
if (this.unmodifiable) {
throw new UnsupportedOperationException("AssetStore is unmodifiable!");
} else {
Objects.requireNonNull(paths, "paths can't be null");
long start = System.nanoTime();
Set<Path> documents = new HashSet<>();
for (Path path : paths) {
Path normalize = path.toAbsolutePath().normalize();
Set<K> keys = this.assetMap.getKeys(normalize);
if (keys != null) {
for (K key : keys) {
this.loadAllChildren(documents, key);
}
}
documents.add(normalize);
this.loadAllChildren(documents, this.decodeFilePathKey(path));
}
List<RawAsset<K>> rawAssets = new ArrayList<>(documents.size());
for (Path p : documents) {
rawAssets.add(new RawAsset<>(this.decodeFilePathKey(p), p));
}
Map<K, T> loadedAssets = Collections.synchronizedMap(new Object2ObjectLinkedOpenHashMap<>());
Map<K, Path> loadedKeyToPathMap = new ConcurrentHashMap<>();
Set<K> failedToLoadKeys = ConcurrentHashMap.newKeySet();
Set<Path> failedToLoadPaths = ConcurrentHashMap.newKeySet();
Map<Class<? extends JsonAssetWithMap>, AssetLoadResult> childAssetResults = new ConcurrentHashMap<>();
this.loadAssets0(packKey, loadedAssets, rawAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, query, forceLoadAll, childAssetResults);
long end = System.nanoTime();
long diff = end - start;
this.logger
.at(Level.FINE)
.log(
"Loaded %d and removed %s (%s total) of %s from %s files in %s",
loadedAssets.size(),
failedToLoadKeys.size(),
this.assetMap.getAssetCount(),
this.tClass.getSimpleName(),
paths.size(),
FormatUtil.nanosToString(diff)
);
return new AssetLoadResult<>(loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, childAssetResults);
}
}
@Nonnull
public AssetLoadResult<K, T> loadBuffersWithKeys(
@Nonnull String packKey, @Nonnull List<RawAsset<K>> preLoaded, @Nonnull AssetUpdateQuery query, boolean forceLoadAll
) {
long start = System.nanoTime();
Set<Path> documents = new HashSet<>();
for (RawAsset<K> document : preLoaded) {
this.loadAllChildren(documents, document.getKey());
}
List<RawAsset<K>> rawAssets = new ArrayList<>(preLoaded.size() + documents.size());
rawAssets.addAll(preLoaded);
for (Path p : documents) {
rawAssets.add(new RawAsset<>(this.decodeFilePathKey(p), p));
}
Map<K, T> loadedAssets = Collections.synchronizedMap(new Object2ObjectLinkedOpenHashMap<>());
Map<K, Path> loadedKeyToPathMap = new ConcurrentHashMap<>();
Set<K> failedToLoadKeys = ConcurrentHashMap.newKeySet();
Set<Path> failedToLoadPaths = ConcurrentHashMap.newKeySet();
Map<Class<? extends JsonAssetWithMap>, AssetLoadResult> childAssetResults = new ConcurrentHashMap<>();
this.loadAssets0(packKey, loadedAssets, rawAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, query, forceLoadAll, childAssetResults);
long end = System.nanoTime();
long diff = end - start;
this.logger
.at(Level.FINE)
.log(
"Loaded %d and removed %s (%s total) of %s via loadBuffersWithKeys in %s",
loadedAssets.size(),
failedToLoadKeys.size(),
this.assetMap.getAssetCount(),
this.tClass.getSimpleName(),
FormatUtil.nanosToString(diff)
);
return new AssetLoadResult<>(loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, childAssetResults);
}
@Nonnull
public AssetLoadResult<K, T> loadAssets(@Nonnull String packKey, @Nonnull List<T> assets) {
return this.loadAssets(packKey, assets, AssetUpdateQuery.DEFAULT);
}
@Nonnull
public AssetLoadResult<K, T> loadAssets(@Nonnull String packKey, @Nonnull List<T> assets, @Nonnull AssetUpdateQuery query) {
return this.loadAssets(packKey, assets, query, false);
}
@Nonnull
public AssetLoadResult<K, T> loadAssets(@Nonnull String packKey, @Nonnull List<T> assets, @Nonnull AssetUpdateQuery query, boolean forceLoadAll) {
if (this.unmodifiable) {
throw new UnsupportedOperationException("AssetStore is unmodifiable!");
} else {
Objects.requireNonNull(assets, "assets can't be null");
long start = System.nanoTime();
Map<K, T> loadedAssets = Collections.synchronizedMap(new Object2ObjectLinkedOpenHashMap<>());
Set<Path> documents = new HashSet<>();
this.loadAllChildren(loadedAssets, assets, documents);
List<RawAsset<K>> rawAssets = new ArrayList<>(documents.size());
for (Path p : documents) {
rawAssets.add(new RawAsset<>(this.decodeFilePathKey(p), p));
}
Map<K, Path> loadedKeyToPathMap = new ConcurrentHashMap<>();
Set<K> failedToLoadKeys = ConcurrentHashMap.newKeySet();
Set<Path> failedToLoadPaths = ConcurrentHashMap.newKeySet();
Map<Class<? extends JsonAssetWithMap>, AssetLoadResult> childAssetResults = new ConcurrentHashMap<>();
this.loadAssets0(packKey, loadedAssets, rawAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, query, forceLoadAll, childAssetResults);
long end = System.nanoTime();
long diff = end - start;
this.logger
.at(Level.FINE)
.log(
"Loaded %d and removed %s (%s total) of %s via loadAssets in %s",
loadedAssets.size(),
failedToLoadKeys.size(),
this.assetMap.getAssetCount(),
this.tClass.getSimpleName(),
FormatUtil.nanosToString(diff)
);
return new AssetLoadResult<>(loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, childAssetResults);
}
}
@Nonnull
public AssetLoadResult<K, T> loadAssetsWithReferences(@Nonnull String packKey, @Nonnull Map<T, List<AssetReferences<?, ?>>> assets) {
return this.loadAssetsWithReferences(packKey, assets, AssetUpdateQuery.DEFAULT);
}
@Nonnull
public AssetLoadResult<K, T> loadAssetsWithReferences(
@Nonnull String packKey, @Nonnull Map<T, List<AssetReferences<?, ?>>> assets, @Nonnull AssetUpdateQuery query
) {
return this.loadAssetsWithReferences(packKey, assets, query, false);
}
@Nonnull
public AssetLoadResult<K, T> loadAssetsWithReferences(
@Nonnull String packKey, @Nonnull Map<T, List<AssetReferences<?, ?>>> assets, @Nonnull AssetUpdateQuery query, boolean forceLoadAll
) {
if (this.unmodifiable) {
throw new UnsupportedOperationException("AssetStore is unmodifiable!");
} else {
Objects.requireNonNull(assets, "assets can't be null");
long start = System.nanoTime();
Map<K, T> loadedAssets = Collections.synchronizedMap(new Object2ObjectLinkedOpenHashMap<>());
Set<T> assetKeys = assets.keySet();
Set<Path> documents = new HashSet<>();
this.loadAllChildren(loadedAssets, assetKeys, documents);
List<RawAsset<K>> rawAssets = new ArrayList<>(documents.size());
for (Path p : documents) {
rawAssets.add(new RawAsset<>(this.decodeFilePathKey(p), p));
}
Map<K, Path> loadedKeyToPathMap = new ConcurrentHashMap<>();
Set<K> failedToLoadKeys = ConcurrentHashMap.newKeySet();
Set<Path> failedToLoadPaths = ConcurrentHashMap.newKeySet();
Map<Class<? extends JsonAssetWithMap>, AssetLoadResult> childAssetResults = new ConcurrentHashMap<>();
this.loadAssets0(packKey, loadedAssets, rawAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, query, forceLoadAll, childAssetResults);
for (Entry<T, List<AssetReferences<?, ?>>> entry : assets.entrySet()) {
T asset = entry.getKey();
Objects.requireNonNull(asset, "asset can't be null");
K key = this.keyFunction.apply(asset);
if (key == null) {
throw new NullPointerException(String.format("key can't be null: %s", asset));
}
for (AssetReferences<?, ?> references : entry.getValue()) {
references.addChildAssetReferences(this.tClass, key);
}
}
long end = System.nanoTime();
long diff = end - start;
this.logger
.at(Level.FINE)
.log(
"Loaded %d and removed %s (%s total) of %s via loadAssetsWithReferences in %s",
loadedAssets.size(),
failedToLoadKeys.size(),
this.assetMap.getAssetCount(),
this.tClass.getSimpleName(),
FormatUtil.nanosToString(diff)
);
return new AssetLoadResult<>(loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, childAssetResults);
}
}
@Nonnull
public Set<K> removeAssetWithPaths(@Nonnull String packKey, @Nonnull List<Path> paths) {
return this.removeAssetWithPaths(packKey, paths, AssetUpdateQuery.DEFAULT);
}
@Nonnull
public Set<K> removeAssetWithPaths(@Nonnull String packKey, @Nonnull List<Path> paths, @Nonnull AssetUpdateQuery assetUpdateQuery) {
if (this.unmodifiable) {
throw new UnsupportedOperationException("AssetStore is unmodifiable!");
} else {
Set<K> allKeys = new HashSet<>();
for (Path path : paths) {
Path normalize = path.toAbsolutePath().normalize();
Set<K> keys = this.assetMap.getKeys(normalize);
if (keys != null) {
allKeys.addAll(keys);
}
}
return this.removeAssets(packKey, false, allKeys, assetUpdateQuery);
}
}
@Nonnull
public Set<K> removeAssetWithPath(Path path) {
return this.removeAssetWithPath(path, AssetUpdateQuery.DEFAULT);
}
@Nonnull
public Set<K> removeAssetWithPath(Path path, @Nonnull AssetUpdateQuery assetUpdateQuery) {
if (this.unmodifiable) {
throw new UnsupportedOperationException("AssetStore is unmodifiable!");
} else {
Path normalize = path.toAbsolutePath().normalize();
Set<K> keys = this.assetMap.getKeys(normalize);
return keys != null ? this.removeAssets("Hytale:Hytale", true, keys, assetUpdateQuery) : Collections.emptySet();
}
}
@Nonnull
public Set<K> removeAssets(@Nonnull Collection<K> keys) {
return this.removeAssets("Hytale:Hytale", true, keys, AssetUpdateQuery.DEFAULT);
}
@Nonnull
public Set<K> removeAssets(@Nonnull String packKey, boolean all, @Nonnull Collection<K> keys, @Nonnull AssetUpdateQuery assetUpdateQuery) {
if (this.unmodifiable) {
throw new UnsupportedOperationException("AssetStore is unmodifiable!");
} else {
long start = System.nanoTime();
AssetRegistry.ASSET_LOCK.writeLock().lock();
List<Entry<String, Object>> pathsToReload;
try {
Set<K> toBeRemoved = new HashSet<>();
Set<K> temp = new HashSet<>();
for (K key : keys) {
toBeRemoved.add(key);
Path path = this.assetMap.getPath(key);
if (path != null) {
this.logRemoveAsset(key, path);
} else {
this.logRemoveAsset(key, null);
}
temp.clear();
this.collectAllChildren(key, temp);
this.logRemoveChildren(key, temp);
toBeRemoved.addAll(temp);
}
if (!toBeRemoved.isEmpty()) {
this.removeChildrenAssets(packKey, toBeRemoved);
pathsToReload = null;
if (all) {
this.assetMap.remove(toBeRemoved);
} else {
pathsToReload = new ArrayList<>();
this.assetMap.remove(packKey, toBeRemoved, pathsToReload);
}
if (this.replaceOnRemove != null) {
Map<K, T> replacements = toBeRemoved.stream().collect(Collectors.toMap(Function.identity(), key -> {
T replacement = this.replaceOnRemove.apply((K)key);
Objects.requireNonNull(replacement, "Replacement can't be null!");
K replacementKey = this.keyFunction.apply(replacement);
if (replacementKey == null) {
throw new NullPointerException(key.toString());
} else {
if (!key.equals(replacementKey)) {
this.logger.at(Level.WARNING).log("Replacement key '%s' doesn't match key '%s'", replacementKey, key);
}
return replacement;
}
}));
this.assetMap.putAll("Hytale:Hytale", this.codec, replacements, Collections.emptyMap(), Collections.emptyMap());
this.handleRemoveOrUpdate(null, replacements, AssetUpdateQuery.DEFAULT);
this.loadContainedAssets("Hytale:Hytale", replacements.values(), new HashMap<>(), AssetUpdateQuery.DEFAULT, false);
} else {
this.handleRemoveOrUpdate(toBeRemoved, null, assetUpdateQuery);
}
if (pathsToReload != null) {
for (Entry<String, Object> e : pathsToReload) {
if (e.getValue() instanceof Path) {
this.loadAssetsFromPaths(e.getKey(), List.of((Path)e.getValue()));
} else {
this.loadAssets(e.getKey(), List.of((T)e.getValue()));
}
}
}
long end = System.nanoTime();
long diff = end - start;
this.logger
.at(Level.INFO)
.log(
"Removed %d (%s total) of %s via removeAssets in %s",
toBeRemoved.size(),
this.assetMap.getAssetCount(),
this.tClass.getSimpleName(),
FormatUtil.nanosToString(diff)
);
if (!toBeRemoved.isEmpty()) {
IEventDispatcher dispatcher = this.getEventBus().dispatchFor(RemovedAssetsEvent.class, this.tClass);
if (dispatcher.hasListener()) {
dispatcher.dispatch(new RemovedAssetsEvent<>(this.tClass, this.assetMap, toBeRemoved, this.replaceOnRemove != null));
}
}
return toBeRemoved;
}
pathsToReload = toBeRemoved;
} finally {
AssetRegistry.ASSET_LOCK.writeLock().unlock();
}
return pathsToReload;
}
}
public void removeAssetPack(@Nonnull String name) {
AssetRegistry.ASSET_LOCK.writeLock().lock();
try {
Set<K> assets = this.assetMap.getKeysForPack(name);
if (assets != null) {
this.removeAssets(name, false, assets, AssetUpdateQuery.DEFAULT);
return;
}
} finally {
AssetRegistry.ASSET_LOCK.writeLock().unlock();
}
}
public AssetLoadResult<K, T> writeAssetToDisk(@Nonnull AssetPack pack, @Nonnull Map<Path, T> assetsByPath) throws IOException {
return this.writeAssetToDisk(pack, assetsByPath, AssetUpdateQuery.DEFAULT);
}
public AssetLoadResult<K, T> writeAssetToDisk(@Nonnull AssetPack pack, @Nonnull Map<Path, T> assetsByPath, @Nonnull AssetUpdateQuery query) throws IOException {
if (pack.isImmutable()) {
throw new IOException("Pack is immutable");
} else {
for (Entry<Path, T> entry : assetsByPath.entrySet()) {
T asset = entry.getValue();
K id = asset.getId();
Path assetPath = pack.getRoot().resolve("Server").resolve(this.path).resolve(entry.getKey());
AssetExtraInfo.Data data = this.codec.getData(asset);
Object parentId = data == null ? null : data.getParentKey();
BsonValue bsonValue = this.codec
.encode(asset, new AssetExtraInfo(assetPath, new AssetExtraInfo.Data(this.tClass, id, this.transformKey(parentId))));
Files.writeString(assetPath, bsonValue.toString(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
}
return this.loadAssets(pack.getName(), new ArrayList<>(assetsByPath.values()), query);
}
}
@Nonnull
public T decode(@Nonnull String packKey, @Nonnull K key, @Nonnull BsonDocument document) {
KeyedCodec<K> parentCodec = this.codec.getParentCodec();
K parentKey = parentCodec != null ? parentCodec.getOrNull(document) : null;
RawJsonReader reader = RawJsonReader.fromBuffer(document.toString().toCharArray());
try {
AssetExtraInfo<K> extraInfo = new AssetExtraInfo<>(new AssetExtraInfo.Data(this.getAssetClass(), key, parentKey));
if (parentKey == null) {
reader.consumeWhiteSpace();
T asset = this.codec.decodeJsonAsset(reader, extraInfo);
if (asset == null) {
throw new NullPointerException(document.toString());
} else {
extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.logger);
this.logUnusedKeys(key, null, extraInfo);
return asset;
}
} else {
T parent = parentKey.equals("super") ? this.assetMap.getAsset(packKey, key) : this.assetMap.getAsset(parentKey);
if (parent == null) {
throw new NullPointerException(parentKey.toString());
} else {
reader.consumeWhiteSpace();
T asset = this.codec.decodeAndInheritJsonAsset(reader, parent, extraInfo);
if (asset == null) {
throw new NullPointerException(document.toString());
} else {
extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.logger);
this.logUnusedKeys(key, null, extraInfo);
return asset;
}
}
}
} catch (IOException var10) {
throw SneakyThrow.sneakyThrow(var10);
}
}
public <CK> void addChildAssetReferences(K parentKey, Class<? extends JsonAssetWithMap<CK, ?>> childAssetClass, @Nonnull Set<CK> childKeys) {
this.childAssetsMap
.computeIfAbsent(childAssetClass, k -> new ConcurrentHashMap<>())
.computeIfAbsent(parentKey, k -> ConcurrentHashMap.newKeySet())
.addAll(childKeys);
}
protected void loadAssets0(
@Nonnull String packKey,
@Nonnull Map<K, T> loadedAssets,
@Nonnull List<RawAsset<K>> preLoaded,
@Nonnull Map<K, Path> loadedKeyToPathMap,
@Nonnull Set<K> failedToLoadKeys,
@Nonnull Set<Path> failedToLoadPaths,
@Nonnull AssetUpdateQuery query,
boolean forceLoadAll,
@Nonnull Map<Class<? extends JsonAssetWithMap>, AssetLoadResult> childAssetResults
) {
Map<K, Set<K>> loadedAssetChildren = new ConcurrentHashMap<>();
this.decodeAssets(
packKey, preLoaded, loadedAssets, loadedKeyToPathMap, loadedAssetChildren, failedToLoadKeys, failedToLoadPaths, this.assetMap, query, forceLoadAll
);
AssetRegistry.ASSET_LOCK.writeLock().lock();
try {
IEventDispatcher generateDispatcher = this.getEventBus().dispatchFor(GenerateAssetsEvent.class, this.tClass);
if (generateDispatcher.hasListener()) {
generateDispatcher.dispatch(new GenerateAssetsEvent<>(this.tClass, this.assetMap, loadedAssets, loadedAssetChildren));
}
Map<K, K> toBeRemovedMap = new HashMap<>();
Set<K> temp = new HashSet<>();
for (K key : failedToLoadKeys) {
if (toBeRemovedMap.putIfAbsent(key, key) == null) {
this.logRemoveAsset(key, null);
temp.clear();
this.collectAllChildren(key, temp);
for (K k : temp) {
toBeRemovedMap.putIfAbsent(k, key);
}
}
}
for (Path path : failedToLoadPaths) {
Set<K> keys = this.assetMap.getKeys(path);
if (keys != null) {
for (K keyx : keys) {
if (toBeRemovedMap.putIfAbsent(keyx, keyx) == null) {
this.logRemoveAsset(keyx, path);
temp.clear();
this.collectAllChildren(keyx, temp);
for (K k : temp) {
toBeRemovedMap.putIfAbsent(k, keyx);
}
}
}
}
}
this.assetMap.putAll(packKey, this.codec, loadedAssets, loadedKeyToPathMap, loadedAssetChildren);
Set<K> toBeRemoved = toBeRemovedMap.keySet();
if (!toBeRemoved.isEmpty()) {
this.logRemoveChildren(toBeRemovedMap);
this.removeChildrenAssets(packKey, toBeRemoved);
}
if (this.replaceOnRemove != null && !toBeRemoved.isEmpty()) {
Map<K, T> replacements = toBeRemoved.stream()
.filter(kx -> this.assetMap.getAsset((K)kx) != null)
.collect(Collectors.toMap(Function.identity(), keyxx -> {
T replacement = this.replaceOnRemove.apply((K)keyxx);
Objects.requireNonNull(replacement, "Replacement can't be null!");
K replacementKey = this.keyFunction.apply(replacement);
if (replacementKey == null) {
throw new NullPointerException(keyxx.toString());
} else {
if (!keyxx.equals(replacementKey)) {
this.logger.at(Level.WARNING).log("Replacement key '%s' doesn't match key '%s'", replacementKey, keyxx);
}
return replacement;
}
}));
this.assetMap.putAll("Hytale:Hytale", this.codec, replacements, Collections.emptyMap(), Collections.emptyMap());
replacements.putAll(loadedAssets);
this.handleRemoveOrUpdate(null, replacements, query);
} else {
this.assetMap.remove(toBeRemoved);
this.handleRemoveOrUpdate(toBeRemoved, loadedAssets, query);
}
this.loadContainedAssets(packKey, loadedAssets.values(), childAssetResults, query, forceLoadAll);
this.reloadChildrenContainerAssets(packKey, loadedAssets);
if (!loadedAssets.isEmpty()) {
IEventDispatcher dispatcher = this.getEventBus().dispatchFor(LoadedAssetsEvent.class, this.tClass);
if (dispatcher.hasListener()) {
dispatcher.dispatch(new LoadedAssetsEvent<>(this.tClass, this.assetMap, loadedAssets, false, query));
}
}
if (!toBeRemoved.isEmpty()) {
IEventDispatcher dispatcher = this.getEventBus().dispatchFor(RemovedAssetsEvent.class, this.tClass);
if (dispatcher.hasListener()) {
dispatcher.dispatch(new RemovedAssetsEvent<>(this.tClass, this.assetMap, toBeRemoved, this.replaceOnRemove != null));
}
}
} finally {
AssetRegistry.ASSET_LOCK.writeLock().unlock();
}
}
private void reloadChildrenContainerAssets(@Nonnull String packKey, @Nonnull Map<K, T> loadedAssets) {
HashSet<Path> toReload = new HashSet<>();
HashMap<Class<? extends JsonAssetWithMap<?, ?>>, Set<Path>> toReloadTypes = new HashMap<>();
for (Entry<K, T> entry : loadedAssets.entrySet()) {
K key = entry.getKey();
Path path = this.assetMap.getPath(key);
if (path != null) {
this.collectChildrenInDifferentFile(key, path, toReload, toReloadTypes, loadedAssets.keySet());
}
}
AssetUpdateQuery query = null;
if (!toReload.isEmpty()) {
query = new AssetUpdateQuery(true, AssetUpdateQuery.RebuildCache.DEFAULT);
this.loadAssetsFromPaths(packKey, toReload, query, true);
}
if (!toReloadTypes.isEmpty()) {
if (query == null) {
query = new AssetUpdateQuery(true, AssetUpdateQuery.RebuildCache.DEFAULT);
}
for (Entry<Class<? extends JsonAssetWithMap<?, ?>>, Set<Path>> entryx : toReloadTypes.entrySet()) {
AssetStore assetStore = AssetRegistry.getAssetStore((Class<T>)entryx.getKey());
assetStore.loadAssetsFromPaths(packKey, entryx.getValue(), query, true);
}
}
}
private void collectChildrenInDifferentFile(
K key, @Nonnull Path path, @Nonnull Set<Path> paths, @Nonnull Map<Class<? extends JsonAssetWithMap<?, ?>>, Set<Path>> typedPaths, @Nonnull Set<K> ignore
) {
for (K child : this.assetMap.getChildren(key)) {
if (!ignore.contains(child)) {
Path childPath = this.assetMap.getPath(child);
if (childPath != null && !path.equals(childPath)) {
paths.add(childPath);
} else {
AssetExtraInfo.Data data = this.codec.getData(this.assetMap.getAsset(child));
AssetExtraInfo.Data root = data != null ? data.getRootContainerData() : null;
if (root != null) {
if (root.getAssetClass() == this.tClass) {
K rootKey = (K)root.getKey();
if (ignore.contains(rootKey)) {
continue;
}
Path rootPath = this.assetMap.getPath(rootKey);
if (!path.equals(rootPath)) {
paths.add(rootPath);
continue;
}
} else {
Class assetClass = root.getAssetClass();
if (assetClass == null) {
continue;
}
AssetStore assetStore = AssetRegistry.getAssetStore(assetClass);
Path rootPath = assetStore.getAssetMap().getPath(root.getKey());
if (rootPath != null) {
typedPaths.computeIfAbsent(assetClass, k -> new HashSet<>()).add(rootPath);
continue;
}
}
}
this.collectChildrenInDifferentFile(child, path, paths, typedPaths, ignore);
}
}
}
}
protected void removeChildrenAssets(@Nonnull String packKey, @Nonnull Set<K> toBeRemoved) {
for (Entry<Class<? extends JsonAssetWithMap<?, ?>>, Map<K, Set<Object>>> entry : this.childAssetsMap.entrySet()) {
Class k = entry.getKey();
Map<K, Set<Object>> value = entry.getValue();
Set<Object> allChildKeys = null;
for (K key : toBeRemoved) {
Set<Object> childKeys = value.remove(key);
if (childKeys != null) {
if (allChildKeys == null) {
allChildKeys = new HashSet<>();
}
allChildKeys.addAll(childKeys);
}
}
if (allChildKeys != null && !allChildKeys.isEmpty()) {
AssetRegistry.<Object, T, M>getAssetStore(k).removeAssets(packKey, false, allChildKeys, AssetUpdateQuery.DEFAULT);
}
}
}
protected void loadContainedAssets(
@Nonnull String packKey,
@Nonnull Collection<T> assets,
@Nonnull Map<Class<? extends JsonAssetWithMap>, AssetLoadResult> childAssetsResults,
@Nonnull AssetUpdateQuery query,
boolean forceLoadAll
) {
Map<Class<? extends JsonAssetWithMap>, Map<K, List<Object>>> containedAssetsByClass = new HashMap<>();
for (T t : assets) {
AssetExtraInfo.Data data = this.codec.getData(t);
if (data != null) {
data.fetchContainedAssets(this.keyFunction.apply(t), containedAssetsByClass);
}
}
for (Entry<Class<? extends JsonAssetWithMap>, Map<K, List<Object>>> entry : containedAssetsByClass.entrySet()) {
Class<? extends JsonAssetWithMap> assetClass = entry.getKey();
Map<K, List<Object>> containedAssets = entry.getValue();
AssetStore assetStore = AssetRegistry.getAssetStore((Class<T>)assetClass);
this.loadedContainedAssetsFor.add(assetClass);
List<Object> childList = new ArrayList<>();
for (Entry<K, List<Object>> containedEntry : containedAssets.entrySet()) {
K key = containedEntry.getKey();
for (Object contained : containedEntry.getValue()) {
Object containedKey = assetStore.getKeyFunction().apply(contained);
this.childAssetsMap
.computeIfAbsent(assetStore.getAssetClass(), k -> new ConcurrentHashMap<>())
.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
.add(containedKey);
childList.add(contained);
}
}
AssetLoadResult result = assetStore.loadAssets(packKey, (List<T>)childList, query, forceLoadAll);
childAssetsResults.put(assetClass, result);
}
Map<Class<? extends JsonAssetWithMap>, Map<K, List<RawAsset<Object>>>> containedRawAssetsByClass = new HashMap<>();
for (T tx : assets) {
AssetExtraInfo.Data data = this.codec.getData(tx);
if (data != null) {
data.fetchContainedRawAssets(this.keyFunction.apply(tx), containedRawAssetsByClass);
}
}
for (Entry<Class<? extends JsonAssetWithMap>, Map<K, List<RawAsset<Object>>>> entry : containedRawAssetsByClass.entrySet()) {
Class<? extends JsonAssetWithMap> assetClass = entry.getKey();
Map<K, List<RawAsset<Object>>> containedAssets = entry.getValue();
AssetStore assetStore = AssetRegistry.getAssetStore((Class<T>)assetClass);
this.loadedContainedAssetsFor.add(assetClass);
List<RawAsset<?>> childList = new ArrayList<>();
for (Entry<K, List<RawAsset<Object>>> containedEntry : containedAssets.entrySet()) {
K key = containedEntry.getKey();
for (RawAsset<Object> contained : containedEntry.getValue()) {
Object containedKey = contained.getKey();
this.childAssetsMap
.computeIfAbsent(assetStore.getAssetClass(), k -> new ConcurrentHashMap<>())
.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
.add(containedKey);
RawAsset<Object> resolvedContained = switch (contained.getContainedAssetMode()) {
case NONE, GENERATE_ID, INJECT_PARENT, INHERIT_ID -> contained;
case INHERIT_ID_AND_PARENT -> {
Object parentKey = contained.getParentKey();
if (parentKey == null) {
yield contained;
} else if (assetStore.getAssetMap().getAsset(parentKey) == null && !containedAssets.containsKey(parentKey)) {
this.logger
.at(Level.WARNING)
.log("Failed to find inherited parent asset %s (%s) for %s", parentKey, assetStore.getAssetClass().getSimpleName(), containedKey);
yield contained.withResolveKeys(containedKey, null);
} else {
yield contained;
}
}
};
childList.add(resolvedContained);
}
}
AssetLoadResult result = assetStore.loadBuffersWithKeys(packKey, childList, query, forceLoadAll);
childAssetsResults.put(assetClass, result);
}
}
protected void decodeAssets(
@Nonnull String packKey,
@Nonnull List<RawAsset<K>> rawAssets,
@Nonnull Map<K, T> loadedAssets,
@Nonnull Map<K, Path> loadedKeyToPathMap,
@Nonnull Map<K, Set<K>> loadedAssetChildren,
@Nonnull Set<K> failedToLoadKeys,
@Nonnull Set<Path> failedToLoadPaths,
@Nullable M assetMap,
@Nonnull AssetUpdateQuery query,
boolean forceLoadAll
) {
if (!rawAssets.isEmpty()) {
Map<K, RawAsset<K>> waitingForParent = new ConcurrentHashMap<>();
CompletableFuture<DecodedAsset<K, T>>[] futuresArr = new CompletableFuture[rawAssets.size()];
for (int i = 0; i < rawAssets.size(); i++) {
futuresArr[i] = this.executeAssetDecode(
loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, assetMap, query, forceLoadAll, waitingForParent, rawAssets.get(i)
);
}
CompletableFuture.allOf(futuresArr).join();
for (CompletableFuture<DecodedAsset<K, T>> future : futuresArr) {
DecodedAsset<K, T> decodedAsset = future.getNow(null);
if (decodedAsset != null) {
loadedAssets.put(decodedAsset.getKey(), decodedAsset.getAsset());
}
}
List<CompletableFuture<DecodedAsset<K, T>>> futures = new ArrayList<>();
while (!waitingForParent.isEmpty()) {
int processedAssets = 0;
for (Entry<K, RawAsset<K>> entry : waitingForParent.entrySet()) {
K key = entry.getKey();
RawAsset<K> rawAsset = entry.getValue();
Path path = rawAsset.getPath();
K parentKey = rawAsset.getParentKey();
T parent = loadedAssets.get(parentKey);
if (parent == null) {
if (waitingForParent.containsKey(parentKey)) {
continue;
}
if (assetMap == null) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, path);
this.logger.at(Level.SEVERE).log("Failed to find parent '%s' for asset: %s, %s (assetMap was null)", parentKey, key, path);
continue;
}
parent = parentKey.equals("super") ? assetMap.getAsset(packKey, key) : assetMap.getAsset(parentKey);
if (parent == null) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, path);
this.logger.at(Level.SEVERE).log("Failed to find parent '%s' for asset: %s, %s", parentKey, key, path);
continue;
}
}
if (this.isUnknown.test(parent)) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, path);
this.logger.at(Level.SEVERE).log("Parent '%s' for asset: %s, %s is an unknown type", parentKey, key, path);
} else {
processedAssets++;
T finalParent = parent;
futures.add(
CompletableFuture.supplyAsync(
() -> {
char[] buffer = RawJsonReader.READ_BUFFER.get();
RawJsonReader reader;
if (rawAsset.getBuffer() != null) {
reader = RawJsonReader.fromBuffer(rawAsset.getBuffer());
} else {
try {
reader = RawJsonReader.fromPath(path, buffer);
} catch (IOException var26) {
this.logger.at(Level.SEVERE).withCause(var26).log("Failed to load asset: %s", path);
return null;
}
}
DecodedAsset<K, T> decodedAsset = null;
try {
decodedAsset = this.decodeAssetWithParent0(
loadedAssets,
loadedKeyToPathMap,
loadedAssetChildren,
failedToLoadKeys,
failedToLoadPaths,
assetMap,
query,
forceLoadAll,
rawAsset,
reader,
finalParent
);
} finally {
try {
if (rawAsset.getBuffer() != null) {
reader.close();
} else {
char[] value = reader.closeAndTakeBuffer();
if (value.length > buffer.length) {
RawJsonReader.READ_BUFFER.set(value);
}
}
} catch (IOException var24x) {
this.logger.at(Level.SEVERE).withCause(var24x).log("Failed to close asset reader: %s", path);
}
if (decodedAsset == null) {
waitingForParent.remove(key);
}
}
return decodedAsset;
}
)
);
}
}
CompletableFuture<DecodedAsset<K, T>>[] futuresArray = futures.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futuresArray).join();
futures.clear();
for (CompletableFuture<DecodedAsset<K, T>> futurex : futuresArray) {
DecodedAsset<K, T> decodedAsset = futurex.getNow(null);
if (decodedAsset != null) {
loadedAssets.put(decodedAsset.getKey(), decodedAsset.getAsset());
waitingForParent.remove(decodedAsset.getKey());
}
}
if (processedAssets == 0) {
for (Entry<K, RawAsset<K>> entry : waitingForParent.entrySet()) {
K keyx = entry.getKey();
Path assetPath = entry.getValue().getPath();
K parentKeyx = entry.getValue().getParentKey();
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, keyx, assetPath);
this.logger.at(Level.SEVERE).log("Failed to find parent with key '%s' for asset: %s, %s", parentKeyx, keyx, assetPath);
}
break;
}
}
}
}
@Nonnull
private CompletableFuture<DecodedAsset<K, T>> executeAssetDecode(
@Nonnull Map<K, T> loadedAssets,
@Nonnull Map<K, Path> loadedKeyToPathMap,
@Nonnull Set<K> failedToLoadKeys,
@Nonnull Set<Path> failedToLoadPaths,
M assetMap,
@Nonnull AssetUpdateQuery query,
boolean forceLoadAll,
@Nonnull Map<K, RawAsset<K>> waitingForParent,
@Nonnull RawAsset<K> rawAsset
) {
return CompletableFuture.supplyAsync(() -> {
RawJsonReader reader;
try {
reader = rawAsset.toRawJsonReader(RawJsonReader.READ_BUFFER::get);
} catch (IOException var20) {
this.logger.at(Level.SEVERE).withCause(var20).log("Failed to load asset: %s", rawAsset);
return null;
}
AssetHolder<K> holder;
try {
holder = this.decodeAsset0(loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, assetMap, query, forceLoadAll, rawAsset, reader);
if (holder instanceof RawAsset<K> waiting) {
waitingForParent.put(waiting.getKey(), waiting);
}
} finally {
try {
if (rawAsset.getBuffer() != null) {
reader.close();
} else {
char[] value = reader.closeAndTakeBuffer();
if (value.length > RawJsonReader.READ_BUFFER.get().length) {
RawJsonReader.READ_BUFFER.set(value);
}
}
} catch (IOException var19) {
this.logger.at(Level.SEVERE).withCause(var19).log("Failed to close asset reader: %s", this.path);
}
}
return holder instanceof DecodedAsset ? (DecodedAsset)holder : null;
});
}
@Nullable
private AssetHolder<K> decodeAsset0(
@Nonnull Map<K, T> loadedAssets,
@Nonnull Map<K, Path> loadedKeyToPathMap,
@Nonnull Set<K> failedToLoadKeys,
@Nonnull Set<Path> failedToLoadPaths,
@Nullable M assetMap,
@Nonnull AssetUpdateQuery query,
boolean forceLoadAll,
@Nonnull RawAsset<K> rawAsset,
@Nonnull RawJsonReader reader
) {
Path assetPath = rawAsset.getPath();
long start = System.nanoTime();
K key = rawAsset.getKey();
K parentKey = rawAsset.getParentKey();
try {
KeyedCodec<K> keyCodec = this.codec.getKeyCodec();
KeyedCodec<K> parentCodec = this.codec.getParentCodec();
if (key == null) {
if (rawAsset.getPath() != null) {
throw new IllegalArgumentException("Asset with path should infer its 'Id'!");
}
reader.mark();
if (parentCodec != null && !rawAsset.isParentKeyResolved()) {
String s = RawJsonReader.seekToKeyFromObjectStart(reader, keyCodec.getKey(), parentCodec.getKey());
if (s != null) {
if (keyCodec.getKey().equals(s)) {
key = keyCodec.getChildCodec().decodeJson(reader);
} else if (parentCodec.getKey().equals(s)) {
parentKey = parentCodec.getChildCodec().decodeJson(reader);
}
s = RawJsonReader.seekToKeyFromObjectContinued(reader, keyCodec.getKey(), parentCodec.getKey());
if (s != null) {
if (keyCodec.getKey().equals(s)) {
key = keyCodec.getChildCodec().decodeJson(reader);
} else if (parentCodec.getKey().equals(s)) {
parentKey = parentCodec.getChildCodec().decodeJson(reader);
}
}
}
} else if (RawJsonReader.seekToKey(reader, keyCodec.getKey())) {
key = keyCodec.getChildCodec().decodeJson(reader);
}
if (key == null) {
throw new CodecException("Unable to find 'Id' in document!");
}
reader.reset();
} else if (parentCodec != null && !rawAsset.isParentKeyResolved()) {
reader.mark();
if (RawJsonReader.seekToKey(reader, parentCodec.getKey())) {
parentKey = parentCodec.getChildCodec().decodeJson(reader);
}
reader.reset();
}
if (assetPath == null) {
assetPath = loadedKeyToPathMap.get(key);
}
if (parentKey != null) {
return rawAsset.withResolveKeys(key, parentKey);
}
AssetExtraInfo<K> extraInfo = new AssetExtraInfo<>(assetPath, rawAsset.makeData(this.getAssetClass(), key, null));
reader.consumeWhiteSpace();
T asset = this.codec.decodeJsonAsset(reader, extraInfo);
if (asset == null) {
throw new NullPointerException(rawAsset.toString());
}
extraInfo.getValidationResults()
.logOrThrowValidatorExceptions(
this.logger, "Failed to validate asset!\n", assetPath == null ? rawAsset.getParentPath() : assetPath, rawAsset.getLineOffset()
);
if (!DISABLE_ASSET_COMPARE && (query == null || !query.isDisableAssetCompare()) && assetMap != null && asset.equals(assetMap.getAsset(key))) {
this.logger.at(Level.INFO).log("Skipping asset that hasn't changed: %s", key);
return null;
}
this.testKeyFormat(key, assetPath);
if (!forceLoadAll) {
}
if (assetPath != null) {
loadedKeyToPathMap.put(key, assetPath);
}
this.logUnusedKeys(key, assetPath, extraInfo);
this.logLoadedAsset(key, null, assetPath);
return new DecodedAsset<>(key, asset);
} catch (CodecValidationException var19) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
this.logger.at(Level.SEVERE).log("Failed to validate asset: %s, %s, %s", key, assetPath, var19.getMessage());
} catch (CodecException | IOException var20) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
if (GithubMessageUtil.isGithub()) {
String pathStr = assetPath == null ? (key == null ? "unknown" : key.toString()) : assetPath.toString();
String message;
if (var20 instanceof CodecException codecException) {
message = codecException.getMessage();
if (codecException.getCause() != null) {
message = message + "\nCause: " + codecException.getCause().getMessage();
}
} else {
message = var20.getMessage();
}
if (reader.getLine() == -1) {
HytaleLoggerBackend.rawLog(GithubMessageUtil.messageError(pathStr, message));
} else {
HytaleLoggerBackend.rawLog(GithubMessageUtil.messageError(pathStr, reader.getLine(), reader.getColumn(), message));
}
}
this.logger.at(Level.SEVERE).withCause(new SkipSentryException(var20)).log("Failed to decode asset: %s, %s:\n%s", key, assetPath, reader);
} catch (Throwable var21) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
if (GithubMessageUtil.isGithub()) {
String pathStrx = assetPath == null ? (key == null ? "unknown" : key.toString()) : assetPath.toString();
String messagex = var21.getMessage();
HytaleLoggerBackend.rawLog(GithubMessageUtil.messageError(pathStrx, messagex));
}
this.logger.at(Level.SEVERE).withCause(var21).log("Failed to decode asset: %s, %s", key, assetPath);
}
return null;
}
@Nullable
private DecodedAsset<K, T> decodeAssetWithParent0(
@Nonnull Map<K, T> loadedAssets,
@Nonnull Map<K, Path> loadedKeyToPathMap,
@Nonnull Map<K, Set<K>> loadedAssetChildren,
@Nonnull Set<K> failedToLoadKeys,
@Nonnull Set<Path> failedToLoadPaths,
@Nullable M assetMap,
@Nonnull AssetUpdateQuery query,
boolean forceLoadAll,
@Nonnull RawAsset<K> rawAsset,
@Nonnull RawJsonReader reader,
T parent
) {
K key = rawAsset.getKey();
if (!rawAsset.isParentKeyResolved()) {
throw new IllegalArgumentException("Parent key is required when decoding an asset with a parent!");
} else {
K parentKey = rawAsset.getParentKey();
Path assetPath = rawAsset.getPath();
try {
if (assetPath == null) {
assetPath = loadedKeyToPathMap.get(key);
}
AssetExtraInfo<K> extraInfo = new AssetExtraInfo<>(assetPath, rawAsset.makeData(this.getAssetClass(), key, parentKey));
reader.consumeWhiteSpace();
T asset = this.codec.decodeAndInheritJsonAsset(reader, parent, extraInfo);
if (asset == null) {
throw new NullPointerException(assetPath.toString());
}
extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.logger);
if (key.equals(parentKey)) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
this.logger.at(Level.SEVERE).log("Failed to load asset '%s' because it is its own parent!", key);
return null;
}
if (!DISABLE_ASSET_COMPARE && (query == null || !query.isDisableAssetCompare()) && assetMap != null && asset.equals(assetMap.getAsset(key))) {
this.logger.at(Level.INFO).log("Skipping asset that hasn't changed: %s", key);
return null;
}
this.testKeyFormat(key, assetPath);
if (!forceLoadAll) {
}
loadedAssetChildren.computeIfAbsent(parentKey, k -> ConcurrentHashMap.newKeySet()).add(key);
if (assetPath != null) {
loadedKeyToPathMap.put(key, assetPath);
}
this.logUnusedKeys(key, assetPath, extraInfo);
this.logLoadedAsset(key, parentKey, assetPath);
return new DecodedAsset<>(key, asset);
} catch (CodecValidationException var17) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
this.logger.at(Level.SEVERE).log("Failed to decode asset: %s, %s, %s", key, assetPath, var17.getMessage());
} catch (CodecException | IOException var18) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
this.logger.at(Level.SEVERE).withCause(new SkipSentryException(var18)).log("Failed to decode asset: %s, %s:\n%s", key, assetPath, reader);
} catch (Exception var19) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
this.logger.at(Level.SEVERE).withCause(var19).log("Failed to decode asset: %s, %s", key, assetPath);
}
return null;
}
}
private void loadAllChildren(@Nonnull Map<K, T> loadedAssets, @Nonnull Collection<T> assetKeys, @Nonnull Set<Path> documents) {
for (T asset : assetKeys) {
Objects.requireNonNull(asset, "asset can't be null");
K key = this.keyFunction.apply(asset);
if (key == null) {
throw new NullPointerException(String.format("key can't be null: %s", asset));
}
loadedAssets.put(key, asset);
if (this.loadAllChildren(documents, key)) {
StringBuilder sb = new StringBuilder();
sb.append(key).append(":\n");
this.logChildTree(sb, " ", key, new HashSet<>());
this.logger.at(Level.SEVERE).log("Found a circular dependency when trying to collect all children!\n%s", sb);
}
}
}
protected boolean loadAllChildren(@Nonnull Set<Path> documents, K key) {
Set<K> set = this.assetMap.getChildren(key);
if (set == null) {
return false;
} else {
boolean circular = false;
for (K child : set) {
Path childPath = this.assetMap.getPath(child);
if (childPath != null) {
if (documents.add(childPath)) {
circular |= this.loadAllChildren(documents, child);
} else {
circular = true;
}
}
}
return circular;
}
}
protected void collectAllChildren(K key, @Nonnull Set<K> children) {
if (this.collectAllChildren0(key, children)) {
StringBuilder sb = new StringBuilder();
sb.append(key).append(":\n");
this.logChildTree(sb, " ", key, new HashSet<>());
this.logger.at(Level.SEVERE).log("Found a circular dependency when trying to collect all children!\n%s", sb);
}
}
private boolean collectAllChildren0(K key, @Nonnull Set<K> children) {
Set<K> set = this.assetMap.getChildren(key);
if (set == null) {
return false;
} else {
boolean circular = false;
for (K child : set) {
if (children.add(child)) {
circular |= this.collectAllChildren0(child, children);
} else {
circular = true;
}
}
return circular;
}
}
protected void logChildTree(@Nonnull StringBuilder sb, String indent, K key, @Nonnull Set<K> children) {
Set<K> set = this.assetMap.getChildren(key);
if (set != null) {
for (K child : set) {
if (children.add(child)) {
sb.append(indent).append("- ").append(child).append('\n');
this.logChildTree(sb, indent + " ", child, children);
} else {
sb.append(indent).append("- ").append(child).append('\n').append(indent).append(" ").append("** Circular **\n");
}
}
}
}
protected void logRemoveChildren(K parentKey, @Nonnull Set<K> toBeRemoved) {
Path path = this.assetMap.getPath(parentKey);
for (K child : toBeRemoved) {
Path childPath = this.assetMap.getPath(child);
if (childPath != null) {
if (path != null) {
this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", childPath, path);
} else {
this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", childPath, parentKey);
}
} else {
this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", child, parentKey);
}
}
}
protected void logRemoveChildren(@Nonnull Map<K, K> toBeRemoved) {
for (Entry<K, K> entry : toBeRemoved.entrySet()) {
K child = entry.getKey();
K parentKey = entry.getValue();
Path childPath = this.assetMap.getPath(child);
if (childPath != null) {
Path path = this.assetMap.getPath(parentKey);
if (path != null) {
this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", childPath, path);
} else {
this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", childPath, parentKey);
}
} else {
this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", child, parentKey);
}
}
}
protected void testKeyFormat(@Nonnull K key, @Nullable Path assetPath) {
String keyStr = key.toString();
if (!StringUtil.isCapitalized(keyStr, '_')) {
String expected = StringUtil.capitalize(keyStr, '_');
if (assetPath == null) {
this.logger.at(Level.WARNING).log("Asset key '%s' has incorrect format! Expected: '%s'", key, expected);
} else {
this.logger.at(Level.WARNING).log("Asset key '%s' for file '%s' has incorrect format! Expected: '%s'", key, assetPath, expected);
}
}
}
public void logUnusedKeys(@Nonnull K key, @Nullable Path assetPath, @Nonnull AssetExtraInfo<K> extraInfo) {
List<String> unknownKeys = extraInfo.getUnknownKeys();
if (!unknownKeys.isEmpty()) {
if (GithubMessageUtil.isGithub()) {
String pathStr = assetPath == null ? key.toString() : assetPath.toString();
for (int i = 0; i < unknownKeys.size(); i++) {
String unknownKey = unknownKeys.get(i);
HytaleLoggerBackend.rawLog(GithubMessageUtil.messageWarning(pathStr, "Unused key: " + unknownKey));
}
} else if (assetPath != null) {
this.logger.at(Level.WARNING).log("Unused key(s) in '%s' file %s: %s", key, assetPath, String.join(", ", unknownKeys));
} else {
this.logger.at(Level.WARNING).log("Unused key(s) in '%s': %s", key, String.join(", ", unknownKeys));
}
}
}
protected void logLoadedAsset(K key, @Nullable K parentKey, @Nullable Path path) {
if (path == null && parentKey == null) {
this.logger.at(Level.FINE).log("Loaded asset: %s", key);
} else if (path == null) {
this.logger.at(Level.FINE).log("Loaded asset: '%s' with parent '%s'", key, parentKey);
} else if (parentKey == null) {
this.logger.at(Level.FINE).log("Loaded asset: '%s' from '%s'", key, path);
} else {
this.logger.at(Level.FINE).log("Loaded asset: '%s' from '%s' with parent '%s'", key, path, parentKey);
}
}
protected void logRemoveAsset(K key, @Nullable Path path) {
if (path == null) {
this.logger.at(Level.FINE).log("Removed asset: '%s'", key);
} else {
this.logger.at(Level.FINE).log("Removed asset: '%s' from '%s'", key, path);
}
}
private void recordFailedToLoad(@Nonnull Set<K> failedToLoadKeys, @Nonnull Set<Path> failedToLoadPaths, @Nullable K key, @Nullable Path path) {
if (key != null) {
failedToLoadKeys.add(key);
}
if (path != null) {
failedToLoadPaths.add(path);
}
}
@Nonnull
@Override
public String toString() {
return "AssetStore{tClass=" + this.tClass + "}";
}
protected abstract static class Builder<K, T extends JsonAssetWithMap<K, M>, M extends AssetMap<K, T>, B extends AssetStore.Builder<K, T, M, B>> {
@Nonnull
protected final Class<K> kClass;
@Nonnull
protected final Class<T> tClass;
protected final M assetMap;
protected final Set<Class<? extends JsonAsset<?>>> loadsAfter = new HashSet<>();
protected final Set<Class<? extends JsonAsset<?>>> loadsBefore = new HashSet<>();
protected String path;
@Nonnull
protected String extension = ".json";
protected AssetCodec<K, T> codec;
protected Function<T, K> keyFunction;
protected Function<K, T> replaceOnRemove;
protected Predicate<T> isUnknown;
protected boolean unmodifiable;
protected List<T> preAddedAssets;
protected Class<? extends JsonAsset<?>> idProvider;
public Builder(Class<K> kClass, Class<T> tClass, M assetMap) {
this.kClass = Objects.requireNonNull(kClass, "key class can't be null!");
this.tClass = Objects.requireNonNull(tClass, "asset class can't be null!");
this.assetMap = assetMap;
}
@Nonnull
public B setPath(String path) {
this.path = Objects.requireNonNull(path, "path can't be null!");
return (B)this;
}
@Nonnull
public B setExtension(@Nonnull String extension) {
Objects.requireNonNull(extension, "extension can't be null!");
if (extension.length() >= 2 && extension.charAt(0) == '.') {
this.extension = extension;
return (B)this;
} else {
throw new IllegalArgumentException("Extension must start with '.' and have at least one character after");
}
}
@Nonnull
public B setCodec(AssetCodec<K, T> codec) {
this.codec = Objects.requireNonNull(codec, "codec can't be null!");
return (B)this;
}
@Nonnull
public B setKeyFunction(Function<T, K> keyFunction) {
this.keyFunction = Objects.requireNonNull(keyFunction, "keyFunction can't be null!");
return (B)this;
}
@Nonnull
public B setIsUnknown(Predicate<T> isUnknown) {
this.isUnknown = Objects.requireNonNull(isUnknown, "isUnknown can't be null!");
return (B)this;
}
@Nonnull
@SafeVarargs
public final B loadsAfter(Class<? extends JsonAsset<?>>... clazz) {
Collections.addAll(this.loadsAfter, clazz);
return (B)this;
}
@Nonnull
@SafeVarargs
public final B loadsBefore(Class<? extends JsonAsset<?>>... clazz) {
Collections.addAll(this.loadsBefore, clazz);
return (B)this;
}
@Nonnull
public B setReplaceOnRemove(Function<K, T> replaceOnRemove) {
this.replaceOnRemove = Objects.requireNonNull(replaceOnRemove, "replaceOnRemove can't be null!");
return (B)this;
}
@Nonnull
public B unmodifiable() {
this.unmodifiable = true;
return (B)this;
}
@Nonnull
public B preLoadAssets(@Nonnull List<T> list) {
if (this.preAddedAssets == null) {
this.preAddedAssets = new ArrayList<>();
}
this.preAddedAssets.addAll(list);
return (B)this;
}
@Nonnull
public B setIdProvider(Class<? extends JsonAsset<?>> provider) {
this.idProvider = provider;
return (B)this;
}
public abstract AssetStore<K, T, M> build();
}
}