Skip to content

Commit babf194

Browse files
committed
implement key serialization
improve builder to sync and detect changes server-side
1 parent 7b2bda0 commit babf194

File tree

6 files changed

+323
-25
lines changed

6 files changed

+323
-25
lines changed

src/main/java/gregtech/api/metatileentity/multiblock/ui/MultiblockUIFactory.java

Lines changed: 93 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import gregtech.api.mui.GTGuis;
1212
import gregtech.api.util.GTLog;
1313
import gregtech.api.util.GTUtility;
14+
import gregtech.api.util.JsonUtils;
1415
import gregtech.api.util.KeyUtil;
1516
import gregtech.api.util.TextFormattingUtil;
1617
import gregtech.common.ConfigHolder;
@@ -24,18 +25,18 @@
2425
import com.cleanroommc.modularui.api.widget.IWidget;
2526
import com.cleanroommc.modularui.drawable.DynamicDrawable;
2627
import com.cleanroommc.modularui.factory.PosGuiData;
28+
import com.cleanroommc.modularui.network.NetworkUtils;
2729
import com.cleanroommc.modularui.screen.ModularPanel;
28-
import com.cleanroommc.modularui.screen.RichTooltip;
2930
import com.cleanroommc.modularui.utils.Alignment;
3031
import com.cleanroommc.modularui.value.sync.BooleanSyncValue;
3132
import com.cleanroommc.modularui.value.sync.IntSyncValue;
3233
import com.cleanroommc.modularui.value.sync.PanelSyncManager;
34+
import com.cleanroommc.modularui.value.sync.SyncHandler;
3335
import com.cleanroommc.modularui.value.sync.ValueSyncHandler;
3436
import com.cleanroommc.modularui.widget.ParentWidget;
3537
import com.cleanroommc.modularui.widget.ScrollWidget;
3638
import com.cleanroommc.modularui.widget.Widget;
3739
import com.cleanroommc.modularui.widget.scroll.VerticalScrollData;
38-
import com.cleanroommc.modularui.widget.sizer.Area;
3940
import com.cleanroommc.modularui.widgets.CycleButtonWidget;
4041
import com.cleanroommc.modularui.widgets.ProgressWidget;
4142
import com.cleanroommc.modularui.widgets.RichTextWidget;
@@ -136,8 +137,9 @@ public void readInitialSync(PacketBuffer buffer) {
136137
.child(createButtons(panel, panelSyncManager)));
137138
}
138139

