polix.triggers.core

Domain-agnostic reactive event-driven trigger system.

polix.triggers connects events to effects via conditions evaluated by polix.unify/unify. The library provides mechanics for trigger registration, event processing, timing, and stacking—consumers define their own event types, entities, and domain logic.

Quick Start

(require '[polix.triggers.core :as triggers])

;; Create a registry
(def reg (triggers/create-registry))

;; Register a trigger
(def reg' (triggers/register-trigger
            reg
            {:event-types #{:entity/damaged}
             :timing :polix.triggers.timing/after
             :condition [:= :doc/target-id :doc/trigger.self]
             :effect {:type :heal :amount 1}}
            "ability-123"  ; source
            "player-1"     ; owner
            "entity-1"))   ; self

;; Fire an event
(triggers/fire-event
  {:state {} :registry reg'}
  {:type :entity/damaged :target-id "entity-1" :amount 5})

Core Concepts

  • Event: A map with :type keyword and domain-specific fields
  • Trigger: A registered listener that responds to events
  • Condition: A polix policy expression evaluated via polix.unify/unify
  • Timing: When the trigger fires (:before, :instead, :after, :at)

Condition Evaluation

Trigger conditions are evaluated using polix.unify/unify against a document built from the event and trigger context. The document contains:

  • All event fields (e.g., :target-id, :amount)
  • :self - the entity the trigger is attached to
  • :owner - the owner of the trigger source
  • :source - the source that registered the trigger
  • :event-type - the event’s :type value

Use :doc/ prefixed accessors in conditions (e.g., :doc/self, :doc/target-id).

Conditions that return a residual (missing data) are treated conservatively: the trigger does NOT fire if its condition cannot be fully evaluated.

See polix.triggers.schema for data structure definitions.

create-registry

(create-registry)

Creates an empty trigger registry.

The registry maintains triggers indexed by ID and by event type for efficient lookup during event processing.

fire-event

(fire-event ctx event)

Fires an event and processes all matching triggers.

Takes a context map containing :registry and :state, plus the event to fire. Returns a result map with:

  • :state - updated state after applying effects
  • :registry - updated registry (triggers may be removed if :once?)
  • :event - the original event
  • :results - vector of trigger results
  • :prevented? - true if a before trigger prevented the action

Processing order: 1. Before triggers (sorted by priority, can set :prevented?) 2. Instead triggers (first matching only, skipped if prevented) 3. After triggers (sorted by priority, skipped if prevented) 4. At triggers (always processed)

Example:

(fire-event {:state {:hp 10} :registry reg}
            {:type :entity/damaged :target-id "e-1" :amount 5})

get-trigger

(get-trigger registry trigger-id)

Returns a trigger by ID, or nil if not found.

get-triggers

(get-triggers registry)

Returns all registered triggers as a sequence.

get-triggers-for-event

(get-triggers-for-event registry event-type)

Returns triggers that listen for the given event type.

Triggers are sorted by priority (lower values fire first).

register-trigger

(register-trigger registry trigger-def source-id owner self)

Registers a trigger definition in the registry.

Takes a trigger definition map and binding context (source, owner, self). Returns the updated registry with the new trigger added.

The trigger definition should contain:

  • :event-types - set of event type keywords to listen for
  • :timing - when to fire (:polix.triggers.timing/before, etc.)
  • :condition - optional polix policy expression
  • :effect - effect to apply when condition is satisfied
  • :once? - optional, remove after firing (default false)
  • :priority - optional, lower values fire first (default 0)

The condition is a polix policy expression evaluated via polix.unify/unify when the trigger is processed. It can reference:

  • Event fields (e.g., :doc/target-id, :doc/amount)
  • Trigger bindings (:doc/self, :doc/owner, :doc/source)
  • Event type via :doc/event-type

Example:

(register-trigger registry
  {:event-types #{:entity/damaged}
   :timing :polix.triggers.timing/after
   :condition [:= :doc/target-id :doc/self]
   :effect {:type :counter-attack}}
  "ability-123"   ; source that registered this
  "player-1"      ; owner of the source
  "entity-1")     ; entity the source is attached to

unregister-trigger

(unregister-trigger registry trigger-id)

Removes a trigger by ID from the registry.

Returns the updated registry. If the trigger ID does not exist, returns the registry unchanged.

unregister-triggers-by-source

(unregister-triggers-by-source registry source-id)

Removes all triggers registered by a source.

Useful when an ability or effect is removed and all its triggers should be cleaned up. Returns the updated registry.