33import appeng .api .config .AccessRestriction ;
44import appeng .api .config .Actionable ;
55import appeng .api .networking .security .IActionSource ;
6+ import appeng .api .networking .ticking .TickRateModulation ;
67import appeng .api .storage .IMEInventoryHandler ;
78import appeng .api .storage .IMEMonitor ;
89import appeng .api .storage .IMEMonitorHandlerReceiver ;
910import appeng .api .storage .IStorageChannel ;
1011import appeng .api .storage .data .IAEStack ;
1112import appeng .api .storage .data .IItemList ;
13+ import appeng .me .storage .ITickingMonitor ;
1214
1315import java .util .*;
1416
1517/**
1618 * Generic IMEMonitor implementation wrapping an IMEInventoryHandler.
17- * Manages change listeners for AE2 network notifications.
18- * Supports detecting external inventory changes via {@link #forceUpdate()}.
19+ * Implements ITickingMonitor so AE2 storage bus can poll for external changes
20+ * (e.g. hopper, pipe, player interaction) via periodic onTick() calls.
21+ * Pattern follows AE2's ItemHandlerAdapter + InventoryCache approach.
1922 */
20- public class DrawerMEMonitor <T extends IAEStack <T >> implements IMEMonitor <T > {
23+ public class DrawerMEMonitor <T extends IAEStack <T >> implements IMEMonitor <T >, ITickingMonitor {
2124
2225 private final IMEInventoryHandler <T > handler ;
2326 private final IStorageChannel <T > channel ;
2427 private final Map <IMEMonitorHandlerReceiver <T >, Object > listeners = new HashMap <>();
28+ private IActionSource mySource ;
2529 private IItemList <T > cachedList ;
26- private boolean suppressExternalNotify = false ;
2730
2831 public DrawerMEMonitor (IMEInventoryHandler <T > handler , IStorageChannel <T > channel ) {
2932 this .handler = handler ;
3033 this .channel = channel ;
34+ this .cachedList = channel .createList ();
35+ handler .getAvailableItems (this .cachedList );
3136 }
3237
38+ // ---- ITickingMonitor ----
39+
40+ @ Override
41+ public TickRateModulation onTick () {
42+ IItemList <T > currentList = channel .createList ();
43+ handler .getAvailableItems (currentList );
44+
45+ // Compute diff: negate cached, add current, collect non-zero
46+ for (T cached : cachedList ) {
47+ cached .setStackSize (-cached .getStackSize ());
48+ }
49+ for (T current : currentList ) {
50+ cachedList .add (current );
51+ }
52+
53+ List <T > changes = new ArrayList <>();
54+ for (T entry : cachedList ) {
55+ if (entry .getStackSize () != 0 ) {
56+ changes .add (entry );
57+ }
58+ }
59+
60+ cachedList = currentList ;
61+
62+ if (!changes .isEmpty ()) {
63+ postDifference (changes );
64+ return TickRateModulation .URGENT ;
65+ }
66+ return TickRateModulation .SLOWER ;
67+ }
68+
69+ @ Override
70+ public void setActionSource (IActionSource source ) {
71+ this .mySource = source ;
72+ }
73+
74+ // ---- IMEInventory ----
75+
3376 @ Override
3477 public T injectItems (T input , Actionable type , IActionSource src ) {
35- suppressExternalNotify = true ;
3678 T result = handler .injectItems (input , type , src );
37- suppressExternalNotify = false ;
3879 if (type == Actionable .MODULATE ) {
3980 long injected = input .getStackSize () - (result != null ? result .getStackSize () : 0 );
4081 if (injected > 0 ) {
4182 T diff = input .copy ();
4283 diff .setStackSize (injected );
43- notifyListeners (diff , src );
84+ cachedList .add (diff );
85+ postDifference (Collections .singletonList (diff ));
4486 }
45- refreshCache ();
4687 }
4788 return result ;
4889 }
4990
5091 @ Override
5192 public T extractItems (T request , Actionable mode , IActionSource src ) {
52- suppressExternalNotify = true ;
5393 T result = handler .extractItems (request , mode , src );
54- suppressExternalNotify = false ;
5594 if (mode == Actionable .MODULATE && result != null ) {
5695 T diff = result .copy ();
5796 diff .setStackSize (-result .getStackSize ());
58- notifyListeners (diff , src );
59- refreshCache ( );
97+ cachedList . add (diff );
98+ postDifference ( Collections . singletonList ( diff ) );
6099 }
61100 return result ;
62101 }
63102
64- /**
65- * Detect external inventory changes (e.g. hopper, pipe, player interaction)
66- * by comparing current state against cached snapshot.
67- * Called from the tile entity when the underlying inventory changes.
68- */
69- public void forceUpdate () {
70- if (suppressExternalNotify || listeners .isEmpty ()) return ;
71-
72- IItemList <T > currentList = channel .createList ();
73- handler .getAvailableItems (currentList );
74-
75- if (cachedList == null ) {
76- cachedList = currentList ;
77- return ;
78- }
79-
80- List <T > changes = new ArrayList <>();
81-
82- // Find increases and new items
83- for (T current : currentList ) {
84- T old = cachedList .findPrecise (current );
85- long oldSize = old != null ? old .getStackSize () : 0 ;
86- long diff = current .getStackSize () - oldSize ;
87- if (diff != 0 ) {
88- T change = current .copy ();
89- change .setStackSize (diff );
90- changes .add (change );
91- }
92- }
93-
94- // Find removed items
95- for (T old : cachedList ) {
96- T current = currentList .findPrecise (old );
97- if (current == null ) {
98- T change = old .copy ();
99- change .setStackSize (-old .getStackSize ());
100- changes .add (change );
101- }
102- }
103-
104- cachedList = currentList ;
105-
106- if (!changes .isEmpty ()) {
107- postChanges (changes , null );
108- }
109- }
110-
111- private void refreshCache () {
112- cachedList = channel .createList ();
113- handler .getAvailableItems (cachedList );
114- }
115-
116103 @ Override
117104 public IItemList <T > getAvailableItems (IItemList <T > out ) {
118105 return handler .getAvailableItems (out );
@@ -121,16 +108,16 @@ public IItemList<T> getAvailableItems(IItemList<T> out) {
121108 @ Override
122109 public IItemList <T > getStorageList () {
123110 IItemList <T > list = channel .createList ();
124- getAvailableItems (list );
125- refreshCache ();
126- return list ;
111+ return getAvailableItems (list );
127112 }
128113
129114 @ Override
130115 public IStorageChannel <T > getChannel () {
131116 return channel ;
132117 }
133118
119+ // ---- IMEInventoryHandler delegated ----
120+
134121 @ Override
135122 public AccessRestriction getAccess () {
136123 return handler .getAccess ();
@@ -161,6 +148,8 @@ public boolean validForPass(int pass) {
161148 return handler .validForPass (pass );
162149 }
163150
151+ // ---- IBaseMonitor ----
152+
164153 @ Override
165154 public void addListener (IMEMonitorHandlerReceiver <T > l , Object verificationToken ) {
166155 listeners .put (l , verificationToken );
@@ -171,18 +160,15 @@ public void removeListener(IMEMonitorHandlerReceiver<T> l) {
171160 listeners .remove (l );
172161 }
173162
174- private void notifyListeners (T changed , IActionSource src ) {
175- if (changed == null ) return ;
176- postChanges (Collections .singletonList (changed ), src );
177- }
163+ // ---- Internal ----
178164
179- private void postChanges (Iterable <T > changes , IActionSource src ) {
165+ private void postDifference (Iterable <T > changes ) {
180166 Iterator <Map .Entry <IMEMonitorHandlerReceiver <T >, Object >> it = listeners .entrySet ().iterator ();
181167 while (it .hasNext ()) {
182168 Map .Entry <IMEMonitorHandlerReceiver <T >, Object > entry = it .next ();
183169 IMEMonitorHandlerReceiver <T > receiver = entry .getKey ();
184170 if (receiver .isValid (entry .getValue ())) {
185- receiver .postChange (this , changes , src );
171+ receiver .postChange (this , changes , mySource );
186172 } else {
187173 it .remove ();
188174 }
0 commit comments