From 9410d025d7a537567733622d7c6b9267312c34c9 Mon Sep 17 00:00:00 2001 From: "Natanael [Root]" Date: Thu, 25 Jun 2026 20:06:01 +0200 Subject: [PATCH] server/world: Add a cancellable neighbour update event Blocks react to a neighbouring block changing through NeighbourUpdateTick, but a Handler had no way to observe or veto this, unlike the redstone update event. Add HandleNeighbourUpdate, fired before a queued neighbour update is dispatched to the block (and any liquid) at the position; cancelling it skips the update, mirroring HandleRedstoneUpdate. The event is only fired for positions whose block or liquid actually handles neighbour updates, so inert blocks add no overhead. Closes #1002. Co-Authored-By: Claude Opus 4.8 --- server/world/handler.go | 6 ++++++ server/world/tick.go | 21 +++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/server/world/handler.go b/server/world/handler.go index bdd248ae1..9cf14f75a 100644 --- a/server/world/handler.go +++ b/server/world/handler.go @@ -66,6 +66,11 @@ type Handler interface { // HandleRedstoneUpdate handles a redstone update at a position. ctx.Cancel() may be called // to cancel the redstone update. HandleRedstoneUpdate(ctx *Context, pos cube.Pos) + // HandleNeighbourUpdate handles a block at pos being updated because of a change to a + // neighbouring block at changedNeighbour. It is only called for a block or liquid that + // reacts to the change, that is, one implementing NeighbourUpdateTicker. ctx.Cancel() may + // be called to prevent both the block and any liquid at the position from being updated. + HandleNeighbourUpdate(ctx *Context, pos, changedNeighbour cube.Pos) // HandleClose handles the World being closed. HandleClose may be used as a // moment to finish code running on other goroutines that operates on the // World specifically. HandleClose is called directly before the World stops @@ -93,4 +98,5 @@ func (NopHandler) HandleEntitySpawn(*Tx, Entity) func (NopHandler) HandleEntityDespawn(*Tx, Entity) {} func (NopHandler) HandleExplosion(*Context, mgl64.Vec3, *[]Entity, *[]cube.Pos, *float64, *bool) {} func (NopHandler) HandleRedstoneUpdate(*Context, cube.Pos) {} +func (NopHandler) HandleNeighbourUpdate(*Context, cube.Pos, cube.Pos) {} func (NopHandler) HandleClose(*Tx) {} diff --git a/server/world/tick.go b/server/world/tick.go index 1088a1156..f35366a24 100644 --- a/server/world/tick.go +++ b/server/world/tick.go @@ -7,6 +7,7 @@ import ( "time" "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/event" "github.com/df-mc/dragonfly/server/internal/sliceutil" ) @@ -102,13 +103,21 @@ func (t ticker) performNeighbourUpdates(tx *Tx) { for _, update := range updates { pos, changedNeighbour := update.pos, update.neighbour - if ticker, ok := tx.Block(pos).(NeighbourUpdateTicker); ok { - ticker.NeighbourUpdateTick(pos, changedNeighbour, tx) + blockTicker, blockOk := tx.Block(pos).(NeighbourUpdateTicker) + liquid, _ := tx.World().additionalLiquid(pos) + liquidTicker, liquidOk := liquid.(NeighbourUpdateTicker) + if !blockOk && !liquidOk { + continue } - if liquid, ok := tx.World().additionalLiquid(pos); ok { - if ticker, ok := liquid.(NeighbourUpdateTicker); ok { - ticker.NeighbourUpdateTick(pos, changedNeighbour, tx) - } + ctx := event.C(tx) + if tx.World().Handler().HandleNeighbourUpdate(ctx, pos, changedNeighbour); ctx.Cancelled() { + continue + } + if blockOk { + blockTicker.NeighbourUpdateTick(pos, changedNeighbour, tx) + } + if liquidOk { + liquidTicker.NeighbourUpdateTick(pos, changedNeighbour, tx) } } }