         (map value)))
 
 ; compiling graphs:
+
+(defprotocol SignalProtocol
+  (signal-deps [s] "returns the set of \"parent\" signals on which this signal depends")
+  (parents-map [s])
+  (kids-map [s])
+  (topsort [s])
+  (-get-graph-meta [s k]))
+
+(defn signal?
+  "returns `true` if the argument satisfies `SignalProtocol`, `false` otherwise"
+  [x]
+  (satisfies? SignalProtocol x))
 
 (defn- node-graph-zipper
   "Takes a signal and returns a zipper which can be used to traverse the signal graph."

  ([value] (->Cached value nil))
  ([value event] (->Cached value event)))

(defprotocol SignalProtocol
  (signal-deps [s] "returns the set of \"parent\" signal which this signal depends on"))

(defn signal?
  "returns `true` if the argument satisfies `SignalProtocol`, `false` otherwise"
  [x]
  (satisfies? SignalProtocol x))

(defrecord SignalDefinition
  [init-fn sources relayed-event-topic msg-fn deps event-sources]
  SignalProtocol
  (signal-deps [_]
    (into #{}
          (filter signal?)
          (or deps sources))))

(defn- setup-event-relay
  "Takes a topic, and returns an input signal which relays matching events as messages to its children"
  [opts]
  (if-let [relayed-topic (:relayed-event-topic opts)]
    (assoc opts
      :sources [:events]
      :msg-fn (fn [prev [event]]
                (when (= relayed-topic (topic event))
                  (fresh (value event)))))
    opts))

(defn make-signal
  "Takes a map of opts and returns a signal."
  [opts]
  (-> opts
      (setup-event-relay)
      (map->SignalDefinition)))

(def ^{:doc "A transducer which takes in batches of signal graph messages and pipes out fresh values."}
  fresh-values
  (comp cat

  [output-node]
  (-> output-node calculate-dependency-maps :parents-map))

(defrecord SignalDefinitionMetadata
  [parents-map kids-map topsort])

(defrecord SignalDefinition
  [init-fn sources relayed-event-topic msg-fn deps event-sources]
  SignalProtocol
  (signal-deps [_]
    (into #{}
          (filter signal?)
          (or deps sources)))
  (-get-graph-meta [s k]
    (let [meta-atom (-> s meta (get k))
          memoized @meta-atom]
      (if-not (nil? memoized)
        memoized
        (let [{:keys [parents-map kids-map topsort]} (calculate-graph-meta s)
              m (meta s)]
          (-> m :parents-map (reset! parents-map))
          (-> m :kids-map (reset! kids-map))
          (-> m :topsort (reset! topsort))
          @meta-atom))))
  (parents-map [s] (-get-graph-meta s :parents-map))
  (kids-map [s] (-get-graph-meta s :kids-map))
  (topsort [s] (-get-graph-meta s :topsort)))

(defn- setup-event-relay
  "Takes a topic, and returns an input signal which relays matching events as messages to its children"
  [opts]
  (if-let [relayed-topic (:relayed-event-topic opts)]
    (assoc opts
      :sources [:events]
      :msg-fn (fn [prev [event]]
                (when (= relayed-topic (topic event))
                  (fresh (value event)))))
    opts))

(defn- setup-metadata
  [sig]
  (with-meta sig (->SignalDefinitionMetadata (atom nil) (atom nil) (atom nil))))

(defn make-signal
  "Takes a map of opts and returns a signal."
  [opts]
  (-> opts
      (setup-event-relay)
      (map->SignalDefinition)
      (setup-metadata)))

(defrecord CompiledGraph
  [output-signal sorted-signals])
