-
Notifications
You must be signed in to change notification settings - Fork 95
/
GenericLoaderRegistry.java
189 lines (160 loc) · 6.54 KB
/
GenericLoaderRegistry.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package slimeknights.mantle.data.registry;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import slimeknights.mantle.data.gson.GenericRegisteredSerializer;
import slimeknights.mantle.data.loadable.Loadable;
import slimeknights.mantle.data.loadable.field.LoadableField;
import slimeknights.mantle.data.registry.GenericLoaderRegistry.IHaveLoader;
import java.util.function.Function;
/**
* Generic registry for an object that can both be sent over a friendly byte buffer and serialized into JSON.
* @param <T> Type of the serializable object
* @see GenericRegisteredSerializer GenericRegisteredSerializer for an alternative that does not need to handle network syncing
* @see DefaultingLoaderRegistry
*/
@SuppressWarnings("unused") // API
public class GenericLoaderRegistry<T extends IHaveLoader> implements Loadable<T> {
/** Empty object instance for compact deserialization */
protected static final JsonObject EMPTY_OBJECT = new JsonObject();
/** Display name for this registry */
@Getter
private final String name;
/** Map of all serializers for implementations */
protected final NamedComponentRegistry<IGenericLoader<? extends T>> loaders;
/** If true, single key serializations will not use a JSON object to serialize, ideal for loaders with many singletons */
protected final boolean compact;
public GenericLoaderRegistry(String name, boolean compact) {
this.name = name;
this.compact = compact;
this.loaders = new NamedComponentRegistry<>("Unknown " + name + " loader");
}
/** Registers a deserializer by name */
public void register(ResourceLocation name, IGenericLoader<? extends T> loader) {
loaders.register(name, loader);
}
@Override
public T convert(JsonElement element, String key) {
// first try object
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
return loaders.getIfPresent(object, "type").deserialize(object);
}
// try primitive if allowed
if (compact && element.isJsonPrimitive()) {
EMPTY_OBJECT.entrySet().clear();
return loaders.convert(element, "type").deserialize(EMPTY_OBJECT);
}
// neither? failed to parse
throw new JsonSyntaxException("Invalid " + name + " JSON at " + key + ", must be a JSON object" + (compact ? " or a string" : ""));
}
/**
* Deserializes the object from JSON
* @param element JSON element
* @return Deserialized object
*/
public T deserialize(JsonElement element) {
return convert(element, "[unknown]");
}
/** Serializes the object to json, fighting generics */
@SuppressWarnings("unchecked")
private <L extends IHaveLoader> JsonElement serialize(IGenericLoader<L> loader, T src) {
JsonObject json = new JsonObject();
JsonElement type = new JsonPrimitive(loaders.getKey((IGenericLoader<? extends T>)loader).toString());
json.add("type", type);
loader.serialize((L)src, json);
if (json.get("type") != type) {
throw new IllegalStateException(name + " serializer " + type.getAsString() + " modified the type key, this is not allowed as it breaks deserialization");
}
// nothing to serialize? use type directly
if (compact && json.entrySet().size() == 1) {
return type;
}
return json;
}
@Override
public JsonElement serialize(T src) {
return serialize(src.getLoader(), src);
}
/** Writes the object to the network, fighting generics */
@SuppressWarnings("unchecked")
protected <L extends IHaveLoader> void toNetwork(IGenericLoader<L> loader, T src, FriendlyByteBuf buffer) {
loader.toNetwork((L)src, buffer);
}
@SuppressWarnings("unchecked") // the cast is safe here as its just doing a map lookup, shouldn't cause harm if it fails. Besides, the loader has to extend T to work
@Override
public void encode(FriendlyByteBuf buffer, T src) {
loaders.encode(buffer, (IGenericLoader<? extends T>)src.getLoader());
toNetwork(src.getLoader(), src, buffer);
}
@Override
public T decode(FriendlyByteBuf buffer) {
return loaders.decode(buffer).fromNetwork(buffer);
}
/** @deprecated use {@link #decode(FriendlyByteBuf)} */
@Deprecated(forRemoval = true)
public void toNetwork(T src, FriendlyByteBuf buffer) {
encode(buffer, src);
}
/** @deprecated use {@link #decode(FriendlyByteBuf)} */
@Deprecated(forRemoval = true)
public T fromNetwork(FriendlyByteBuf buffer) {
return decode(buffer);
}
/** Creates a field that loads this object directly into the parent JSON object */
public <P> LoadableField<T,P> directField(String typeKey, Function<P,T> getter) {
return new DirectRegistryField<>(this, typeKey, getter);
}
@Override
public String toString() {
return getClass().getName() + "('" + name + "')";
}
/** @deprecated use {@link slimeknights.mantle.data.loadable.record.RecordLoadable} */
@Deprecated(forRemoval = true)
public interface IGenericLoader<T> {
/** Deserializes the object from json */
T deserialize(JsonObject json);
/** Reads the object from the packet buffer */
T fromNetwork(FriendlyByteBuf buffer);
/** Writes this object to json */
void serialize(T object, JsonObject json);
/** Writes this object to the packet buffer */
void toNetwork(T object, FriendlyByteBuf buffer);
}
/** Interface for an object with a loader */
public interface IHaveLoader {
/** Gets the loader for the object */
IGenericLoader<? extends IHaveLoader> getLoader();
}
/** Loader instance for an object with only a single implementation */
@RequiredArgsConstructor
public static class SingletonLoader<T> implements IGenericLoader<T> {
@Getter
private final T instance;
/** Helper for creating a loader using an anonymous class */
public SingletonLoader(Function<IGenericLoader<T>,T> creator) {
this.instance = creator.apply(this);
}
@Override
public T deserialize(JsonObject json) {
return instance;
}
@Override
public T fromNetwork(FriendlyByteBuf buffer) {
return instance;
}
@Override
public void serialize(T object, JsonObject json) {}
@Override
public void toNetwork(T object, FriendlyByteBuf buffer) {}
/** Helper to create a singleton object as an anonymous class */
public static <T> T singleton(Function<IGenericLoader<T>,T> instance) {
return new SingletonLoader<>(instance).getInstance();
}
}
}