Skip to content

Commit 4a5c1fd

Browse files
committed
DynamicLinkedSyncHandler
1 parent 6ff2006 commit 4a5c1fd

File tree

5 files changed

+213
-8
lines changed

5 files changed

+213
-8
lines changed

src/main/java/com/cleanroommc/modularui/test/TestTile.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
import com.cleanroommc.modularui.value.BoolValue;
2828
import com.cleanroommc.modularui.value.IntValue;
2929
import com.cleanroommc.modularui.value.StringValue;
30+
import com.cleanroommc.modularui.value.sync.DynamicLinkedSyncHandler;
3031
import com.cleanroommc.modularui.value.sync.DynamicSyncHandler;
32+
import com.cleanroommc.modularui.value.sync.GenericListSyncHandler;
3133
import com.cleanroommc.modularui.value.sync.GenericSyncValue;
3234
import com.cleanroommc.modularui.value.sync.IntSyncValue;
3335
import com.cleanroommc.modularui.value.sync.ItemSlotSH;
@@ -66,6 +68,7 @@
6668
import net.minecraft.item.Item;
6769
import net.minecraft.item.ItemStack;
6870
import net.minecraft.nbt.NBTTagCompound;
71+
import net.minecraft.network.PacketBuffer;
6972
import net.minecraft.tileentity.TileEntity;
7073
import net.minecraft.util.ITickable;
7174
import net.minecraftforge.fluids.FluidRegistry;
@@ -80,7 +83,9 @@
8083
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
8184
import org.jetbrains.annotations.NotNull;
8285

86+
import java.util.ArrayList;
8387
import java.util.Collection;
88+
import java.util.List;
8489
import java.util.Map;
8590
import java.util.Random;
8691
import java.util.concurrent.atomic.AtomicInteger;
@@ -108,6 +113,7 @@ public class TestTile extends TileEntity implements IGuiHolder<PosGuiData>, ITic
108113
private final int duration = 80;
109114
private int progress = 0;
110115
private int cycleState = 0;
116+
private List<Integer> serverInts = new ArrayList<>();
111117
private ItemStack displayItem = new ItemStack(Items.DIAMOND);
112118
private final IItemHandlerModifiable inventory = new ItemStackHandler(2) {
113119
@Override
@@ -141,6 +147,14 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager syncManager, UI
141147
IntSyncValue cycleStateValue = new IntSyncValue(() -> this.cycleState, val -> this.cycleState = val);
142148
syncManager.getHyperVisor().syncValue("cycle_state", cycleStateValue);
143149
syncManager.syncValue("display_item", GenericSyncValue.forItem(() -> this.displayItem, null));
150+
GenericListSyncHandler<Integer> numberListSyncHandler = GenericListSyncHandler.<Integer>builder()
151+
.getter(() -> this.serverInts)
152+
.setter(v -> this.serverInts = v)
153+
.serializer(PacketBuffer::writeVarInt)
154+
.deserializer(PacketBuffer::readVarInt)
155+
.immutableCopy()
156+
.build();
157+
syncManager.syncValue("number_list", numberListSyncHandler);
144158
syncManager.bindPlayerInventory(guiData.getPlayer());
145159

146160
DynamicSyncHandler dynamicSyncHandler = new DynamicSyncHandler()
@@ -159,6 +173,15 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager syncManager, UI
159173
return flow;
160174
});
161175