139-
private Widget<?> createIndicator() {
140+
private Widget<?> createIndicator(PanelSyncManager syncManager) {
140141
var builder = builder();
142+
builder.sync("indicator", syncManager);
141143
return new Widget<>()
142144
.size(18)
143145
.pos(174 - 5, screenHeight - 18 - 3)
@@ -147,19 +149,16 @@ private Widget<?> createIndicator() {
147149
}
148150

149151
private IDrawable getIndicatorOverlay(Builder builder) {
150-
builder.clear();
151-
RichTooltip test = new RichTooltip(new Area());
152-
153-
this.errorText.accept(builder);
154-
builder.build(test);
155-
if (!test.isEmpty()) {
152+
builder.setAction(this.errorText);
153+
builder.build();
154+
if (!builder.isEmpty()) {
156155
// error
157156
return GTGuiTextures.GREGTECH_LOGO_BLINKING_RED;
158157
}
159158

160-
this.warningText.accept(builder);
161-
builder.build(test);
162-
if (!test.isEmpty()) {
159+
builder.setAction(this.warningText);
160+
builder.build();
161+
if (!builder.isEmpty()) {
163162
// warn
164163
return GTGuiTextures.GREGTECH_LOGO_BLINKING_YELLOW;
165164
}
@@ -309,19 +308,25 @@ public MultiblockUIFactory customScreen(Supplier<ParentWidget<?>> customScreen)
309308
}
310309

311310
protected Widget<?> createScreen(PanelSyncManager syncManager) {
312-
final var builder = builder();
313-
this.displayText.accept(builder);
314-
315-
return new ParentWidget<>()
316-
.child(customScreen != null ? customScreen.get() : new ScrollWidget<>(new VerticalScrollData())
317-
.sizeRel(1f)
318-
.child(new RichTextWidget()
319-
.sizeRel(1f)
320-
.alignment(Alignment.TopLeft)
321-
.margin(4, 4)
322-
.autoUpdate(true)
323-
.textBuilder(builder::build)))
324-
.child(createIndicator())
311+
ParentWidget<?> root = new ParentWidget<>();
312+
if (customScreen != null && customScreen.get() != null) {
313+
root.child(customScreen.get());
314+
} else {
315+
Builder display = builder();
316+
display.setAction(this.displayText);
317+
display.sync("display", syncManager);
318+
319+
root.child(new ScrollWidget<>(new VerticalScrollData())
320+
.sizeRel(1f)
321+
.child(new RichTextWidget()
322+
.sizeRel(1f)
323+
.alignment(Alignment.TopLeft)
324+
.margin(4, 4)
325+
.autoUpdate(true)
326+
.textBuilder(display::build)));
327+
}
328+
329+
return root.child(createIndicator(syncManager))
325330
.background(GTGuiTextures.DISPLAY)
326331
.size(190, screenHeight)
327332
.pos(4, 4);
@@ -449,6 +454,8 @@ public static class Builder {
449454
* copying from GregTechDisplayScreen is easy, but extremely tedious to implement
450455
**/
451456
private final List<IDrawable> textList = new ArrayList<>();
457+
private Consumer<Builder> action;
458+
private final SyncHandler syncHandler = makeSyncHandler();
452459

453460
private BooleanSupplier isWorkingEnabled = () -> false;
454461
private BooleanSupplier isActive = () -> false;
@@ -870,10 +877,71 @@ protected void clear() {
870877
textList.clear();
871878
}
872879

880+
protected boolean hasChanged() {
881+
if (this.action == null) return false;
882+
List<String> old = new ArrayList<>();
883+
for (var drawable : this.textList) old.add(JsonUtils.toJsonString(drawable));
884+
build();
885+
if (textList.size() != old.size()) return true;
886+
for (int i = 0; i < textList.size(); i++) {
887+
if (!JsonUtils.toJsonString(textList.get(i)).equals(old.get(i)))
888+
return true;
889+
}
890+
return false;
891+
}
892+
893+
protected void sync(String key, PanelSyncManager syncManager) {
894+
syncManager.syncValue(key, this.syncHandler);
895+
}
896+
897+
private SyncHandler makeSyncHandler() {
898+
return new SyncHandler() {
899+
900+
@Override
901+
public void detectAndSendChanges(boolean init) {
902+
if (init || hasChanged()) {
903+
sync(0, this::syncText);
904+
}
905+
}
906+
907+
private void syncText(PacketBuffer buffer) {
908+
buffer.writeVarInt(textList.size());
909+
for (IDrawable drawable : textList) {
910+
var jsonString = JsonUtils.toJsonString(drawable);
911+
NetworkUtils.writeStringSafe(buffer, jsonString);
912+
}
913+
}
914+
915+
@Override
916+
public void readOnClient(int id, PacketBuffer buf) {
917+
if (id == 0) {
918+
clear();
919+
for (int i = buf.readVarInt(); i > 0; i--) {
920+
String jsonString = NetworkUtils.readStringSafe(buf);
921+
addKey(JsonUtils.fromJsonString(jsonString));
922+
}
923+
}
924+
}
925+
926+
@Override
927+
public void readOnServer(int id, PacketBuffer buf) {}
928+
};
929+
}
930+
873931
protected void build(IRichTextBuilder<?> richText) {
932+
if (dirty) build();
874933
richText.addDrawableLines(this.textList);
875934
}
876935

936+
protected void build() {
937+
this.textList.clear();
938+
if (this.action != null) this.action.accept(this);
939+
}
940+
941+
protected void setAction(Consumer<Builder> action) {
942+
this.action = action;
943+
}
944+
877945
private void addKey(IDrawable key) {
878946
this.textList.add(key);
879947
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package gregtech.api.mui.serialize;
2+
3+
import gregtech.api.mui.drawables.HoverableKey;
4+
5+
import com.cleanroommc.modularui.api.drawable.IDrawable;
6+
import com.cleanroommc.modularui.api.drawable.IKey;
7+
import com.google.gson.JsonArray;
8+
import com.google.gson.JsonDeserializationContext;
9+
import com.google.gson.JsonElement;
10+
import com.google.gson.JsonObject;
11+
import com.google.gson.JsonParseException;
12+
import com.google.gson.JsonSerializationContext;
13+
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
17+
public class DrawableSerializer implements JsonHandler<IDrawable> {
18+
19+
@Override
20+
public IDrawable deserialize(JsonElement json, JsonDeserializationContext context)
21+
throws JsonParseException {
22+
if (!json.isJsonObject()) return IDrawable.EMPTY;
23+
JsonObject parsed = json.getAsJsonObject();
24+
if (parsed.has("key") && parsed.has("tooltip")) {
25+
IKey key = context.deserialize(parsed.get("key"), IKey.class);
26+
27+
List<IDrawable> list = new ArrayList<>();
28+
for (JsonElement jsonElement : parsed.getAsJsonArray("tooltip")) {
29+
list.add(context.deserialize(jsonElement, IDrawable.class));
30+
}
31+
return HoverableKey.of(key).addLines(list);
32+
} else {
33+
return context.deserialize(parsed, IKey.class);
34+
}
35+
}
36+
37+
@Override
38+
public JsonElement serialize(IDrawable src, JsonSerializationContext context) {
39+
if (src instanceof IKey) return context.serialize(src, IKey.class);
40+
JsonObject object = new JsonObject();
41+
if (src instanceof HoverableKey hoverable) {
42+
object.add("key", context.serialize(hoverable.getKey(), IKey.class));
43+
JsonArray array = new JsonArray();
44+
for (IDrawable tooltipLine : hoverable.getTooltipLines()) {
45+
array.add(context.serialize(tooltipLine, IDrawable.class));
46+
}
47+
object.add("tooltip", array);
48+
return object;
49+
}
50+
return object;
51+
}
52+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package gregtech.api.mui.serialize;
2+
3+
import net.minecraft.util.text.TextFormatting;
4+
5+
import com.google.gson.JsonDeserializationContext;
6+
import com.google.gson.JsonElement;
7+
import com.google.gson.JsonParseException;
8+
import com.google.gson.JsonSerializationContext;
9+
10+
public class FormatSerializer implements JsonHandler<TextFormatting> {
11+
12+
@Override
13+
public TextFormatting deserialize(JsonElement json,
14+
JsonDeserializationContext context) throws JsonParseException {
15+
return TextFormatting.getValueByName(json.getAsString());
16+
}
17+
18+
@Override
19+
public JsonElement serialize(TextFormatting src,
20+
JsonSerializationContext context) {
21+
return context.serialize(src.getFriendlyName());
22+
}
23+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package gregtech.api.mui.serialize;
2+
3+
import com.cleanroommc.modularui.api.drawable.IDrawable;
4+
import com.google.gson.JsonArray;
5+
import com.google.gson.JsonDeserializationContext;
6+
import com.google.gson.JsonDeserializer;
7+
import com.google.gson.JsonElement;
8+
import com.google.gson.JsonParseException;
9+
import com.google.gson.JsonPrimitive;
10+
import com.google.gson.JsonSerializationContext;
11+
import com.google.gson.JsonSerializer;
12+
13+
import java.lang.reflect.Type;
14+
import java.util.Arrays;
15+
import java.util.function.IntFunction;
16+
17+
public interface JsonHandler<T> extends JsonSerializer<T>, JsonDeserializer<T> {
18+
19+
@Override
20+
default JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
21+
return serialize(src, context);
22+
}
23+
24+
@Override
25+
default T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
26+
throws JsonParseException {
27+
return deserialize(json, context);
28+
}
29+
30+
JsonElement serialize(T src, JsonSerializationContext context);
31+
32+
T deserialize(JsonElement json, JsonDeserializationContext context) throws JsonParseException;
33+
34+
default <R> JsonArray serializeArray(R[] objects, JsonSerializationContext context) {
35+
JsonArray array = new JsonArray();
36+
Type arrayType = objects.getClass().getComponentType();
37+
for (R t : objects) {
38+
array.add(context.serialize(t, arrayType));
39+
}
40+
return array;
41+
}
42+
43+
default <R> R[] deserializeArray(JsonArray jsonElements, JsonDeserializationContext context,
44+
IntFunction<R[]> function) {
45+
if (jsonElements == null) return function.apply(0);
46+
R[] array2 = function.apply(jsonElements.size());
47+
Type arrayType = array2.getClass().getComponentType();
48+
Arrays.setAll(array2, i -> handleArg(jsonElements.get(i), context, arrayType));
49+
return array2;
50+
}
51+
52+
static Object handleArg(JsonElement element, JsonDeserializationContext context, Type arrayType) {
53+
// args can sometimes be keys
54+
if (element.isJsonObject()) {
55+
return context.deserialize(element.getAsJsonObject(), IDrawable.class);
56+
} else if (element instanceof JsonPrimitive primitive && primitive.isNumber()) {
57+
return primitive.getAsNumber();
58+
} else {
59+
return context.deserialize(element, arrayType);
60+
}
61+
}
62+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package gregtech.api.mui.serialize;
2+
3+
import net.minecraft.util.text.TextFormatting;
4+
5+
import com.cleanroommc.modularui.api.drawable.IKey;
6+
import com.cleanroommc.modularui.drawable.text.CompoundKey;
7+
import com.cleanroommc.modularui.drawable.text.DynamicKey;
8+
import com.cleanroommc.modularui.drawable.text.LangKey;
9+
import com.cleanroommc.modularui.drawable.text.StringKey;
10+
import com.google.gson.JsonDeserializationContext;
11+
import com.google.gson.JsonElement;
12+
import com.google.gson.JsonObject;
13+
import com.google.gson.JsonParseException;
14+
import com.google.gson.JsonSerializationContext;
15+
import org.apache.commons.lang3.ArrayUtils;
16+
17+
public class KeySerializer implements JsonHandler<IKey> {
18+
19+
@Override
20+
public IKey deserialize(JsonElement json, JsonDeserializationContext context)
21+
throws JsonParseException {
22+
JsonObject object = json.getAsJsonObject();
23+
if (object.has("string")) {
24+
return IKey.str(object.get("string").getAsString());
25+
} else if (object.has("lang")) {
26+
String lang = context.deserialize(object.get("lang"), String.class);
27+
TextFormatting[] formatting = deserializeArray(
28+
object.getAsJsonArray("format"), context, TextFormatting[]::new);
29+
Object[] args = deserializeArray(
30+
object.getAsJsonArray("args"), context, Object[]::new);
31+
return IKey.lang(lang, args).format(formatting);
32+
} else if (object.has("keys")) {
33+
IKey[] keys = deserializeArray(
34+
object.getAsJsonArray("keys"), context, IKey[]::new);
35+
TextFormatting[] formatting = deserializeArray(
36+
object.getAsJsonArray("format"), context, TextFormatting[]::new);
37+
return IKey.comp(keys).format(formatting);
38+
}
39+
return IKey.EMPTY;
40+
}
41+
42+
@Override
43+
public JsonElement serialize(IKey src, JsonSerializationContext context) {
44+
JsonObject obj = new JsonObject();
45+
if (src instanceof StringKey || src instanceof DynamicKey) {
46+
obj.add("string", context.serialize(src.getFormatted()));
47+
} else if (src instanceof LangKey langKey) {
48+
obj.add("lang", context.serialize(langKey.getKeySupplier().get()));
49+
obj.add("format", serializeArray(langKey.getFormatting(), context));
50+
Object[] args = langKey.getArgsSupplier().get();
51+
if (!ArrayUtils.isEmpty(args))
52+
obj.add("args", serializeArray(args, context));
53+
} else if (src instanceof CompoundKey compoundKey) {
54+
obj.add("keys", serializeArray(compoundKey.getKeys(), context));
55+
obj.add("format", context.serialize(compoundKey.getFormatting()));
56+
}
57+
return obj;
58+
}
59+
}

0 commit comments

Comments
 (0)