Skip to content

Commit 9217707

Browse files
committed
发信器采用watcher系统避免tick轮询
1 parent 66277db commit 9217707

6 files changed

Lines changed: 256 additions & 32 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ org.gradle.jvmargs=-Xmx6G -XX:MaxMetaspaceSize=1G -Dfile.encoding=UTF-8
66

77
# mod version info
88
mod_id=frostedheart
9-
mod_version = 0.7.4
9+
mod_version = 0.7.6
1010
minecraft_version = 1.20.1
1111
forge_version = 47.3.0
1212

src/main/java/com/teammoeg/frostedheart/content/town/buildings/warehouse/WarehouseLevelEmitterBlockEntity.java

Lines changed: 110 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@
2929
import com.teammoeg.frostedheart.content.town.ITownWithResources;
3030
import com.teammoeg.frostedheart.content.town.building.AbstractTownBuilding;
3131
import com.teammoeg.frostedheart.content.town.provider.ITownProviderSerializable;
32+
import com.teammoeg.frostedheart.content.town.resource.TeamTownResourceActionExecutorHandler;
33+
import com.teammoeg.frostedheart.content.town.resource.TeamTownResourceHolder;
3234
import com.teammoeg.frostedheart.content.town.resource.action.TownResourceActions;
35+
import com.teammoeg.frostedheart.content.town.resource.watcher.IWarehouseStockWatcher;
36+
import com.teammoeg.frostedheart.content.town.resource.watcher.IWarehouseStockWatcherNode;
3337
import net.minecraft.core.BlockPos;
3438
import net.minecraft.core.Direction;
3539
import net.minecraft.nbt.CompoundTag;
@@ -62,12 +66,11 @@
6266
* warehouse interface's redstone control mode it enables level-emitter-controlled
6367
* warehouse output.
6468
*/
65-
public class WarehouseLevelEmitterBlockEntity extends CBlockEntity implements CTickableBlockEntity, MenuProvider {
69+
public class WarehouseLevelEmitterBlockEntity extends CBlockEntity implements IWarehouseStockWatcherNode, MenuProvider {
6670
public static final int STATUS_UNBOUND = 0;
6771
public static final int STATUS_UNAVAILABLE = 1;
6872
public static final int STATUS_WORKING = 2;
6973

70-
private final LazyTickWorker refreshWorker = new LazyTickWorker(10, this::refreshState);
7174
@Nullable
7275
private SimpleItemKey filter;
7376
private int threshold = 1;
@@ -78,11 +81,16 @@ public class WarehouseLevelEmitterBlockEntity extends CBlockEntity implements CT
7881
private ITownProviderSerializable<? extends ITownWithBuildings> townProvider;
7982
private BlockPos warehousePos;
8083
private int connectionStatus = STATUS_UNBOUND;
84+
// 由资源持有者分配的 watcher,用于精准订阅物品数量变化
85+
@Nullable
86+
private IWarehouseStockWatcher watcher;
8187

88+
//构造器
8289
public WarehouseLevelEmitterBlockEntity(BlockPos pos, BlockState state) {
8390
super(FHBlockEntityTypes.WAREHOUSE_LEVEL_EMITTER.get(), pos, state);
8491
}
8592

93+
// --- getters ---
8694
@Nullable
8795
public SimpleItemKey getFilter() {
8896
return filter;
@@ -108,16 +116,44 @@ public int getConnectionStatus() {
108116
return connectionStatus;
109117
}
110118

119+
120+
// ---------- IWarehouseStockWatcherNode 实现 ----------
121+
111122
public void setFilter(@Nullable SimpleItemKey newFilter) {
123+
SimpleItemKey oldFilter = this.filter;
112124
this.filter = newFilter;
113125
if (level != null) {
114126
setChanged();
115127
if (!level.isClientSide) {
116-
refreshWorker.enqueue();
128+
if (watcher != null) {
129+
configureWatcher(); // 自动重新 add/remove
130+
}
131+
refreshState(); // 立即查一次库存更新红石
117132
}
118133
}
119134
}
120135

136+
@Override
137+
public void updateWatcher(IWarehouseStockWatcher newWatcher) {
138+
this.watcher = newWatcher;
139+
configureWatcher();
140+
}
141+
142+
@Override
143+
public void onStockChange(SimpleItemKey item, long newAmount) {
144+
if (level == null || level.isClientSide) return;
145+
lastKnownStock = newAmount;
146+
boolean on = (mode == WarehouseRedstoneMode.LOW_SIGNAL) == (newAmount < threshold);
147+
setEmitterOn(on, newAmount);
148+
}
149+
150+
private void configureWatcher() {
151+
if (watcher == null) return;
152+
watcher.reset();
153+
if (filter != null) {
154+
watcher.addWatch(filter);
155+
}
156+
}
121157
public void setFilterFromStack(ItemStack stack) {
122158
if (!stack.isEmpty()) {
123159
setFilter(SimpleItemKey.from(stack));
@@ -128,22 +164,20 @@ public void setThreshold(int newThreshold) {
128164
this.threshold = Math.max(1, newThreshold);
129165
if (level != null) {
130166
setChanged();
131-
if (!level.isClientSide) {
132-
refreshWorker.enqueue();
133-
}
167+
if (!level.isClientSide) refreshState();
134168
}
135169
}
136170

137171
public void cycleMode() {
138172
this.mode = this.mode.nextEmitterMode();
139173
if (level != null) {
140174
setChanged();
141-
if (!level.isClientSide) {
142-
refreshWorker.enqueue();
143-
}
175+
if (!level.isClientSide) refreshState();
144176
}
145177
}
146178

179+
// ---------- 绑定管理 ----------
180+
147181
/**
148182
* Claims this emitter for a warehouse. A still-valid binding owned by a
149183
* different warehouse is never stolen.
@@ -157,16 +191,22 @@ public boolean tryBind(ITownProviderSerializable<? extends ITownWithBuildings> p
157191
return true;
158192
}
159193
if (warehousePos != null && resolveBinding(false).isPresent()) {
194+
// 旧绑定仍有效,不抢夺
160195
return false;
161196
}
162197

198+
// 清理旧绑定(包括旧的 watcher)
199+
clearBinding();
200+
163201
this.townProvider = provider;
164202
this.warehousePos = newWarehousePos.immutable();
165203
this.connectionStatus = STATUS_UNAVAILABLE;
166204
if (level != null) {
167205
setChanged();
168206
}
169-
refreshWorker.enqueue();
207+
208+
// 获取新的 watcher 并配置
209+
refreshWatcherAndState();
170210
return true;
171211
}
172212

@@ -215,6 +255,12 @@ private Optional<BindingContext> invalidBinding(boolean clearWhenInvalid) {
215255
}
216256

217257
private void clearBinding() {
258+
// 释放 watcher,它会自动从资源持有者的索引中清除
259+
if (watcher != null) {
260+
watcher.reset();
261+
watcher = null;
262+
}
263+
218264
boolean changed = townProvider != null || warehousePos != null;
219265
townProvider = null;
220266
warehousePos = null;
@@ -225,26 +271,48 @@ private void clearBinding() {
225271
}
226272

227273
/**
228-
* 查询城镇库存并刷新输出状态。状态翻转时通知相邻方块及正面方块的邻居,
229-
* 与 AE2 发信器的邻居通知行为一致。
230-
* <p>
231-
* Polls the town stock and refreshes the output state. On a state flip, neighbors
232-
* of this block and of the block in front are notified, like the AE2 level emitter.
274+
* 与仓库建立新的 Watcher 订阅,并且刷新一次当前库存状态。
233275
*/
234-
private void refreshState() {
235-
if (level == null || level.isClientSide) {
276+
private void refreshWatcherAndState() {
277+
if (level == null || level.isClientSide) return;
278+
279+
Optional<BindingContext> binding = resolveBinding(false);
280+
if (binding.isEmpty()) {
281+
connectionStatus = STATUS_UNBOUND;
282+
setEmitterOn(false, 0);
283+
return;
284+
}
285+
286+
BindingContext ctx = binding.get();
287+
if (!(ctx.town() instanceof ITownWithResources resourceTown)
288+
|| !ctx.warehouse().isBuildingWorkable()) {
289+
connectionStatus = STATUS_UNAVAILABLE;
290+
setEmitterOn(false, 0);
236291
return;
237292
}
293+
294+
TeamTownResourceHolder holder = ((TeamTownResourceActionExecutorHandler) resourceTown.getActionExecutorHandler()).resourceHolder;
295+
this.watcher = holder.createWatcher(this); // 会回调 updateWatcher
296+
297+
connectionStatus = STATUS_WORKING;
298+
refreshState(); // 主动拉取一次当前库存
299+
}
300+
301+
// ---------- 状态刷新(仅用于配置变更或主动查询) ----------
302+
303+
private void refreshState() {
304+
if (level == null || level.isClientSide) return;
305+
238306
Optional<BindingContext> binding = resolveBinding(true);
239307
if (binding.isEmpty()) {
240308
connectionStatus = STATUS_UNBOUND;
241309
setEmitterOn(false, 0);
242310
return;
243311
}
244312

245-
BindingContext context = binding.get();
246-
if (!(context.town() instanceof ITownWithResources resourceTown)
247-
|| !context.warehouse().isBuildingWorkable()) {
313+
BindingContext ctx = binding.get();
314+
if (!(ctx.town() instanceof ITownWithResources resourceTown)
315+
|| !ctx.warehouse().isBuildingWorkable()) {
248316
connectionStatus = STATUS_UNAVAILABLE;
249317
setEmitterOn(false, 0);
250318
return;
@@ -257,8 +325,7 @@ private void refreshState() {
257325
}
258326

259327
long stock = (long) TownResourceActions.get(resourceTown.getActionExecutorHandler(), filter.toStack(1));
260-
// 与 AE2 发信器一致的判定:HIGH_SIGNAL 在存量 >= 阈值时开启,LOW_SIGNAL 在存量 < 阈值时开启。
261-
boolean on = mode == WarehouseRedstoneMode.LOW_SIGNAL ? stock < threshold : stock >= threshold;
328+
boolean on = (mode == WarehouseRedstoneMode.LOW_SIGNAL) == (stock < threshold);
262329
setEmitterOn(on, stock);
263330
}
264331

@@ -276,29 +343,42 @@ private void setEmitterOn(boolean on, long stock) {
276343
}
277344
}
278345

279-
@Override
280-
public void tick() {
281-
if (level != null && !level.isClientSide) {
282-
refreshWorker.tick();
283-
}
284-
}
346+
// ---------- 生命周期 ----------
285347

286348
@Override
287349
public void onLoad() {
288350
super.onLoad();
289351
if (level != null && !level.isClientSide) {
290-
refreshWorker.enqueue();
352+
// 方块加载时(无论是首次放置还是 chunk 重载),重新连接 watcher
353+
// 城镇资源常驻,Watcher 机制会自动同步最新库存
354+
refreshWatcherAndState();
291355
}
292356
}
293357

294358
@Override
295359
public void onRemoved() {
296360
if (level != null && !level.isClientSide) {
297-
resolveBinding(false).ifPresent(context -> context.warehouse().removeEmitter(worldPosition));
361+
// 只有方块真正被破坏(不再是发信器)才清理 watcher 和绑定
362+
if (!(level.getBlockState(worldPosition).getBlock() instanceof WarehouseLevelEmitterBlock)) {
363+
// 释放 watcher(自动从索引清理)
364+
if (watcher != null) {
365+
watcher.reset();
366+
watcher = null;
367+
}
368+
// 从仓库注销
369+
resolveBinding(false).ifPresent(context -> context.warehouse().removeEmitter(worldPosition));
370+
// 清空绑定信息
371+
townProvider = null;
372+
warehousePos = null;
373+
connectionStatus = STATUS_UNBOUND;
374+
}
298375
}
299376
super.onRemoved();
300377
}
301378

379+
380+
// ---------- 序列化 ----------
381+
302382
@Override
303383
public void readCustomNBT(CompoundTag nbt, boolean descPacket) {
304384
filter = null;
@@ -321,7 +401,6 @@ public void readCustomNBT(CompoundTag nbt, boolean descPacket) {
321401
ITownProviderSerializable<? extends ITown> rawProvider =
322402
ITownProviderSerializable.fromNBT(nbt.getCompound("townProvider"));
323403
if (rawProvider != null && ITownWithBuildings.class.isAssignableFrom(rawProvider.getTownType())) {
324-
// The runtime type check above guarantees this provider supplies a town with buildings.
325404
townProvider = castTownProvider(rawProvider);
326405
warehousePos = BlockPos.of(nbt.getLong("warehousePos"));
327406
}

src/main/java/com/teammoeg/frostedheart/content/town/resource/TeamTownResourceHolder.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
import com.teammoeg.frostedheart.content.town.buildings.warehouse.SimpleItemKey;
2727
import com.teammoeg.frostedheart.content.town.event.ITownResourceChangeEventListener;
2828
import com.teammoeg.frostedheart.content.town.event.TownResourceChangeEvent;
29+
import com.teammoeg.frostedheart.content.town.resource.watcher.IWarehouseStockWatcher;
30+
import com.teammoeg.frostedheart.content.town.resource.watcher.IWarehouseStockWatcherNode;
31+
import com.teammoeg.frostedheart.content.town.resource.watcher.WarehouseStockWatcher;
2932
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
3033
import lombok.AccessLevel;
3134
import lombok.Setter;
@@ -77,6 +80,9 @@ public class TeamTownResourceHolder {
7780
@Getter(AccessLevel.NONE)
7881
@Setter
7982
private transient ITownResourceChangeEventListener changeListener;
83+
public final Map<SimpleItemKey, Set<WarehouseStockWatcher>> exactIndex = new HashMap<>();
84+
// 全监听 Watcher 集合
85+
public final Set<WarehouseStockWatcher> watchAllWatchers = new HashSet<>();
8086

8187
/**
8288
* 缓存ItemResourceAttribute对应的物品。
@@ -371,6 +377,7 @@ private void addSigned(ITownResourceKey townResourceKey, double amount){
371377
this.occupiedCapacity += amount;
372378
}
373379
}
380+
notifyStockWatchers(townResourceKey);
374381
if(amount != 0 && this.changeListener != null){
375382
this.changeListener.onResourceChange(new TownResourceChangeEvent(this, townResourceKey));
376383
}
@@ -545,4 +552,31 @@ public Map<SimpleItemKey, Long> getVirtualItemMap() {
545552
return this.cachedVirtualItems;
546553
}
547554

555+
/**
556+
* 为指定 Node 创建一个新的 Watcher,并立即通过 updateWatcher 交还给 Node。
557+
* Node 应保存返回的引用,用于后续配置。
558+
*/
559+
public IWarehouseStockWatcher createWatcher(IWarehouseStockWatcherNode node) {
560+
WarehouseStockWatcher watcher = new WarehouseStockWatcher(node, this);
561+
node.updateWatcher(watcher);
562+
return watcher;
563+
}
564+
565+
// 资源变化时通知
566+
private void notifyStockWatchers(ITownResourceKey townResourceKey) {
567+
if (!(townResourceKey instanceof ItemStackResourceKey itemKey)) return;
568+
SimpleItemKey simpleKey = SimpleItemKey.from(itemKey);
569+
long newAmount = (long) get(itemKey);
570+
571+
Set<WarehouseStockWatcher> exact = exactIndex.get(simpleKey);
572+
if (exact != null) {
573+
for (WarehouseStockWatcher w : exact) {
574+
w.getNode().onStockChange(simpleKey, newAmount);
575+
}
576+
}
577+
for (WarehouseStockWatcher w : watchAllWatchers) {
578+
w.getNode().onStockChange(simpleKey, newAmount);
579+
}
580+
}
581+
548582
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.teammoeg.frostedheart.content.town.resource.watcher;
2+
3+
import com.teammoeg.frostedheart.content.town.buildings.warehouse.SimpleItemKey;
4+
5+
public interface IWarehouseStockWatcher {
6+
/** 清空所有监听配置,准备重新设置。 */
7+
void reset();
8+
9+
/** 添加一个要监听的物品。 */
10+
void addWatch(SimpleItemKey item);
11+
12+
/** 移除一个物品的监听。 */
13+
void removeWatch(SimpleItemKey item);
14+
15+
/** 设置是否监听所有物品(模糊模式)。 */
16+
void setWatchAll(boolean watchAll);
17+
18+
/** 是否处于全监听模式。 */
19+
boolean isWatchAll();
20+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.teammoeg.frostedheart.content.town.resource.watcher;
2+
3+
import com.teammoeg.frostedheart.content.town.buildings.warehouse.SimpleItemKey;
4+
5+
public interface IWarehouseStockWatcherNode {
6+
/**
7+
* 当资源持有者分配/更新 Watcher 时调用。
8+
* 宿主应保存该 Watcher 引用,并立即通过它配置监听的物品。
9+
*/
10+
void updateWatcher(IWarehouseStockWatcher newWatcher);
11+
12+
/**
13+
* 当监听的物品库存发生变化时回调。
14+
* @param itemKey 变化的物品(精确键)
15+
* @param newAmount 该物品在仓库中的最新总量
16+
*/
17+
void onStockChange(SimpleItemKey itemKey, long newAmount);
18+
}

0 commit comments

Comments
 (0)