effects

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2026 License: MIT Imports: 2 Imported by: 0

Documentation

Overview

Package effects models transient compositional lifecycle state: an entity carries a set of timed effects, each with apply/tick/expire/ refresh hooks, that can stack with rules (refresh, add, ignore, replace). Named generically because the pattern is domain-free.

Common applications:

  • Games — buffs/debuffs/DoTs/auras carried by a player or NPC.
  • Trading-bot — signal modifiers ("buy weight decays over 30min"), volatility scalings, regime overlays.
  • Observability — alert states with debounce / suppression / escalation.
  • Rate-limit windows with TTL stacking.

Trackers are single-writer: one Tracker per subject. For multi-subject systems hold a Tracker map keyed by subject ID and own each from a dedicated goroutine (typically via kit/actor).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ActiveEffect

type ActiveEffect struct {
	ID        EffectID
	Stacks    int
	AppliedAt time.Time
	ExpiresAt time.Time // zero for infinite-duration effects
}

ActiveEffect is the public snapshot of an effect instance held by a Tracker.

type Effect

type Effect[D any] interface {
	// ID returns the unique identifier for this effect kind. Two effects
	// with the same ID are considered the same kind for stacking purposes.
	ID() EffectID

	// Stack returns the policy applied when this effect is added to a
	// subject that already carries an effect with the same ID.
	Stack() StackPolicy

	// MaxStacks is the cap for StackAdd; ignored for other policies.
	// Zero means unlimited.
	MaxStacks() int

	// Duration is how long this effect lasts from Apply. Zero means
	// the effect persists until Remove is called explicitly.
	Duration() time.Duration

	// OnApply runs when the effect is first added (or when StackReplace
	// replaces a prior instance).
	OnApply(ctx context.Context, data *D, stacks int)

	// OnTick runs once per Tracker.Tick call while the effect is active.
	// dt is the elapsed time since the previous Tick.
	OnTick(ctx context.Context, data *D, stacks int, dt time.Duration)

	// OnRefresh runs when the effect's duration is reset (StackRefresh
	// or StackAdd applied to existing).
	OnRefresh(ctx context.Context, data *D, stacks int)

	// OnExpire runs when the effect's duration elapses or it is removed.
	OnExpire(ctx context.Context, data *D, stacks int)
}

Effect is the unit of transient state. Implementations are typically small value types or stateless singletons that read/write a shared per-subject data block D via the lifecycle hooks.

type EffectID

type EffectID uint32

EffectID identifies an effect kind. Two effects with the same ID applied to the same Tracker interact via StackPolicy.

type StackPolicy

type StackPolicy uint8

StackPolicy controls what happens when an effect is applied to a Tracker that already holds one with the same EffectID.

const (
	// StackRefresh resets the existing effect's duration; stack count
	// is unchanged. Default behaviour for most game buffs.
	StackRefresh StackPolicy = iota

	// StackAdd increments the stack count (up to MaxStacks) and refreshes
	// duration. Use for damage-over-time effects that stack intensity.
	StackAdd

	// StackIgnore rejects the new application if one already exists.
	StackIgnore

	// StackReplace replaces the existing effect with the new one.
	StackReplace
)

type Tracker

type Tracker[D any] struct {
	// contains filtered or unexported fields
}

Tracker holds the active effect set for a single subject. Single- writer by design: Apply/Tick/Remove must be called from one goroutine (typically the actor owning the subject).

func NewTracker

func NewTracker[D any]() *Tracker[D]

NewTracker returns an empty Tracker.

func (*Tracker[D]) Apply

func (t *Tracker[D]) Apply(ctx context.Context, e Effect[D], data *D, now time.Time) bool

Apply adds or stacks an effect on the subject, returning true if the application took effect (false for StackIgnore against an existing instance). The data pointer is passed through to the effect's hooks so it can mutate per-subject state (HP, signal weight, etc.).

func (*Tracker[D]) Has

func (t *Tracker[D]) Has(id EffectID) bool

Has reports whether an effect with this ID is currently active.

func (*Tracker[D]) Len

func (t *Tracker[D]) Len() int

Len returns the number of active effects.

func (*Tracker[D]) Remove

func (t *Tracker[D]) Remove(ctx context.Context, id EffectID, data *D)

Remove removes the effect with the given ID, firing OnExpire. No-op if the effect is not present.

func (*Tracker[D]) Snapshot

func (t *Tracker[D]) Snapshot() []ActiveEffect

Snapshot returns a copy of the active effect list for replication or inspection. The returned slice is owned by the caller.

func (*Tracker[D]) Tick

func (t *Tracker[D]) Tick(ctx context.Context, data *D, now time.Time, dt time.Duration)

Tick advances all active effects, firing OnTick for any still alive and OnExpire+removing those whose duration has elapsed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL