Skip to content

Commit 002181d

Browse files
Auto-updating observables
Instead of scheduling tasks for auto-updating elements such as items with .updatePeriodically or the contents of a referencing inventory, make Observables capable of declaring auto-update intervals in which they want to be queried. This commit also changes all observer implementations to use concurrent collections instead of synchronization where necessary to prevent calling into other methods like notifyUpdate while holding a lock.
1 parent 0e581fb commit 002181d

19 files changed

Lines changed: 177 additions & 356 deletions

invui/src/main/java/xyz/xenondevs/invui/Observable.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,33 @@ public interface Observable {
77

88
/**
99
* Adds an {@link Observer} to this {@link Observable}.
10-
* @param who The {@link Observer} to that observes this {@link Observable}.
10+
*
11+
* @param who The {@link Observer} to that observes this {@link Observable}.
1112
* @param what An integer specifying what part of this {@link Observable} the {@link Observer} is observing.
12-
* @param how An integer specifying how the {@link Observer} is observing this {@link Observable}.
13-
* Used to {@link Observer#notifyUpdate(int) notify} the {@link Observer} about updates.
13+
* @param how An integer specifying how the {@link Observer} is observing this {@link Observable}.
14+
* Used to {@link Observer#notifyUpdate(int) notify} the {@link Observer} about updates.
1415
*/
1516
void addObserver(Observer who, int what, int how);
1617

1718
/**
1819
* Removes an {@link Observer} from this {@link Observable}.
19-
* @param who The {@link Observer} that is no longer observes this {@link Observable}.
20+
*
21+
* @param who The {@link Observer} that is no longer observes this {@link Observable}.
2022
* @param what An integer specifying what part of this {@link Observable} the {@link Observer} was observing.
21-
* @param how An integer specifying how the {@link Observer} was observing this {@link Observable}.
23+
* @param how An integer specifying how the {@link Observer} was observing this {@link Observable}.
2224
*/
2325
void removeObserver(Observer who, int what, int how);
2426

27+
/**
28+
* Instead of calling {@link Observer#notifyUpdate(int)}, {@link Observable Observables} may
29+
* also define an auto-update period after which {@link Observer Obervers} should automatically
30+
* query for updates.
31+
*
32+
* @param what An integer specifying what part of this {@link Observable} the update period is requested for.
33+
* @return The update period of this {@link Observable} in ticks, or <= 0 for no auto-updates.
34+
*/
35+
default int getUpdatePeriod(int what) {
36+
return -1;
37+
}
38+
2539
}
Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package xyz.xenondevs.invui;
22

3-
import io.papermc.paper.threadedregions.scheduler.EntityScheduler;
4-
53
/**
64
* Something that can observe an {@link Observable}.
75
*/
@@ -15,10 +13,4 @@ public interface Observer {
1513
*/
1614
void notifyUpdate(int i);
1715

18-
/**
19-
* @return An {@link EntityScheduler} that can be used to schedule tasks for this {@link Observer},
20-
* or {@code null} if no such scheduler is available.
21-
*/
22-
EntityScheduler getScheduler();
23-
2416
}

invui/src/main/java/xyz/xenondevs/invui/gui/AbstractGui.java

Lines changed: 31 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import xyz.xenondevs.invui.window.WindowManager;
3030

3131
import java.util.*;
32+
import java.util.concurrent.ConcurrentHashMap;
3233
import java.util.function.Consumer;
3334
import java.util.function.Supplier;
3435
import java.util.stream.Collectors;
@@ -43,7 +44,7 @@ non-sealed abstract class AbstractGui implements Gui {
4344
private final int height;
4445
private final int size;
4546
private final @Nullable SlotElement[] slotElements;
46-
private final @Nullable Set<ObserverAtSlot>[] observers;
47+
private final List<Set<ObserverAtSlot>> observers;
4748

4849
private final MutableProperty<Boolean> frozen;
4950
private final MutableProperty<Boolean> ignoreObscuredInventorySlots;
@@ -63,7 +64,6 @@ non-sealed abstract class AbstractGui implements Gui {
6364
);
6465
}
6566

