Skip to content

Commit 835717a

Browse files
Per-observer update schedulers
For custom item and referencing inventoy: Create one update task per observer, using the observer's own EntityScheduler
1 parent 715da23 commit 835717a

File tree

7 files changed

+221
-88
lines changed

7 files changed

+221
-88
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package xyz.xenondevs.invui;
22

3+
import io.papermc.paper.threadedregions.scheduler.EntityScheduler;
4+
35
/**
46
* Something that can observe an {@link Observable}.
57
*/
@@ -13,4 +15,10 @@ public interface Observer {
1315
*/
1416
void notifyUpdate(int i);
1517

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+
1624
}

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

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package xyz.xenondevs.invui.inventory;
22

3+
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
4+
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
5+
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
6+
import it.unimi.dsi.fastutil.ints.IntSet;
37
import org.bukkit.Material;
48
import org.bukkit.event.inventory.InventoryAction;
59
import org.bukkit.inventory.ItemStack;
@@ -18,7 +22,6 @@
1822
import xyz.xenondevs.invui.inventory.event.ItemPreUpdateEvent;
1923
import xyz.xenondevs.invui.inventory.event.UpdateReason;
2024
import xyz.xenondevs.invui.util.ItemUtils;
21-
import xyz.xenondevs.invui.util.ObserverAtSlot;
2225
import xyz.xenondevs.invui.window.Window;
2326

