This commit is contained in:
DanMB 2022-07-08 10:46:56 -07:00
parent efe800c606
commit 6d8c92b512
14 changed files with 423 additions and 29 deletions

View File

@ -140,6 +140,13 @@ public class IrisBukkit extends JavaPlugin implements IrisPlatform {
return f;
}
@Override
public File getStudioFolder() {
File f = new File(getDataFolder(), "packs/");
f.mkdirs();
return f;
}
@Override
public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
return new IrisBukkitChunkGenerator(this, EngineConfiguration.builder()

View File

@ -1,6 +1,7 @@
package com.volmit.iris.engine;
import art.arcane.chrono.PrecisionStopwatch;
import com.volmit.iris.engine.dimension.IrisBiome;
import com.volmit.iris.engine.feature.features.FeatureTerrain;
import com.volmit.iris.engine.pipeline.EnginePipeline;
import com.volmit.iris.engine.pipeline.EnginePlumbing;
@ -11,6 +12,7 @@ import com.volmit.iris.platform.PlatformBlock;
import com.volmit.iris.platform.PlatformNamespaceKey;
import com.volmit.iris.platform.PlatformRegistry;
import com.volmit.iris.platform.PlatformWorld;
import com.volmit.iris.util.NSK;
import lombok.Data;
import manifold.util.concurrent.ConcurrentWeakHashMap;
@ -57,8 +59,8 @@ public class Engine implements Closeable {
.build())
.build();
data.loadData(getConfiguration().isMutable()
? getPlatform().getStudioFolder(getConfiguration().getDimension())
: getWorld().getIrisDataFolder());
? getPlatform().getStudioFolder()
: getWorld().getIrisDataFolder(), getConfiguration().getDimension());
}
public PlatformBlock block(String block)

View File

@ -1,6 +1,8 @@
package com.volmit.iris.engine;
import com.volmit.iris.platform.PlatformNamespaceKey;
import com.volmit.iris.platform.PlatformWorld;
import com.volmit.iris.util.NSK;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -24,5 +26,5 @@ public class EngineConfiguration {
private int threadPriority = 3;
@Builder.Default
private String dimension = "overworld";
private PlatformNamespaceKey dimension = new NSK("overworld", "main");
}

View File