66-
@SuppressWarnings("unchecked")
6767
AbstractGui(
6868
int width, int height,
6969
MutableProperty<Boolean> frozen,
@@ -79,7 +79,7 @@ non-sealed abstract class AbstractGui implements Gui {
7979
this.background = background;
8080

8181
slotElements = new SlotElement[size];
82-
observers = new Set[size];
82+
observers = CollectionUtils.newList(size, x -> ConcurrentHashMap.newKeySet());
8383

8484
this.background.observeWeak(this, AbstractGui::notifyWindowsOnBackgroundSlots);
8585
}
@@ -533,91 +533,59 @@ private Map<? extends Inventory, Set<Integer>> getAllActiveInventorySlots(Invent
533533

534534
@Override
535535
public void notifyWindows() {
536-
synchronized (observers) {
537-
for (var observerSet : observers) {
538-
if (observerSet != null) {
539-
for (var viewerAtSlot : observerSet) {
540-
viewerAtSlot.notifyUpdate();
541-
}
542-
}
536+
for (var observerSet : observers) {
537+
for (var viewerAtSlot : observerSet) {
538+
viewerAtSlot.notifyUpdate();
543539
}
544540
}
545541
}
546542

547543
@Override
548544
public void notifyWindows(int index) {
549-
var element = getSlotElement(index);
550-
if (element == null)
551-
return;
552-
553-
synchronized (observers) {
554-
var observerSet = observers[index];
555-
if (observerSet == null)
556-
return;
557-
558-
for (var viewerAtSlot : observerSet) {
559-
viewerAtSlot.notifyUpdate();
560-
}
545+
for (var viewerAtSlot : observers.get(index)) {
546+
viewerAtSlot.notifyUpdate();
561547
}
562548
}
563549

564550
private void notifyWindowsOnBackgroundSlots() {
565-
synchronized (observers) {
566-
for (int i = 0; i < getSize(); i++) {
567-
if (slotElements[i] != null)
568-
continue;
569-
570-
var observerSet = observers[i];
571-
if (observerSet == null)
572-
continue;
573-
574-
for (var viewerAtSlot : observerSet) {
575-
viewerAtSlot.notifyUpdate();
576-
}
577-
}
551+
for (int i = 0; i < getSize(); i++) {
552+
if (slotElements[i] != null)
553+
continue;
554+
555+
notifyWindows(i);
578556
}
579557
}
580558

581559
@Override
582560
public void addObserver(Observer who, int what, int how) {
583-
synchronized (observers) {
584-
var observerSet = this.observers[what];
585-
if (observerSet == null) {
586-
observerSet = new HashSet<>();
587-
this.observers[what] = observerSet;
588-
}
589-
observerSet.add(new ObserverAtSlot(who, how));
590-
}
561+
observers.get(what).add(new ObserverAtSlot(who, how));
591562
}
592563

593564
@Override
594565
public void removeObserver(Observer who, int what, int how) {
595-
synchronized (observers) {
596-
var observerSet = this.observers[what];
597-
if (observerSet != null) {
598-
observerSet.remove(new ObserverAtSlot(who, how));
599-
if (observerSet.isEmpty())
600-
this.observers[what] = null;
601-
}
602-
}
566+
observers.get(what).remove(new ObserverAtSlot(who, how));
567+
}
568+
569+
@Override
570+
public int getUpdatePeriod(int what) {
571+
var element = getSlotElement(what);
572+
if (element != null)
573+
return element.getUpdatePeriod();
574+
return -1;
603575
}
604576

605577
@Override
606578
public @Unmodifiable Collection<Window> getWindows() {
607-
synchronized (observers) {
608-
var windows = new HashSet<Window>();
609-
for (var observerSet : observers) {
610-
if (observerSet == null)
579+
var windows = new HashSet<Window>();
580+
for (var observerSet : observers) {
581+
for (var viewerAtSlot : observerSet) {
582+
if (!(viewerAtSlot.observer() instanceof Window w))
611583
continue;
612-
for (var viewerAtSlot : observerSet) {
613-
if (!(viewerAtSlot.observer() instanceof Window w))
614-
continue;
615-
windows.add(w);
616-
}
584+
windows.add(w);
617585
}
618-
619-
return Collections.unmodifiableSet(windows);
620586
}
587+
588+
return Collections.unmodifiableSet(windows);
621589
}
622590

623591
@Override
@@ -731,12 +699,7 @@ public void setSlotElement(int index, @Nullable SlotElement slotElement) {
731699
}
732700

733701
// notify parents that a slot element has been changed
734-
var viewers = this.observers[index];
735-
if (viewers != null) {
736-
for (var viewer : viewers) {
737-
viewer.notifyUpdate();
738-
}
739-
}
702+
notifyWindows(index);
740703
}
741704

742705
@Override

invui/src/main/java/xyz/xenondevs/invui/gui/SlotElement.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ public sealed interface SlotElement {
5353
*/
5454
void removeObserver(Observer who, int how);
5555

56+
/**
57+
* Gets the {@link Observable#getUpdatePeriod(int)} of the content of this {@link SlotElement}.
58+
*
59+
* @return The update period in ticks, or <= 0 for no auto-updates.
60+
*/
61+
int getUpdatePeriod();
62+
5663
/**
5764
* Contains an {@link xyz.xenondevs.invui.item.Item}
5865
*
@@ -86,6 +93,11 @@ public void removeObserver(Observer who, int how) {
8693
item.removeObserver(who, 0, how);
8794
}
8895

96+
@Override
97+
public int getUpdatePeriod() {
98+
return item.getUpdatePeriod(0);
99+
}
100+
89101
}
90102

91103
/**
@@ -130,6 +142,11 @@ public void removeObserver(Observer who, int how) {
130142
inventory.removeObserver(who, slot, how);
131143
}
132144

145+
@Override
146+
public int getUpdatePeriod() {
147+
return inventory.getUpdatePeriod(slot);
148+
}
149+
133150
}
134151

135152
/**
@@ -190,6 +207,10 @@ public void removeObserver(Observer who, int how) {
190207
gui.removeObserver(who, slot, how);
191208
}
192209

210+
@Override
211+
public int getUpdatePeriod() {
212+
return gui.getUpdatePeriod(slot);
213+
}
193214
}
194215

195216
}

invui/src/main/java/xyz/xenondevs/invui/internal/menu/CustomContainerMenu.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,8 @@ protected void handleButtonClick(int buttonId) {
588588
protected void runInInteractionContext(Runnable run) {
589589
try {
590590
run.run();
591-
getWindowEvents().updateSlots();
591+
if (getWindow().isOpen())
592+
getWindowEvents().updateSlots();
592593
} catch (Throwable t) {
593594
InvUI.getInstance().handleException("An exception occurred while handling a window interaction", t);
594595
}

invui/src/main/java/xyz/xenondevs/invui/internal/util/CollectionUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public static <T> void forEachCatching(Iterable<? extends T> iterable, Consumer<
5757
* @return A new {@link List} of the specified size, filled with the results of the
5858
*/
5959
public static <T> List<T> newList(int size, Function<? super Integer, ? extends T> initializer) {
60-
var list = new ArrayList<T>();
60+
var list = new ArrayList<T>(size);
6161
for (int i = 0; i < size; i++) {
6262
list.add(initializer.apply(i));
6363
}

invui/src/main/java/xyz/xenondevs/invui/inventory/CompositeInventory.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,10 @@ public InventorySlot getBackingSlot(int slot) {
237237
return invSlot.inventory().getBackingSlot(invSlot.slot());
238238
}
239239

240+
@Override
241+
public int getUpdatePeriod(int what) {
242+
var invSlot = findInventory(what);
243+
return invSlot.inventory().getUpdatePeriod(invSlot.slot());
244+
}
245+
240246
}

0 commit comments

Comments
 (0)