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.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 java.util.stream.Stream;
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 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()) {
String var10002 = this.tClass.getSimpleName();
throw new IllegalArgumentException("AssetStore for " + var10002 + " 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(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 (K)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;
}
}
}
String var10001 = String.valueOf(key);
results.fail("Asset '" + var10001 + "' of type " + this.tClass.getName() + " doesn't exist!");
}
}
}
public void validateCodecDefaults() {
ExtraInfo extraInfo = new ExtraInfo(2147483647, 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(2147483647, 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<?> arrayValidator = (ArrayValidator)validator;
validator = arrayValidator.getValidator();
} else if (validator instanceof MapKeyValidator) {
MapKeyValidator<?> arrayValidator = (MapKeyValidator)validator;
validator = arrayValidator.getKeyValidator();
} else if (validator instanceof MapValueValidator) {
MapValueValidator<?> arrayValidator = (MapValueValidator)validator;
validator = arrayValidator.getValueValidator();
}
if (validator instanceof AssetKeyValidator) {
AssetKeyValidator assetKeyValidator = (AssetKeyValidator)validator;
assetClasses.add(assetKeyValidator.getStore().getAssetClass());
}
}
}
}
}
}
}
} else if (other instanceof ContainedAssetCodec) {
ContainedAssetCodec<?, ?, ?> containedAssetCodec = (ContainedAssetCodec)other;
maybeLateAssetClasses.add(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()) {
HytaleLogger.Api var10000 = this.logger.at(Level.WARNING);
Stream var10001 = missing.stream().map(Object::toString);
var10000.log("\nMissing Dependencies:" + (String)var10001.collect(Collectors.joining("\n- ", "\n- ", "")));
}
if (!unused.isEmpty()) {
HytaleLogger.Api var27 = this.logger.at(Level.WARNING);
Stream var28 = unused.stream().map(Object::toString);
var27.log("\nUnused Dependencies:" + (String)var28.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, 2147483647, 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<K, T>(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<K, T>(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<K, T>(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(Map.Entry<T, List<AssetReferences<?, ?>>> entry : assets.entrySet()) {
T asset = (T)(entry.getKey());
Objects.requireNonNull(asset, "asset can't be null");
K key = (K)this.keyFunction.apply(asset);
if (key == null) {
throw new NullPointerException(String.format("key can't be null: %s", asset));
}
for(AssetReferences<?, ?> references : (List)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<K, T>(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<Map.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, (Path)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 = (Map)toBeRemoved.stream().collect(Collectors.toMap(Function.identity(), (keyx) -> {
T replacement = (T)(this.replaceOnRemove.apply(keyx));
Objects.requireNonNull(replacement, "Replacement can't be null!");
K replacementKey = (K)this.keyFunction.apply(replacement);
if (replacementKey == null) {
throw new NullPointerException(keyx.toString());
} else {
if (!keyx.equals(replacementKey)) {
this.logger.at(Level.WARNING).log("Replacement key '%s' doesn't match key '%s'", replacementKey, keyx);
}
return replacement;
}
}));
this.assetMap.putAll("Hytale:Hytale", this.codec, replacements, Collections.emptyMap(), Collections.emptyMap());
this.handleRemoveOrUpdate((Set)null, replacements, AssetUpdateQuery.DEFAULT);
this.loadContainedAssets("Hytale:Hytale", replacements.values(), new HashMap(), AssetUpdateQuery.DEFAULT, false);
} else {
this.handleRemoveOrUpdate(toBeRemoved, (Map)null, assetUpdateQuery);
}
if (pathsToReload != null) {
for(Map.Entry<String, Object> e : pathsToReload) {
if (e.getValue() instanceof Path) {
this.loadAssetsFromPaths((String)e.getKey(), List.of((Path)e.getValue()));
} else {
this.loadAssets((String)e.getKey(), List.of((JsonAssetWithMap)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));
}
}
Object var24 = toBeRemoved;
return (Set<K>)var24;
}
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(Map.Entry<Path, T> entry : assetsByPath.entrySet()) {
T asset = (T)(entry.getValue());
K id = (K)asset.getId();
Path assetPath = pack.getRoot().resolve("Server").resolve(this.path).resolve((Path)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 = (K)(parentCodec != null ? parentCodec.getOrNull(document) : null);
RawJsonReader reader = RawJsonReader.fromBuffer(document.toString().toCharArray());
try {
AssetExtraInfo<K> extraInfo = new AssetExtraInfo<K>(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, (Path)null, extraInfo);
return asset;
}
} else {
T parent = parentKey.equals("super") ? (JsonAssetWithMap)this.assetMap.getAsset(packKey, key) : (JsonAssetWithMap)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, (Path)null, extraInfo);
return asset;
}
}
}
} catch (IOException e) {
throw SneakyThrow.sneakyThrow(e);
}
}
public <CK> void addChildAssetReferences(K parentKey, Class<? extends JsonAssetWithMap<CK, ?>> childAssetClass, @Nonnull Set<CK> childKeys) {
((Set)((Map)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, (Path)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 key : keys) {
if (toBeRemovedMap.putIfAbsent(key, key) == null) {
this.logRemoveAsset(key, path);
temp.clear();
this.collectAllChildren(key, temp);
for(K k : temp) {
toBeRemovedMap.putIfAbsent(k, key);
}
}
}
}
}
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 = (Map)toBeRemoved.stream().filter((kx) -> this.assetMap.getAsset(kx) != null).collect(Collectors.toMap(Function.identity(), (keyx) -> {
T replacement = (T)(this.replaceOnRemove.apply(keyx));
Objects.requireNonNull(replacement, "Replacement can't be null!");
K replacementKey = (K)this.keyFunction.apply(replacement);
if (replacementKey == null) {
throw new NullPointerException(keyx.toString());
} else {
if (!keyx.equals(replacementKey)) {
this.logger.at(Level.WARNING).log("Replacement key '%s' doesn't match key '%s'", replacementKey, keyx);
}
return replacement;
}
}));
this.assetMap.putAll("Hytale:Hytale", this.codec, replacements, Collections.emptyMap(), Collections.emptyMap());
replacements.putAll(loadedAssets);
this.handleRemoveOrUpdate((Set)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(Map.Entry<K, T> entry : loadedAssets.entrySet()) {
K key = (K)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(Map.Entry<Class<? extends JsonAssetWithMap<?, ?>>, Set<Path>> entry : toReloadTypes.entrySet()) {
AssetStore assetStore = AssetRegistry.getAssetStore((Class)entry.getKey());
assetStore.loadAssetsFromPaths(packKey, (Collection)entry.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((JsonAssetWithMap)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) {
((Set)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(Map.Entry<Class<? extends JsonAssetWithMap<?, ?>>, Map<K, Set<Object>>> entry : this.childAssetsMap.entrySet()) {
Class k = (Class)entry.getKey();
Map<K, Set<Object>> value = (Map)entry.getValue();
Set<Object> allChildKeys = null;
for(K key : toBeRemoved) {
Set<Object> childKeys = (Set)value.remove(key);
if (childKeys != null) {
if (allChildKeys == null) {
allChildKeys = new HashSet();
}
allChildKeys.addAll(childKeys);
}
}
if (allChildKeys != null && !allChildKeys.isEmpty()) {
AssetRegistry.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(Map.Entry<Class<? extends JsonAssetWithMap>, Map<K, List<Object>>> entry : containedAssetsByClass.entrySet()) {
Class<? extends JsonAssetWithMap> assetClass = (Class)entry.getKey();
Map<K, List<Object>> containedAssets = (Map)entry.getValue();
AssetStore assetStore = AssetRegistry.getAssetStore(assetClass);
this.loadedContainedAssetsFor.add(assetClass);
List<Object> childList = new ArrayList();
for(Map.Entry<K, List<Object>> containedEntry : containedAssets.entrySet()) {
K key = (K)containedEntry.getKey();
for(Object contained : (List)containedEntry.getValue()) {
Object containedKey = assetStore.getKeyFunction().apply(contained);
((Set)((Map)this.childAssetsMap.computeIfAbsent(assetStore.getAssetClass(), (k) -> new ConcurrentHashMap())).computeIfAbsent(key, (k) -> ConcurrentHashMap.newKeySet())).add(containedKey);
childList.add(contained);
}
}
AssetLoadResult result = assetStore.loadAssets(packKey, childList, query, forceLoadAll);
childAssetsResults.put(assetClass, result);
}
Map<Class<? extends JsonAssetWithMap>, Map<K, List<RawAsset<Object>>>> containedRawAssetsByClass = new HashMap();
for(T t : assets) {
AssetExtraInfo.Data data = this.codec.getData(t);
if (data != null) {
data.fetchContainedRawAssets(this.keyFunction.apply(t), containedRawAssetsByClass);
}
}
for(Map.Entry<Class<? extends JsonAssetWithMap>, Map<K, List<RawAsset<Object>>>> entry : containedRawAssetsByClass.entrySet()) {
Class<? extends JsonAssetWithMap> assetClass = (Class)entry.getKey();
Map<K, List<RawAsset<Object>>> containedAssets = (Map)entry.getValue();
AssetStore assetStore = AssetRegistry.getAssetStore(assetClass);
this.loadedContainedAssetsFor.add(assetClass);
List<RawAsset<?>> childList = new ArrayList();
for(Map.Entry<K, List<RawAsset<Object>>> containedEntry : containedAssets.entrySet()) {
K key = (K)containedEntry.getKey();
for(RawAsset<Object> contained : (List)containedEntry.getValue()) {
Object containedKey = contained.getKey();
((Set)((Map)this.childAssetsMap.computeIfAbsent(assetStore.getAssetClass(), (k) -> new ConcurrentHashMap())).computeIfAbsent(key, (k) -> ConcurrentHashMap.newKeySet())).add(containedKey);
RawAsset var10000;
switch (contained.getContainedAssetMode()) {
case NONE:
case GENERATE_ID:
case INJECT_PARENT:
case INHERIT_ID:
var10000 = contained;
break;
case INHERIT_ID_AND_PARENT:
Object parentKey = contained.getParentKey();
if (parentKey == null) {
var10000 = 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);
var10000 = contained.withResolveKeys(containedKey, (Object)null);
} else {
var10000 = contained;
}
break;
default:
throw new MatchException((String)null, (Throwable)null);
}
RawAsset<Object> resolvedContained = var10000;
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, (RawAsset)rawAssets.get(i));
}
CompletableFuture.allOf(futuresArr).join();
for(CompletableFuture<DecodedAsset<K, T>> future : futuresArr) {
DecodedAsset<K, T> decodedAsset = (DecodedAsset)future.getNow((Object)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(Map.Entry<K, RawAsset<K>> entry : waitingForParent.entrySet()) {
K key = (K)entry.getKey();
RawAsset<K> rawAsset = (RawAsset)entry.getValue();
Path path = rawAsset.getPath();
K parentKey = rawAsset.getParentKey();
T parent = (T)(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") ? (JsonAssetWithMap)assetMap.getAsset(packKey, key) : (JsonAssetWithMap)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;
futures.add(CompletableFuture.supplyAsync(() -> {
char[] buffer = (char[])RawJsonReader.READ_BUFFER.get();
RawJsonReader reader;
if (rawAsset.getBuffer() != null) {
reader = RawJsonReader.fromBuffer(rawAsset.getBuffer());
} else {
try {
reader = RawJsonReader.fromPath(path, buffer);
} catch (IOException e) {
((HytaleLogger.Api)this.logger.at(Level.SEVERE).withCause(e)).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, parent);
} 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 e) {
((HytaleLogger.Api)this.logger.at(Level.SEVERE).withCause(e)).log("Failed to close asset reader: %s", path);
}
if (decodedAsset == null) {
waitingForParent.remove(key);
}
}
return decodedAsset;
}));
}
}
CompletableFuture<DecodedAsset<K, T>>[] futuresArray = (CompletableFuture[])futures.toArray((x$0) -> new CompletableFuture[x$0]);
CompletableFuture.allOf(futuresArray).join();
futures.clear();
for(CompletableFuture<DecodedAsset<K, T>> future : futuresArray) {
DecodedAsset<K, T> decodedAsset = (DecodedAsset)future.getNow((Object)null);
if (decodedAsset != null) {
loadedAssets.put(decodedAsset.getKey(), decodedAsset.getAsset());
waitingForParent.remove(decodedAsset.getKey());
}
}
if (processedAssets == 0) {
for(Map.Entry<K, RawAsset<K>> entry : waitingForParent.entrySet()) {
K key = (K)entry.getKey();
Path assetPath = ((RawAsset)entry.getValue()).getPath();
K parentKey = (K)((RawAsset)entry.getValue()).getParentKey();
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
this.logger.at(Level.SEVERE).log("Failed to find parent with key '%s' for asset: %s, %s", parentKey, key, 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 {
ThreadLocal var10001 = RawJsonReader.READ_BUFFER;
Objects.requireNonNull(var10001);
reader = rawAsset.toRawJsonReader(var10001::get);
} catch (IOException e) {
((HytaleLogger.Api)this.logger.at(Level.SEVERE).withCause(e)).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 > ((char[])RawJsonReader.READ_BUFFER.get()).length) {
RawJsonReader.READ_BUFFER.set(value);
}
}
} catch (IOException e) {
((HytaleLogger.Api)this.logger.at(Level.SEVERE).withCause(e)).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 = (K)keyCodec.getChildCodec().decodeJson(reader);
} else if (parentCodec.getKey().equals(s)) {
parentKey = (K)parentCodec.getChildCodec().decodeJson(reader);
}
s = RawJsonReader.seekToKeyFromObjectContinued(reader, keyCodec.getKey(), parentCodec.getKey());
if (s != null) {
if (keyCodec.getKey().equals(s)) {
key = (K)keyCodec.getChildCodec().decodeJson(reader);
} else if (parentCodec.getKey().equals(s)) {
parentKey = (K)parentCodec.getChildCodec().decodeJson(reader);
}
}
}
} else if (RawJsonReader.seekToKey(reader, keyCodec.getKey())) {
key = (K)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 = (K)parentCodec.getChildCodec().decodeJson(reader);
}
reader.reset();
}
if (assetPath == null) {
assetPath = (Path)loadedKeyToPathMap.get(key);
}
if (parentKey != null) {
return rawAsset.withResolveKeys(key, parentKey);
}
AssetExtraInfo<K> extraInfo = new AssetExtraInfo<K>(assetPath, rawAsset.makeData(this.getAssetClass(), key, (Object)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)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, (Object)null, assetPath);
return new DecodedAsset(key, asset);
} catch (CodecValidationException e) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
this.logger.at(Level.SEVERE).log("Failed to validate asset: %s, %s, %s", key, assetPath, e.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 codecException = (CodecException)var20;
message = codecException.getMessage();
if (codecException.getCause() != null) {
message = message + "\nCause: " + codecException.getCause().getMessage();
}
} else {
message = ((Exception)var20).getMessage();
}
if (reader.getLine() == -1) {
HytaleLoggerBackend.rawLog(GithubMessageUtil.messageError(pathStr, message));
} else {
HytaleLoggerBackend.rawLog(GithubMessageUtil.messageError(pathStr, reader.getLine(), reader.getColumn(), message));
}
}
((HytaleLogger.Api)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 pathStr = assetPath == null ? (key == null ? "unknown" : key.toString()) : assetPath.toString();
String message = var21.getMessage();
HytaleLoggerBackend.rawLog(GithubMessageUtil.messageError(pathStr, message));
}
((HytaleLogger.Api)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 = (Path)loadedKeyToPathMap.get(key);
}
AssetExtraInfo<K> extraInfo = new AssetExtraInfo<K>(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)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) {
}
((Set)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<K, T>(key, asset);
} catch (CodecValidationException e) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
this.logger.at(Level.SEVERE).log("Failed to decode asset: %s, %s, %s", key, assetPath, e.getMessage());
} catch (CodecException | IOException e) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
((HytaleLogger.Api)this.logger.at(Level.SEVERE).withCause(new SkipSentryException(e))).log("Failed to decode asset: %s, %s:\n%s", key, assetPath, reader);
} catch (Exception e) {
this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath);
((HytaleLogger.Api)this.logger.at(Level.SEVERE).withCause(e)).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 = (K)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(Map.Entry<K, K> entry : toBeRemoved.entrySet()) {
K child = (K)entry.getKey();
K parentKey = (K)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 = (String)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
public String toString() {
return "AssetStore{tClass=" + String.valueOf(this.tClass) + "}";
}
protected abstract static class Builder<K, T extends JsonAssetWithMap<K, M>, M extends AssetMap<K, T>, B extends 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 = (Class)Objects.requireNonNull(kClass, "key class can't be null!");
this.tClass = (Class)Objects.requireNonNull(tClass, "asset class can't be null!");
this.assetMap = assetMap;
}
@Nonnull
public B setPath(String path) {
this.path = (String)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 = (AssetCodec)Objects.requireNonNull(codec, "codec can't be null!");
return (B)this;
}
@Nonnull
public B setKeyFunction(Function<T, K> keyFunction) {
this.keyFunction = (Function)Objects.requireNonNull(keyFunction, "keyFunction can't be null!");
return (B)this;
}
@Nonnull
public B setIsUnknown(Predicate<T> isUnknown) {
this.isUnknown = (Predicate)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 = (Function)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();
}
}