176+
DynamicLinkedSyncHandler<GenericListSyncHandler<Integer>> dynamicLinkedSyncHandler = new DynamicLinkedSyncHandler<>(numberListSyncHandler)
177+
.widgetProvider((syncManager1, value1) -> {
178+
List<Integer> vals = value1.getValue();
179+
return new Column()
180+
.widthRel(1f)
181+
.coverChildrenHeight()
182+
.children(vals.size(), i -> IKey.str(String.valueOf(vals.get(i))).asWidget().padding(2));
183+
});
184+
162185
Rectangle colorPickerBackground = new Rectangle().color(Color.RED.main);
163186
ModularPanel panel = new ModularPanel("test_tile");
164187
IPanelHandler panelSyncHandler = syncManager.syncedPanel("other_panel", true, this::openSecondWindow);
@@ -463,7 +486,11 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager syncManager, UI
463486
}))))
464487
.child(new DynamicSyncedWidget<>()
465488
.widthRel(1f)
466-
.syncHandler(dynamicSyncHandler)))
489+
.syncHandler(dynamicSyncHandler))
490+
.child(new DynamicSyncedWidget<>()
491+
.widthRel(1f)
492+
.coverChildrenHeight()
493+
.syncHandler(dynamicLinkedSyncHandler)))
467494
)
468495
.addPage(createSchemaPage(guiData))))
469496
.child(SlotGroupWidget.playerInventory(false))
@@ -587,6 +614,12 @@ public void update() {
587614
Collection<Item> vals = ForgeRegistries.ITEMS.getValuesCollection();
588615
Item item = vals.stream().skip(new Random().nextInt(vals.size())).findFirst().orElse(Items.DIAMOND);
589616
this.displayItem = new ItemStack(item, 26735987);
617+
618+
Random rnd = new Random();
619+
this.serverInts.clear();
620+
for (int i = 0; i < 5; i++) {
621+
this.serverInts.add(rnd.nextInt(100));
622+
}
590623
}
591624
}
592625
if (++this.progress == this.duration) {
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package com.cleanroommc.modularui.value.sync;
2+
3+
import com.cleanroommc.modularui.api.widget.IWidget;
4+
import com.cleanroommc.modularui.widget.WidgetTree;
5+
6+
import net.minecraft.network.PacketBuffer;
7+
8+
import org.jetbrains.annotations.ApiStatus;
9+
import org.jetbrains.annotations.Nullable;
10+
11+
import java.io.IOException;
12+
import java.util.function.Consumer;
13+
import java.util.function.Supplier;
14+
15+
/**
16+
* This is a variation of {@link DynamicSyncHandler} with the difference that this is linked to a {@link ValueSyncHandler}.
17+
* This sync handler is automatically notified, when the linked value is updated. The widget provider here has the linked sync handler as an
18+
* argument instead of a packet.
19+
* To use it simply pass in a registered value sync handler into the constructor and link it to a
20+
* {@link com.cleanroommc.modularui.widgets.DynamicSyncedWidget DynamicSyncedWidget}.
21+
*/
22+
public class DynamicLinkedSyncHandler<S extends ValueSyncHandler<?>> extends SyncHandler implements IDynamicSyncNotifiable {
23+
24+
private IWidgetProvider<S> widgetProvider;
25+
private Consumer<IWidget> onWidgetUpdate;
26+
27+
private boolean updateQueued;
28+
private IWidget lastRejectedWidget;
29+
30+
private final S linkedValue;
31+
32+
public DynamicLinkedSyncHandler(S linkedValue) {
33+
this.linkedValue = linkedValue;
34+
linkedValue.setChangeListener(() -> notifyUpdate(false));
35+
}
36+
37+
@Override
38+
public void readOnClient(int id, PacketBuffer buf) throws IOException {
39+
if (id == 0) {
40+
updateWidget(parseWidget());
41+
}
42+
}
43+
44+
@Override
45+
public void readOnServer(int id, PacketBuffer buf) throws IOException {
46+
if (id == 0) {
47+
// do nothing with the widget on server side
48+
parseWidget();
49+
}
50+
}
51+
52+
@Override
53+
public void init(String key, PanelSyncManager syncManager) {
54+
super.init(key, syncManager);
55+
if (this.updateQueued) {
56+
notifyUpdate(true);
57+
this.updateQueued = false;
58+
}
59+
}
60+
61+
private IWidget parseWidget() {
62+
getSyncManager().allowTemporarySyncHandlerRegistration(true);
63+
IWidget widget = this.widgetProvider.createWidget(getSyncManager(), this.linkedValue);
64+
getSyncManager().allowTemporarySyncHandlerRegistration(false);
65+
// collects any unregistered sync handlers
66+
// since the sync manager is currently locked and we no longer allow bypassing the lock it will crash if it finds any
67+
int unregistered = WidgetTree.countUnregisteredSyncHandlers(widget);
68+
if (unregistered > 0) {
69+
throw new IllegalStateException("Widgets created by DynamicSyncHandler can't have implicitly registered sync handlers. All" +
70+
"sync handlers must be registered with a variant of 'PanelSyncManager#getOrCreateSyncHandler(...)'.");
71+
}
72+
return widget;
73+
}
74+
75+
private void updateWidget(IWidget widget) {
76+
if (this.onWidgetUpdate == null) {
77+
// no dynamic widget is yet attached
78+
// store for later
79+
// also ignore previous stored widget
80+
this.lastRejectedWidget = widget;
81+
} else {
82+
this.onWidgetUpdate.accept(widget);
83+
}
84+
}
85+
86+
/**
87+
* Notifies the sync handler to create a new widget. It is allowed to call this method before this sync handler is initialised.
88+
* The packet will be cached until the sync handler is initialised. Only the last call of this method, while this sync handler is not
89+
* initialised is effective.
90+
*/
91+
private void notifyUpdate(boolean sync) {
92+
if (!isValid()) {
93+
// sync handler not yet initialised
94+
this.updateQueued = true;
95+
return;
96+
}
97+
IWidget widget = parseWidget();
98+
if (getSyncManager().isClient()) {
99+
updateWidget(widget);
100+
}
101+
if (sync) sync(0, b -> {});
102+
}
103+
104+
/**
105+
* Sets a widget creator which is called on client and server. {@link SyncHandler}s can be created here using
106+
* {@link PanelSyncManager#getOrCreateSyncHandler(String, int, Class, Supplier)}. Returning null in the function will not update the widget.
107+
* On client side the result is handed over to a linked {@link com.cleanroommc.modularui.widgets.DynamicSyncedWidget}.
108+
*
109+
* @param widgetProvider the widget creator function
110+
* @return this
111+
* @see IWidgetProvider
112+
*/
113+
public DynamicLinkedSyncHandler<S> widgetProvider(IWidgetProvider<S> widgetProvider) {
114+
this.widgetProvider = widgetProvider;
115+
return this;
116+
}
117+
118+
/**
119+
* An internal function which is used to link the {@link com.cleanroommc.modularui.widgets.DynamicSyncedWidget}.
120+
*/
121+
@ApiStatus.Internal
122+
@Override
123+
public void attachDynamicWidgetListener(Consumer<IWidget> onWidgetUpdate) {
124+
this.onWidgetUpdate = onWidgetUpdate;
125+
if (this.onWidgetUpdate != null && this.lastRejectedWidget != null) {
126+
this.onWidgetUpdate.accept(this.lastRejectedWidget);
127+
this.lastRejectedWidget = null;
128+
}
129+
}
130+
131+
public interface IWidgetProvider<S extends ValueSyncHandler<?>> {
132+
133+
/**
134+
* This is the function which creates a widget on client and server.
135+
* In this method sync handlers can only be registered with {@link PanelSyncManager#getOrCreateSyncHandler(String, int, Class, Supplier)}.
136+
*
137+
* @param syncManager the sync manager of the current panel
138+
* @param value the linked sync value
139+
* @return a new widget or null if widget shouldn't be updated
140+
*/
141+
@Nullable IWidget createWidget(PanelSyncManager syncManager, S value);
142+
}
143+
}

src/main/java/com/cleanroommc/modularui/value/sync/DynamicSyncHandler.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@
1414
import java.util.function.Supplier;
1515

1616
/**
17-
* This sync handler calls a function on client and server which creates a widget after being notified. The widget is then handed over to a
18-
* linked {@link com.cleanroommc.modularui.widgets.DynamicSyncedWidget}.
17+
* This sync handler is used to update a widget dynamically. The update can be called from client and server side.
18+
* To use it add a widget provider with {@link #widgetProvider(IWidgetProvider)} and link this sync handler to a
19+
* {@link com.cleanroommc.modularui.widgets.DynamicSyncedWidget DynamicSyncedWidget}. When you want the widget to be updated call
20+
* {@link #notifyUpdate(IPacketWriter)}. The passed in packed writer will write a packet, which can the be read inside the widget provider.
21+
* The widget provider as ran on both sides. Inside the provider sync handlers can be registered with variants of
22+
* {@link ISyncRegistrar#getOrCreateSyncHandler(String, int, Class, Supplier)}.
1923
*/
20-
public class DynamicSyncHandler extends SyncHandler {
24+
public class DynamicSyncHandler extends SyncHandler implements IDynamicSyncNotifiable {
2125

2226
private IWidgetProvider widgetProvider;
2327
private Consumer<IWidget> onWidgetUpdate;
@@ -114,6 +118,7 @@ public DynamicSyncHandler widgetProvider(IWidgetProvider widgetProvider) {
114118
* An internal function which is used to link the {@link com.cleanroommc.modularui.widgets.DynamicSyncedWidget}.
115119
*/
116120
@ApiStatus.Internal
121+
@Override
117122
public void attachDynamicWidgetListener(Consumer<IWidget> onWidgetUpdate) {
118123
this.onWidgetUpdate = onWidgetUpdate;
119124
if (this.onWidgetUpdate != null && this.lastRejectedWidget != null) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.cleanroommc.modularui.value.sync;
2+
3+
import com.cleanroommc.modularui.api.widget.IWidget;
4+
5+
import org.jetbrains.annotations.ApiStatus;
6+
7+
import java.util.function.Consumer;
8+
9+
public interface IDynamicSyncNotifiable {
10+
11+
/**
12+
* An internal function which is used to link the {@link com.cleanroommc.modularui.widgets.DynamicSyncedWidget}.
13+
*/
14+
@ApiStatus.Internal
15+
void attachDynamicWidgetListener(Consumer<IWidget> onWidgetUpdate);
16+
}

src/main/java/com/cleanroommc/modularui/widgets/DynamicSyncedWidget.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import com.cleanroommc.modularui.api.value.ISyncOrValue;
44
import com.cleanroommc.modularui.api.widget.IWidget;
5+
import com.cleanroommc.modularui.value.sync.DynamicLinkedSyncHandler;
56
import com.cleanroommc.modularui.value.sync.DynamicSyncHandler;
7+
import com.cleanroommc.modularui.value.sync.IDynamicSyncNotifiable;
68
import com.cleanroommc.modularui.value.sync.SyncHandler;
79
import com.cleanroommc.modularui.widget.Widget;
810

@@ -16,24 +18,25 @@
1618
* A widget which can update its child based on a function in {@link DynamicSyncHandler}.
1719
* Such a sync handler must be supplied or else this widget has no effect.
1820
* The dynamic child can be a widget tree of any size which can also contain {@link SyncHandler}s. These sync handlers MUST be registered
19-
* via a variant of {@link com.cleanroommc.modularui.value.sync.PanelSyncManager#getOrCreateSyncHandler(String, Class, Supplier)}.
21+
* via a variant of {@link com.cleanroommc.modularui.value.sync.PanelSyncManager#getOrCreateSyncHandler(String, Class, Supplier) PanelSyncManager#getOrCreateSyncHandler(String, Class, Supplier)}.
22+
* L
2023
*
2124
* @param <W> type of this widget
2225
*/
2326
public class DynamicSyncedWidget<W extends DynamicSyncedWidget<W>> extends Widget<W> {
2427

25-
private DynamicSyncHandler syncHandler;
28+
private IDynamicSyncNotifiable syncHandler;
2629
private IWidget child;
2730

2831
@Override
2932
public boolean isValidSyncOrValue(@NotNull ISyncOrValue syncOrValue) {
30-
return syncOrValue.isTypeOrEmpty(DynamicSyncHandler.class);
33+
return syncOrValue.isTypeOrEmpty(IDynamicSyncNotifiable.class);
3134
}
3235

3336
@Override
3437
protected void setSyncOrValue(@NotNull ISyncOrValue syncOrValue) {
3538
super.setSyncOrValue(syncOrValue);
36-
this.syncHandler = syncOrValue.castNullable(DynamicSyncHandler.class);
39+
this.syncHandler = syncOrValue.castNullable(IDynamicSyncNotifiable.class);
3740
if (this.syncHandler != null) this.syncHandler.attachDynamicWidgetListener(this::updateChild);
3841
}
3942

@@ -64,6 +67,11 @@ public W syncHandler(DynamicSyncHandler syncHandler) {
6467
return getThis();
6568
}
6669

70+
public W syncHandler(DynamicLinkedSyncHandler<?> syncHandler) {
71+
setSyncOrValue(ISyncOrValue.orEmpty(syncHandler));
72+
return getThis();
73+
}
74+
6775
/**
6876
* Sets an initial child. This can only be done before the widget is initialised.
6977
*

0 commit comments

Comments
 (0)