@ -6,21 +6,26 @@ import art.arcane.amulet.io.JarLoader;
import art.arcane.cram.PakFile;
import art.arcane.cram.PakKey;
import art.arcane.cram.PakResource;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.volmit.iris.engine.dimension.IrisDimension;
import com.volmit.iris.engine.dimension.IrisGenerator;
import com.volmit.iris.engine.dimension.IrisRange;
import com.volmit.iris.engine.resolver.*;
import com.volmit.iris.platform.PlatformNamespaceKey;
import com.volmit.iris.util.NSK;
import lombok.Data;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
@Data
public class EngineData {
public class EngineData implements TypeAdapterFactory {
private final Engine engine;
private final Gson gson;
private List<Resolvable> resolvableTypes;
@ -35,12 +40,31 @@ public class EngineData {
.filter(i -> i.isAssignableFrom(Resolvable.class) || Resolvable.class.isAssignableFrom(i))
.filter(i -> !i.equals(EngineResolvable.class))
.map(i -> J.attempt(() -> (Resolvable) i.getDeclaredConstructor().newInstance(), null)).toList(), List.of());
GsonBuilder gsonBuilder = new GsonBuilder();
GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapterFactory(this);
resolvableTypes.forEach(i -> i.apply(gsonBuilder));
this.gson = gsonBuilder.setPrettyPrinting().create();
i("Registered " + resolvableTypes.size() + " Mutators with " + resolvableTypes.stream().filter(i -> i instanceof TypeAdapterFactory).count() + " Type Adapter Factories");
}
public void generateSchemas(File folder) throws IOException {
folder.mkdirs();
for(Resolvable i : resolvableTypes) {
IO.writeAll(new File(folder, i.entity().getId() + ".json"), i.generateSchema(this).toString());
}
}
public <T extends Resolvable> T resolve(Class<T> clazz, PlatformNamespaceKey key)
{
Resolver<?> r = resolvers.get(clazz);
if(r == null) {
return null;
}
return (T) r.resolve(key);
}
public void registerResolver(Class<?> type, Resolver<?> resolver, String namespace)
{
if(resolvers.containsKey(type)) {
@ -53,13 +77,78 @@ public class EngineData {
}
}
public void loadData(File folder) throws IOException {
public void loadData(File folder, PlatformNamespaceKey dimension) throws IOException {
i("Loading Data in " + folder.getPath());
for(File i : folder.listFiles()) {
if(i.isDirectory()) {
if(i.isDirectory() && i.getName().equals(dimension.getNamespace())) {
loadDataNamespaced(i, i.getName());
if(getEngine().getConfiguration().isMutable()) {
generateSchemas(new File(i, ".iris/schema"));
i("Generated " + new File(i, ".iris/schema").listFiles().length + " Schemas");
generateCodeWorkspace(new File(i, dimension.getNamespace() + ".code-workspace"));
i("Generated Code Workspace");
}
}
else if(i.getName().equals(dimension.getNamespace() + ".dat")) {
loadPakFile(folder, i.getName().split("\\Q.\\E")[0]);
}
}
IrisDimension dim = resolve(IrisDimension.class, dimension);
if(dim == null) {
f("Failed to load dimension " + dimension);
}
}
private void generateCodeWorkspace(File file) throws IOException {
file.getParentFile().mkdirs();
JsonObject ws = new JsonObject();
JsonArray folders = new JsonArray();
JsonObject folder = new JsonObject();
folder.add("path", new JsonPrimitive("."));
folders.add(folder);
ws.add("folders", folders);
JsonObject settings = new JsonObject();
settings.add("workbench.colorTheme", new JsonPrimitive("Monokai"));
settings.add("workbench.preferredDarkColorTheme", new JsonPrimitive("Solarized Dark"));
settings.add("workbench.tips.enabled", new JsonPrimitive(false));
settings.add("workbench.tree.indent", new JsonPrimitive(24));
settings.add("files.autoSave", new JsonPrimitive("onFocusChange"));
JsonObject jc = new JsonObject();
jc.add("editor.autoIndent", new JsonPrimitive("brackets"));
jc.add("editor.acceptSuggestionOnEnter", new JsonPrimitive("smart"));
jc.add("editor.cursorSmoothCaretAnimation", new JsonPrimitive(true));
jc.add("editor.dragAndDrop", new JsonPrimitive(false));
jc.add("files.trimTrailingWhitespace", new JsonPrimitive(true));
jc.add("diffEditor.ignoreTrimWhitespace", new JsonPrimitive(true));
jc.add("files.trimFinalNewlines", new JsonPrimitive(true));
jc.add("editor.suggest.showKeywords", new JsonPrimitive(false));
jc.add("editor.suggest.showSnippets", new JsonPrimitive(false));
jc.add("editor.suggest.showWords", new JsonPrimitive(false));
JsonObject st = new JsonObject();
st.add("strings", new JsonPrimitive(true));
jc.add("editor.quickSuggestions", st);
jc.add("editor.suggest.insertMode", new JsonPrimitive("replace"));
settings.add("[json]", jc);
settings.add("json.maxItemsComputed", new JsonPrimitive(30000));
JsonArray schemas = new JsonArray();
for(Resolvable i : resolvableTypes) {
String id = i.entity().getId();
JsonObject o = new JsonObject();
JsonArray fileMatch = new JsonArray();
fileMatch.add("/" + id + "/*.json");
o.add("fileMatch", fileMatch);
o.add("url", new JsonPrimitive(".iris/schema/" + id + ".json"));
schemas.add(o);
}
settings.add("json.schemas", schemas);
ws.add("settings", settings);
IO.writeAll(file, ws.toString());
}
public void loadDataNamespaced(File folder, String namespace) throws IOException {
@ -67,8 +156,7 @@ public class EngineData {
for(Resolvable i : resolvableTypes)
{
new File(folder, i.entity().getId()).mkdirs();
IO.writeAll(
new File(new File(folder, i.entity().getId()), "example.json"), gson.toJson(i));
IO.writeAll(new File(new File(folder, i.entity().getId()), "example.json"), gson.toJson(i));
}
for(File i : folder.listFiles())
@ -76,10 +164,6 @@ public class EngineData {
if(i.isDirectory()) {
loadDataFolder(i, namespace);
}
else if(i.getName().endsWith(".dat")) {
loadPakFile(folder, i.getName().split("\\Q.\\E")[0]);
}
}
}
@ -114,4 +198,77 @@ public class EngineData {
public void printResolvers() {
resolvers.forEach((k, i) -> i.print(k.simpleName(), this));
}
private <T> void writeSafeJson(TypeAdapter<T> delegate, JsonWriter out, T value) {
try {
delegate.write(out, value);
} catch (IOException e) {
try {
delegate.write(out, null);
} catch(IOException ex) {
throw new RuntimeException(ex);
}
}
}
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
for(Resolvable i : resolvableTypes) {
if(type.getRawType().equals(i.getClass())) {
return new TypeAdapter<>() {
public void write(JsonWriter out, T value) {writeSafeJson(delegate, out, value);}
@SuppressWarnings("unchecked")
public T read(JsonReader in) throws IOException {
JsonToken token = in.peek();
if(token == JsonToken.STRING) {
Resolver<?> resolver = getResolvers().get(i.getClass());
String key = in.nextString();
if(resolver == null) {
w("Unable to find a resolver for " + i.getClass() + " received " + key);
return null;
}
T t = (T) resolver.resolve(new NSK(key));
if(t == null) {
w("Unable to resolve " + i.getClass() + " " + key);
}
return t;
}
return delegate.read(in);
}
};
}
}
return null;
}
public Resolvable getInstance(Class<?> type) {
for(Resolvable i : resolvableTypes) {
if(i.getClass().equals(type)) {
return i;
}
}
return null;
}
public List<PlatformNamespaceKey> getAllKeys(Class<?> type) {
List<PlatformNamespaceKey> keys = new ArrayList<>();
Resolver<?> resolver = resolvers.get(type);
if(resolver != null) {
resolver.addAllKeys(keys);
}
return keys;
}
}

View File

@ -28,10 +28,11 @@ import java.util.List;
@NoArgsConstructor
@EqualsAndHashCode(callSuper=false)
@Accessors(fluent = true, chain = true)
@Resolvable.Entity(id = "palette")
@Resolvable.Entity(id = "palette", jsonTypes = {JsonToken.STRING, JsonToken.BEGIN_OBJECT})
public class IrisPalette extends EngineResolvable implements TypeAdapterFactory {
@Singular
@PlatformType(PlatformBlock.class)
@Type(String.class)
@TokenConstructor(JsonToken.STRING)
private List<String> blocks = new ArrayList<>();

View File

@ -19,7 +19,7 @@ import java.util.List;
@NoArgsConstructor
@EqualsAndHashCode(callSuper=false)
@Accessors(fluent = true, chain = true)
@Resolvable.Entity(id = "surface-layer")
@Resolvable.Entity(id = "surface")
public class IrisSurface extends EngineResolvable {
@Singular
@Type(IrisSurfaceLayer.class)

View File

@ -5,6 +5,7 @@ import com.volmit.iris.platform.PlatformNamespaceKey;
import lombok.Data;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Data
@ -62,4 +63,9 @@ public class CompositeResolver<T extends Resolvable> implements Resolver<T> {
i.print(type, printer, indent + 2);
}
}
@Override
public void addAllKeys(List<PlatformNamespaceKey> keys) {
resolvers.forEach((k, v) -> v.addAllKeys(keys));
}
}

View File

@ -2,9 +2,11 @@ package com.volmit.iris.engine.resolver;
import art.arcane.amulet.format.Form;
import com.volmit.iris.platform.PlatformNamespaceKey;
import com.volmit.iris.util.NSK;
import lombok.Data;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@Data
@ -37,6 +39,11 @@ public class FrozenResolver<T extends Resolvable> implements Resolver<T> {
printer.i(Form.repeat(" ", indent) + "Frozen[" + namespace + "] " + type);
}
@Override
public void addAllKeys(List<PlatformNamespaceKey> keys) {
registry.keySet().forEach((k) -> keys.add(new NSK(getNamespace(), k)));
}
@Override
public Resolver<T> and(String namespace, Resolver<T> resolver) {
if(!namespace.equals(getNamespace())) {

View File

@ -5,23 +5,29 @@ import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.volmit.iris.platform.PlatformNamespaceKey;
import com.volmit.iris.util.NSK;
import lombok.Data;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@Data
public class HotResolver<T extends Resolvable> implements Resolver<T>, CacheLoader<String, T> {
private final LoadingCache<String, T> cache;
private final String namespace;
private final Function<String, T> loader;
private final Supplier<List<String>> keyGetter;
public HotResolver(String namespace, Function<String, T> loader)
public HotResolver(String namespace, Function<String, T> loader, Supplier<List<String>> keyGetter)
{
this.namespace = namespace;
this.keyGetter = keyGetter;
this.loader = loader;
cache = Caffeine.newBuilder().build(this);
}
@ -70,4 +76,9 @@ public class HotResolver<T extends Resolvable> implements Resolver<T>, CacheLoad
public void print(String type, Object printer, int indent) {
printer.i(Form.repeat(" ", indent) + "Hot[" + namespace + "] " + type);
}
@Override
public void addAllKeys(List<PlatformNamespaceKey> keys) {
keys.addAll(keyGetter.get().stream().map(i -> new NSK(getNamespace(), i)).toList());
}
}

View File

@ -66,6 +66,11 @@ public class MergedNamespaceResolver<T extends Resolvable> implements Resolver<T
}
}
@Override
public void addAllKeys(List<PlatformNamespaceKey> keys) {
resolvers.forEach(i -> i.addAllKeys(keys));
}
@Override
public Resolver<T> and(String namespace, Resolver<T> resolver) {
if(namespace.equals(getNamespace()))

View File

@ -1,12 +1,15 @@
package com.volmit.iris.engine.resolver;
import art.arcane.amulet.concurrent.J;
import art.arcane.amulet.format.Form;
import art.arcane.cram.PakResource;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.*;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.volmit.iris.engine.EngineData;
import com.volmit.iris.engine.dimension.IrisSeedSetMode;
import com.volmit.iris.platform.PlatformBiome;
import com.volmit.iris.platform.PlatformBlock;
import com.volmit.iris.platform.PlatformNamespaced;
import com.volmit.iris.platform.PlatformNamespacedMutable;
import lombok.AllArgsConstructor;
@ -16,8 +19,171 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
public interface Resolvable extends PlatformNamespaced, PlatformNamespacedMutable, PakResource {
String SCHEMA = "http://json-schema.org/draft-07/schema#";
default String getSchemaId() {
return "https://art.arcane/iris/schemas/"+entity().getId()+".json";
}
default String getSchemaRefId() {
return entity().getId()+".json";
}
default JsonObject generateSchema(EngineData data) {
JsonObject object = new JsonObject();
Entity.ResolverEntityData entity = entity();
object.add("$schema", new JsonPrimitive(SCHEMA));
object.add("$id", new JsonPrimitive(getSchemaId()));
object.add("type", new JsonPrimitive("object"));
object.add("description", new JsonPrimitive("No Description for " + entity.getName()));
JsonObject properties = new JsonObject();
JsonObject definitions = new JsonObject();
for(Field i : getClass().getDeclaredFields()) {
i.access();
if (Modifier.isStatic(i.getModifiers()) || Modifier.isTransient(i.getModifiers())) {
continue;
}
properties.add(i.getName(), generateSchemaProperty(data, i.getName(), i.getType(), i.getDeclaredAnnotation(Type.class), i.getDeclaredAnnotation(PlatformType.class), definitions));
}
object.add("properties", properties);
object.add("definitions", definitions);
return object;
}
default String getSchemaDefinition(String name) {
return "D" + name.hashCode();
}
default JsonObject generateSchemaProperty(EngineData data, String name, Class<?> type, Type listType, PlatformType platformType, JsonObject definitions) {
JsonObject main = new JsonObject();
JsonArray anyOf = new JsonArray();
JsonObject object = new JsonObject();
anyOf.add(object);
object.add("description", new JsonPrimitive("No Description for field " + name));
if(type.isAssignableFrom(Resolvable.class) || Resolvable.class.isAssignableFrom(type)) {
Resolvable r = data.getInstance(type);
if(r != null) {
String def = getSchemaDefinition(r.getClass().simpleName());
object.add("$ref", new JsonPrimitive(r.getSchemaRefId()));
JsonObject registry = new JsonObject();
registry.add("description", new JsonPrimitive("No Description for field " + name));
String defName = getSchemaDefinition("resolve" + type.getCanonicalName());
registry.add("type", new JsonPrimitive("string"));
registry.add("$ref", new JsonPrimitive("#/definitions/" + defName));
if(!definitions.has(defName)) {
JsonObject enumDef = new JsonObject();
JsonArray enums = new JsonArray();
data.getAllKeys(type).forEach(i -> enums.add(i.toString()));
enumDef.add("enum", enums);
definitions.add(defName, enumDef);
}
anyOf.add(registry);
}
}
else if(type.isAssignableFrom(List.class) || List.class.isAssignableFrom(type)) {
if(listType != null) {
JsonObject internal = generateSchemaProperty(data, "list", listType.value(), null, platformType, definitions);
object.add("items", internal);
}
}
else if(type.isEnum()) {
String defName = getSchemaDefinition(type.getCanonicalName());
object.add("type", new JsonPrimitive("string"));
object.add("$ref", new JsonPrimitive("#/definitions/" + defName));
if(!definitions.has(defName)) {
try {
JsonObject enumDef = new JsonObject();
JsonArray enums = new JsonArray();
Arrays.stream((Enum<?>[])type.getDeclaredMethod("values").invoke(null)).forEach(i -> enums.add(i.name()));
enumDef.add("enum", enums);
definitions.add(defName, enumDef);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
else if(type.equals(String.class)) {
object.add("type", new JsonPrimitive("string"));
if(platformType != null) {
String defName = getSchemaDefinition(platformType.value().getCanonicalName());
object.add("$ref", new JsonPrimitive("#/definitions/" + defName));
if(!definitions.has(defName)) {
JsonObject enumDef = new JsonObject();
JsonArray enums = buildSchemaPlatformEnum(data, platformType.value());
enumDef.add("enum", enums);
definitions.add(defName, enumDef);
}
}
}
else if(type.equals(int.class) || type.equals(Integer.class)
|| type.equals(long.class) || type.equals(Long.class)
|| type.equals(byte.class) || type.equals(Byte.class)
|| type.equals(short.class) || type.equals(Short.class))
{
object.add("type", new JsonPrimitive("integer"));
if(type.equals(int.class) || type.equals(Integer.class)) {
object.add("minimum", new JsonPrimitive(Integer.MIN_VALUE));
object.add("maximum", new JsonPrimitive(Integer.MAX_VALUE));
}
else if(type.equals(long.class) || type.equals(Long.class)) {
object.add("minimum", new JsonPrimitive(Long.MIN_VALUE));
object.add("maximum", new JsonPrimitive(Long.MAX_VALUE));
}
else if(type.equals(short.class) || type.equals(Short.class)) {
object.add("minimum", new JsonPrimitive(Short.MIN_VALUE));
object.add("maximum", new JsonPrimitive(Short.MAX_VALUE));
}
else {
object.add("minimum", new JsonPrimitive(Byte.MIN_VALUE));
object.add("maximum", new JsonPrimitive(Byte.MAX_VALUE));
}
}
main.add("anyOf", anyOf);
return main;
}
default JsonArray buildSchemaPlatformEnum(EngineData data, Class<? extends PlatformNamespaced> value) {
JsonArray a = new JsonArray();
if(value.equals(PlatformBlock.class)) {
data.getEngine().getPlatform().getBlocks().map(i -> i.getKey().toString()).forEach(a::add);
}
else if(value.equals(PlatformBiome.class)) {
data.getEngine().getPlatform().getBiomes().map(i -> i.getKey().toString()).forEach(a::add);
}
return a;
}
default void apply(GsonBuilder builder) {
if(this instanceof TypeAdapterFactory f) {
builder.registerTypeAdapterFactory(f);
@ -77,7 +243,7 @@ public interface Resolvable extends PlatformNamespaced, PlatformNamespacedMutabl
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface Type {
Class<? extends Resolvable> value();
Class<?> value();
}
@Target({ElementType.FIELD, ElementType.TYPE})

View File

@ -10,10 +10,14 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public interface Resolver<T extends Resolvable> {
@SuppressWarnings("unchecked")
@ -41,8 +45,8 @@ public interface Resolver<T extends Resolvable> {
return new FrozenResolver<>(namespace, map);
}
static <F extends Resolvable> Resolver<F> hot(String namespace, Function<String, F> loader) {
return new HotResolver<>(namespace, loader);
static <F extends Resolvable> Resolver<F> hot(String namespace, Function<String, F> loader, Supplier<List<String>> keyGetter) {
return new HotResolver<>(namespace, loader, keyGetter);
}
static <F extends Resolvable> Resolver<F> hotDirectoryJson(String namespace, Class<?> resolvableClass, File folder, Gson gson) {
@ -68,6 +72,24 @@ public interface Resolver<T extends Resolvable> {
}
return null;
}, () -> {
List<String> s = new ArrayList<>();
for(File i : folder.listFiles())
{
if(i.isFile()) {
for(String j : extensions)
{
if(i.getName().endsWith(j))
{
s.add(i.getName().split("\\Q.\\E")[0]);
break;
}
}
}
}
return s;
});
}
@ -93,4 +115,6 @@ public interface Resolver<T extends Resolvable> {
}
void print(String type, Object printer, int index);
void addAllKeys(List<PlatformNamespaceKey> keys);
}

View File

@ -23,5 +23,7 @@ public interface IrisPlatform {
return key("minecraft", nsk);
}
File getStudioFolder();
File getStudioFolder(String dimension);
}

View File

@ -25,4 +25,8 @@ public class NSK implements PlatformNamespaceKey {
public String getKey() {
return key;
}
public String toString() {
return namespace + ":" + key;
}
}