166 lines
5.2 KiB
Go
166 lines
5.2 KiB
Go
|
|
package redstone
|
||
|
|
|
||
|
|
import (
|
||
|
|
"slices"
|
||
|
|
|
||
|
|
"github.com/df-mc/dragonfly/server/block/cube"
|
||
|
|
)
|
||
|
|
|
||
|
|
const (
|
||
|
|
// torchBurnoutThreshold is the maximum number of state changes allowed before burnout occurs.
|
||
|
|
torchBurnoutThreshold = 8
|
||
|
|
// torchBurnoutWindowTicks is the time window during which state changes are counted.
|
||
|
|
torchBurnoutWindowTicks = 60
|
||
|
|
)
|
||
|
|
|
||
|
|
// State holds transient redstone runtime state owned by a world.
|
||
|
|
type State struct {
|
||
|
|
torchBurnout map[cube.Pos]torchBurnout
|
||
|
|
activeTorchUpdates map[cube.Pos]int
|
||
|
|
updateSources []cube.Pos
|
||
|
|
}
|
||
|
|
|
||
|
|
// torchBurnout holds the burnout state and state change history for a redstone torch.
|
||
|
|
type torchBurnout struct {
|
||
|
|
expirationTicks []int64
|
||
|
|
burnedOut bool
|
||
|
|
// selfTriggered is set when the next scheduled toggle was caused by this torch's own outgoing propagation.
|
||
|
|
selfTriggered bool
|
||
|
|
}
|
||
|
|
|
||
|
|
// TorchBurnoutStatus returns the current transient burnout state for a redstone torch. Expired state-change
|
||
|
|
// history is pruned before the state is returned.
|
||
|
|
func (s *State) TorchBurnoutStatus(pos cube.Pos, currentTick int64) (burnedOut, recoverable bool) {
|
||
|
|
data, ok := s.pruneTorchBurnoutData(pos, currentTick)
|
||
|
|
if !ok {
|
||
|
|
return false, false
|
||
|
|
}
|
||
|
|
return data.burnedOut, len(data.expirationTicks) < torchBurnoutThreshold
|
||
|
|
}
|
||
|
|
|
||
|
|
// PruneTorchBurnout removes idle redstone torch burnout state once all tracked state changes have expired.
|
||
|
|
func (s *State) PruneTorchBurnout(pos cube.Pos, currentTick int64) {
|
||
|
|
data, ok := s.torchBurnout[pos]
|
||
|
|
if !ok {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
remaining := data.removeExpired(currentTick)
|
||
|
|
data.selfTriggered = false
|
||
|
|
if remaining == 0 && !data.burnedOut {
|
||
|
|
s.ClearTorchBurnout(pos)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.torchBurnout[pos] = data
|
||
|
|
}
|
||
|
|
|
||
|
|
// RecordTorchToggle records a self-triggered redstone torch toggle and reports whether it should burn out.
|
||
|
|
func (s *State) RecordTorchToggle(pos cube.Pos, currentTick int64) (burnsOut bool) {
|
||
|
|
data, ok := s.torchBurnout[pos]
|
||
|
|
if !ok {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
data.removeExpired(currentTick)
|
||
|
|
if data.selfTriggered {
|
||
|
|
data.expirationTicks = append(data.expirationTicks, currentTick+torchBurnoutWindowTicks)
|
||
|
|
}
|
||
|
|
data.selfTriggered = false
|
||
|
|
if len(data.expirationTicks) == 0 && !data.burnedOut {
|
||
|
|
s.ClearTorchBurnout(pos)
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
s.torchBurnout[pos] = data
|
||
|
|
return len(data.expirationTicks) >= torchBurnoutThreshold
|
||
|
|
}
|
||
|
|
|
||
|
|
// BurnOutTorch marks a redstone torch as burned out until a later redstone update recovers it.
|
||
|
|
func (s *State) BurnOutTorch(pos cube.Pos) {
|
||
|
|
data := s.torchBurnoutData(pos)
|
||
|
|
data.burnedOut = true
|
||
|
|
data.selfTriggered = false
|
||
|
|
s.torchBurnout[pos] = data
|
||
|
|
}
|
||
|
|
|
||
|
|
// ClearTorchBurnout removes transient burnout state for a redstone torch.
|
||
|
|
func (s *State) ClearTorchBurnout(pos cube.Pos) {
|
||
|
|
delete(s.torchBurnout, pos)
|
||
|
|
}
|
||
|
|
|
||
|
|
// WithActiveTorchUpdate marks redstone propagation as originating from the redstone torch at pos for the duration of fn.
|
||
|
|
func (s *State) WithActiveTorchUpdate(pos cube.Pos, fn func()) {
|
||
|
|
if s.activeTorchUpdates == nil {
|
||
|
|
s.activeTorchUpdates = make(map[cube.Pos]int)
|
||
|
|
}
|
||
|
|
s.activeTorchUpdates[pos]++
|
||
|
|
defer func() {
|
||
|
|
if s.activeTorchUpdates[pos] <= 1 {
|
||
|
|
delete(s.activeTorchUpdates, pos)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.activeTorchUpdates[pos]--
|
||
|
|
}()
|
||
|
|
fn()
|
||
|
|
}
|
||
|
|
|
||
|
|
// WithUpdateSource marks redstone propagation as originating from pos for the duration of fn.
|
||
|
|
func (s *State) WithUpdateSource(pos cube.Pos, fn func()) {
|
||
|
|
s.updateSources = append(s.updateSources, pos)
|
||
|
|
defer func() {
|
||
|
|
s.updateSources = s.updateSources[:len(s.updateSources)-1]
|
||
|
|
}()
|
||
|
|
fn()
|
||
|
|
}
|
||
|
|
|
||
|
|
// UpdateSource returns the position that caused the current redstone update.
|
||
|
|
func (s *State) UpdateSource() (cube.Pos, bool) {
|
||
|
|
if len(s.updateSources) == 0 {
|
||
|
|
return cube.Pos{}, false
|
||
|
|
}
|
||
|
|
return s.updateSources[len(s.updateSources)-1], true
|
||
|
|
}
|
||
|
|
|
||
|
|
// MarkTorchSelfTriggeredIfActive marks the next scheduled tick for the redstone torch at pos as self-triggered if the
|
||
|
|
// current redstone propagation originated from that torch.
|
||
|
|
func (s *State) MarkTorchSelfTriggeredIfActive(pos cube.Pos) {
|
||
|
|
if s.activeTorchUpdates[pos] == 0 {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
data := s.torchBurnoutData(pos)
|
||
|
|
data.selfTriggered = true
|
||
|
|
s.torchBurnout[pos] = data
|
||
|
|
}
|
||
|
|
|
||
|
|
// torchBurnoutData retrieves or creates burnout tracking data for the given torch position.
|
||
|
|
func (s *State) torchBurnoutData(pos cube.Pos) torchBurnout {
|
||
|
|
if s.torchBurnout == nil {
|
||
|
|
s.torchBurnout = make(map[cube.Pos]torchBurnout)
|
||
|
|
}
|
||
|
|
data, ok := s.torchBurnout[pos]
|
||
|
|
if !ok {
|
||
|
|
data.expirationTicks = make([]int64, 0, torchBurnoutThreshold+1)
|
||
|
|
}
|
||
|
|
return data
|
||
|
|
}
|
||
|
|
|
||
|
|
// pruneTorchBurnoutData removes expired burnout history and returns the remaining torch data if it is still relevant.
|
||
|
|
func (s *State) pruneTorchBurnoutData(pos cube.Pos, currentTick int64) (torchBurnout, bool) {
|
||
|
|
data, ok := s.torchBurnout[pos]
|
||
|
|
if !ok {
|
||
|
|
return torchBurnout{}, false
|
||
|
|
}
|
||
|
|
data.removeExpired(currentTick)
|
||
|
|
if len(data.expirationTicks) == 0 && !data.burnedOut && !data.selfTriggered {
|
||
|
|
s.ClearTorchBurnout(pos)
|
||
|
|
return torchBurnout{}, false
|
||
|
|
}
|
||
|
|
s.torchBurnout[pos] = data
|
||
|
|
return data, true
|
||
|
|
}
|
||
|
|
|
||
|
|
// removeExpired removes expired state change entries and returns the number of entries that remain.
|
||
|
|
func (data *torchBurnout) removeExpired(currentTick int64) int {
|
||
|
|
data.expirationTicks = slices.DeleteFunc(data.expirationTicks, func(t int64) bool {
|
||
|
|
return t < currentTick
|
||
|
|
})
|
||
|
|
return len(data.expirationTicks)
|
||
|
|
}
|