aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/common/gui/modularui/widget/DataControllerWidget.java
blob: f29b8eeaf955b462d710dd7c4e7191aa73372d9f (plain)
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
package gregtech.common.gui.modularui.widget;

import com.gtnewhorizons.modularui.api.widget.ISyncedWidget;
import com.gtnewhorizons.modularui.api.widget.Widget;
import com.gtnewhorizons.modularui.common.internal.network.NetworkUtils;
import com.gtnewhorizons.modularui.common.widget.MultiChildWidget;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import gregtech.api.gui.modularui.IDataFollowerWidget;
import gregtech.api.util.ISerializableObject;
import java.io.IOException;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import net.minecraft.network.PacketBuffer;

/**
 * Controls state of child widgets with specific data, and allows centralized control of multiple widgets.
 * e.g. clicking button B will set machine mode to B, so button A, whose state is bound to the mode,
 * will be automatically deactivated by this widget.
 * <br> This widget wraps data and handles validation, e.g. tell client to close GUI when tile is broken or cover is removed.
 * <br> Data can be anything, e.g. {@link ISerializableObject} or machine recipe mode.
 * @param <T> Data type stored in this widget
 * @see IDataFollowerWidget
 */
public abstract class DataControllerWidget<T> extends MultiChildWidget implements ISyncedWidget {

    private final Supplier<T> dataGetter;
    private final Function<T, Boolean> dataSetter;

    protected T lastData;

    private boolean needsUpdate;

    /**
     * @param dataGetter () -> data this widget handles
     * @param dataSetter data to set -> if setting data is successful
     */
    public DataControllerWidget(Supplier<T> dataGetter, Function<T, Boolean> dataSetter) {
        this.dataGetter = dataGetter;
        this.dataSetter = dataSetter;
    }

    protected T getLastData() {
        return lastData;
    }

    @Override
    public void onPostInit() {
        super.onPostInit();
        // client _should_ have received initial cover data from `GT_UIInfos#openCoverUI`
        lastData = dataGetter.get();
        if (NetworkUtils.isClient()) {
            updateChildren(true);
        }
    }

    @Override
    public void detectAndSendChanges(boolean init) {
        T actualValue = dataGetter.get();
        if (actualValue == null) {
            // data is in invalid state e.g. tile is broken, cover is removed
            getWindow().tryClose();
            return;
        }
        if (init || !actualValue.equals(getLastData())) {
            // init sync or someone else edited data
            lastData = actualValue;
            syncDataToClient(actualValue);
        }
    }

    protected void syncDataToClient(T data) {
        syncToClient(0, buffer -> writeToPacket(buffer, data));
    }

    protected void syncDataToServer(T data) {
        syncToServer(0, buffer -> writeToPacket(buffer, data));
        updateChildren();
    }

    @Override
    public void readOnClient(int id, PacketBuffer buf) throws IOException {
        if (id == 0) {
            lastData = readFromPacket(buf);
            dataSetter.apply(getLastData());
            updateChildren();
        }
    }

    @Override
    public void readOnServer(int id, PacketBuffer buf) throws IOException {
        if (id == 0) {
            lastData = readFromPacket(buf);
            if (dataSetter.apply(getLastData())) {
                markForUpdate();
            } else {
                getWindow().closeWindow();
            }
        }
    }

    @SuppressWarnings("unchecked")
    @SideOnly(Side.CLIENT)
    protected void updateChildren(boolean postInit) {
        for (Widget child : getChildren()) {
            if (child instanceof IDataFollowerWidget) {
                ((IDataFollowerWidget<T, ?>) child).updateState(getLastData());
                if (postInit) {
                    ((IDataFollowerWidget<T, ?>) child).onPostInit();
                }
            }
        }
    }

    @SideOnly(Side.CLIENT)
    protected void updateChildren() {
        updateChildren(false);
    }

    protected abstract void writeToPacket(PacketBuffer buffer, T data);

    protected abstract T readFromPacket(PacketBuffer buffer) throws IOException;

    @Override
    public void markForUpdate() {
        needsUpdate = true;
    }

    @Override
    public void unMarkForUpdate() {
        needsUpdate = false;
    }

    @Override
    public boolean isMarkedForUpdate() {
        return needsUpdate;
    }

    /**
     * @param widget widget to add that implements {@link IDataFollowerWidget}
     * @param dataToStateGetter given data -> state of the widget to add
     * @param dataUpdater (current data, state of the widget to add) -> new data to set
     * @param applyForWidget methods to call for the widget to add
     * @param <U> state type stored in the widget to add
     * @param <W> widget type to add
     */
    public <U, W extends Widget & IDataFollowerWidget<T, U>> DataControllerWidget<T> addFollower(
            W widget, Function<T, U> dataToStateGetter, BiFunction<T, U, T> dataUpdater, Consumer<W> applyForWidget) {
        widget.setDataToStateGetter(dataToStateGetter);
        widget.setStateSetter(state -> {
            T newData = dataUpdater.apply(getLastData(), state);
            lastData = newData;
            dataSetter.apply(getLastData());
            syncDataToServer(newData);
        });
        applyForWidget.accept(widget);
        addChild(widget);
        return this;
    }
}