2427
import java.util.*;
@@ -54,7 +57,7 @@
5457
public sealed abstract class Inventory implements Observable permits VirtualInventory, CompositeInventory, ObscuredInventory, ReferencingInventory {
5558

5659
protected int size;
57-
protected final @Nullable Set<ObserverAtSlot>[] observers;
60+
protected final Map<Observer, Int2ObjectMap<IntSet>> observers;
5861
private @Nullable List<Consumer<? super InventoryClickEvent>> clickHandlers;
5962
private @Nullable List<Consumer<? super ItemPreUpdateEvent>> preUpdateHandlers;
6063
private @Nullable List<Consumer<? super ItemPostUpdateEvent>> postUpdateHandlers;
@@ -64,7 +67,7 @@ public sealed abstract class Inventory implements Observable permits VirtualInve
6467
@SuppressWarnings("unchecked")
6568
public Inventory(int size) {
6669
this.size = size;
67-
observers = new Set[size];
70+
observers = new HashMap<>();
6871
iterationOrders = CollectionUtils.newEnumMap(OperationCategory.class, k -> IntStream.range(0, size).toArray());
6972
guiPriorities = CollectionUtils.newEnumMap(OperationCategory.class, k -> 0);
7073
}
@@ -222,24 +225,29 @@ public void reverseIterationOrder(OperationCategory category) {
222225
@Override
223226
public void addObserver(Observer who, int what, int how) {
224227
synchronized (observers) {
225-
var observerSet = observers[what];
226-
if (observerSet == null) {
227-
observerSet = new HashSet<>();
228-
observers[what] = observerSet;
229-
}
230-
231-
observerSet.add(new ObserverAtSlot(who, how));
228+
Int2ObjectMap<IntSet> whatToHow = observers.computeIfAbsent(who, x -> new Int2ObjectOpenHashMap<>());
229+
IntSet hows = whatToHow.computeIfAbsent(what, x -> new IntOpenHashSet());
230+
hows.add(how);
232231
}
233232
}
234233

235234
@Override
236235
public void removeObserver(Observer who, int what, int how) {
237236
synchronized (observers) {
238-
var observerSet = observers[what];
239-
if (observerSet != null) {
240-
observerSet.remove(new ObserverAtSlot(who, how));
241-
if (observerSet.isEmpty())
242-
observers[what] = null;
237+
if (!observers.containsKey(who))
238+
return;
239+
240+
Int2ObjectMap<IntSet> whatToHow = observers.get(who);
241+
if (!whatToHow.containsKey(what))
242+
return;
243+
244+
IntSet hows = whatToHow.get(what);
245+
hows.remove(how);
246+
if (hows.isEmpty()) {
247+
whatToHow.remove(what);
248+
if (whatToHow.isEmpty()) {
249+
observers.remove(who);
250+
}
243251
}
244252
}
245253
}
@@ -252,14 +260,9 @@ public void removeObserver(Observer who, int what, int how) {
252260
public List<Window> getWindows() {
253261
var windows = new ArrayList<Window>();
254262
synchronized (observers) {
255-
for (var observerSet : observers) {
256-
if (observerSet == null)
257-
continue;
258-
for (var viewerAtSlot : observerSet) {
259-
if (!(viewerAtSlot.observer() instanceof Window w))
260-
continue;
263+
for (var entry : observers.keySet()) {
264+
if (entry instanceof Window w)
261265
windows.add(w);
262-
}
263266
}
264267
}
265268
return windows;
@@ -273,11 +276,12 @@ public List<Window> getWindows() {
273276
*/
274277
public void notifyWindows() {
275278
synchronized (observers) {
276-
for (var observerSet : observers) {
277-
if (observerSet == null)
278-
continue;
279-
for (var viewerAtSlot : observerSet) {
280-
viewerAtSlot.notifyUpdate();
279+
for (var entry : observers.entrySet()) {
280+
var observer = entry.getKey();
281+
for (var howSet : entry.getValue().values()) {
282+
for (int how : howSet) {
283+
observer.notifyUpdate(how);
284+
}
281285
}
282286
}
283287
}
@@ -293,10 +297,13 @@ public void notifyWindows() {
293297
*/
294298
public void notifyWindows(int slot) {
295299
synchronized (observers) {
296-
var observerSet = observers[slot];
297-
if (observerSet != null) {
298-
for (var viewerAtSlot : observerSet) {
299-
viewerAtSlot.notifyUpdate();
300+
for (var entry : observers.entrySet()) {
301+
var observer = entry.getKey();
302+
var whatToHow = entry.getValue();
303+
if (!whatToHow.containsKey(slot))
304+
continue;
305+
for (int how : whatToHow.get(slot)) {
306+
observer.notifyUpdate(how);
300307
}
301308
}
302309
}
@@ -1456,6 +1463,7 @@ public org.bukkit.inventory.Inventory asBukkitInventory() {
14561463

14571464
/**
14581465
* Gets the slot that is backing the given slot in this inventory.
1466+
*
14591467
* @param slot The slot number
14601468
* @return The backing {@link InventorySlot}
14611469
*/

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

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
import xyz.xenondevs.invui.util.TriConsumer;
1818

1919
import java.util.Arrays;
20-
import java.util.concurrent.TimeUnit;
20+
import java.util.HashMap;
21+
import java.util.Map;
2122
import java.util.function.BiFunction;
2223
import java.util.function.Function;
2324

@@ -37,7 +38,7 @@ public sealed class ReferencingInventory extends xyz.xenondevs.invui.inventory.I
3738
protected final TriConsumer<Inventory, Integer, @Nullable ItemStack> itemSetter;
3839
protected final int[] maxStackSizes;
3940

40-
private @Nullable ScheduledTask updateTask;
41+
private final Map<Observer, ScheduledTask> updateTasks = new HashMap<>();
4142

4243
/**
4344
* Constructs a new {@link ReferencingInventory}.
@@ -135,26 +136,34 @@ protected void setDirectBackingItem(int slot, @Nullable ItemStack itemStack) {
135136

136137
@Override
137138
public void addObserver(Observer who, int what, int how) {
138-
super.addObserver(who, what, how);
139-
if (updateTask == null) {
140-
updateTask = Bukkit.getAsyncScheduler().runAtFixedRate(
139+
synchronized (observers) {
140+
super.addObserver(who, what, how);
141+
updateTasks.computeIfAbsent(who, x -> who.getScheduler().runAtFixedRate(
141142
InvUI.getInstance().getPlugin(),
142-
x -> notifyWindows(),
143-
0,
144-
50,
145-
TimeUnit.MILLISECONDS
146-
);
143+
x1 -> notifyWindows(who),
144+
null,
145+
1,
146+
1
147+
));
147148
}
148149
}
149150

150151
@Override
151152
public void removeObserver(Observer who, int what, int how) {
152-
super.removeObserver(who, what, how);
153-
if (updateTask != null
154-
&& Arrays.stream(observers).allMatch(s -> s == null || s.isEmpty())
155-
) {
156-
updateTask.cancel();
157-
updateTask = null;
153+
synchronized (observers) {
154+
super.removeObserver(who, what, how);
155+
156+
ScheduledTask task;
157+
if (!observers.containsKey(who) && (task = updateTasks.remove(who)) != null)
158+
task.cancel();
159+
}
160+
}
161+
162+
private void notifyWindows(Observer who) {
163+
synchronized (observers) {
164+
if (!observers.containsKey(who))
165+
return;
166+
observers.get(who).forEach((what, hows) -> hows.forEach(who::notifyUpdate));
158167
}
159168
}
160169

invui/src/main/java/xyz/xenondevs/invui/item/CustomBoundItem.java

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package xyz.xenondevs.invui.item;
22

3-
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
43
import org.bukkit.Bukkit;
54
import org.bukkit.entity.Player;
65
import org.bukkit.event.inventory.ClickType;
@@ -18,7 +17,6 @@
1817

1918
import java.util.List;
2019
import java.util.concurrent.CompletableFuture;
21-
import java.util.concurrent.TimeUnit;
2220
import java.util.concurrent.atomic.AtomicReference;
2321
import java.util.function.*;
2422

@@ -30,8 +28,7 @@ class CustomBoundItem<G extends Gui> extends AbstractBoundItem {
3028
private volatile BiFunction<? super Player, ? super G, ? extends ItemProvider> itemProvider;
3129
private final BiConsumer<? super Item, ? super G> bindHandler;
3230
private final BiConsumer<? super Item, ? super G> unbindHandler;
33-
private final int updatePeriod;
34-
private @Nullable ScheduledTask updateTask;
31+
private final ObserverHolder observers;
3532

3633
public CustomBoundItem(
3734
Class<? extends Gui> guiClass,
@@ -48,7 +45,7 @@ public CustomBoundItem(
4845
this.clickHandler = clickHandler;
4946
this.selectHandler = selectHandler;
5047
this.itemProvider = itemProvider;
51-
this.updatePeriod = updatePeriod;
48+
this.observers = updatePeriod > 0 ? new ObserverHolder.Ticking(updatePeriod) : new ObserverHolder.NonTicking();
5249
}
5350

5451
@Override
@@ -89,25 +86,17 @@ public void handleBundleSelect(Player player, int bundleSlot) {
8986

9087
@Override
9188
public void addObserver(Observer who, int what, int how) {
92-
super.addObserver(who, what, how);
93-
if (updatePeriod > 0 && updateTask == null) {
94-
updateTask = Bukkit.getAsyncScheduler().runAtFixedRate(
95-
InvUI.getInstance().getPlugin(),
96-
x -> notifyWindows(),
97-
0,
98-
updatePeriod * 50L,
99-
TimeUnit.MILLISECONDS
100-
);
101-
}
89+
observers.addObserver(who, how);
10290
}
10391

10492
@Override
10593
public void removeObserver(Observer who, int what, int how) {
106-
super.removeObserver(who, what, how);
107-
if (updateTask != null && observers.isEmpty()) {
108-
updateTask.cancel();
109-
updateTask = null;
110-
}
94+
observers.removeObserver(who, how);
95+
}
96+
97+
@Override
98+
public void notifyWindows() {
99+
observers.notifyWindows();
111100
}
112101

113102
non-sealed static class Builder<G extends Gui> implements BoundItem.Builder<G> {

invui/src/main/java/xyz/xenondevs/invui/item/CustomItem.java

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package xyz.xenondevs.invui.item;
22

3-
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
43
import org.bukkit.Bukkit;
54
import org.bukkit.entity.Player;
65
import org.bukkit.event.inventory.ClickType;
@@ -12,19 +11,17 @@
1211

1312
import java.util.List;
1413
import java.util.concurrent.CompletableFuture;
15-
import java.util.concurrent.TimeUnit;
1614
import java.util.function.BiConsumer;
1715
import java.util.function.Consumer;
1816
import java.util.function.Function;
1917
import java.util.function.Supplier;
2018

21-
class CustomItem extends AbstractItem {
19+
class CustomItem implements Item {
2220

2321
private final BiConsumer<? super Item, ? super Click> clickHandler;
2422
private final TriConsumer<? super Item, ? super Player, ? super Integer> selectHandler;
2523
private volatile Function<? super Player, ? extends ItemProvider> itemProvider;
26-
private final int updatePeriod;
27-
private @Nullable ScheduledTask updateTask;
24+
private final ObserverHolder observers;
2825

2926
public CustomItem(
3027
BiConsumer<? super Item, ? super Click> clickHandler,
@@ -35,7 +32,7 @@ public CustomItem(
3532
this.clickHandler = clickHandler;
3633
this.selectHandler = selectHandler;
3734
this.itemProvider = itemProvider;
38-
this.updatePeriod = updatePeriod;
35+
this.observers = updatePeriod > 0 ? new ObserverHolder.Ticking(updatePeriod) : new ObserverHolder.NonTicking();
3936
}
4037

4138
@Override
@@ -55,25 +52,17 @@ public void handleBundleSelect(Player player, int bundleSlot) {
5552

5653
@Override
5754
public void addObserver(Observer who, int what, int how) {
58-
super.addObserver(who, what, how);
59-
if (updatePeriod > 0 && updateTask == null) {
60-
updateTask = Bukkit.getAsyncScheduler().runAtFixedRate(
61-
InvUI.getInstance().getPlugin(),
62-
x -> notifyWindows(),
63-
0,
64-
updatePeriod * 50L,
65-
TimeUnit.MILLISECONDS
66-
);
67-
}
55+
observers.addObserver(who, how);
6856
}
6957

7058
@Override
7159
public void removeObserver(Observer who, int what, int how) {
72-
super.removeObserver(who, what, how);
73-
if (updateTask != null && observers.isEmpty()) {
74-
updateTask.cancel();
75-
updateTask = null;
76-
}
60+
observers.removeObserver(who, how);
61+
}
62+
63+
@Override
64+
public void notifyWindows() {
65+
observers.notifyWindows();
7766
}
7867

7968
static final class Builder implements Item.Builder<Builder> {

0 commit comments

Comments
 (0)