<commit_before>(ns sparkle.core
  (:require [clojure.core.async :refer [thread chan <!! >!! alt!! close!]]
   [clojure.algo.generic.functor :refer [fmap]]
            [com.stuartsierra.component :as component])
  (:gen-class))

; Let's start by implementing rendering a vec of layers on one static set of LEDs

(def black {:r 0 :g 0 :b 0})
(def white {:r 1 :g 1 :b 1})

(def leds (take 5 (repeat black)))

(defn static-color [color]
  (fn [env leds]
    (map (constantly color) leds)))

(defn scale-brightness [factor]
  (fn [env leds]
    (map (fn [led]
           (fmap #(* factor %) led))
         leds)))

(defn brightness-gradient [start-factor end-factor]
  (fn [env leds]
    (let [step (/ (- end-factor start-factor)
                  (- (count leds) 1))
          factors (iterate #(+ % step) start-factor)]
      (map (fn [led factor]
             (fmap #(* factor %) led))
           leds factors))))

(defn pulse-brightness [{:keys [time] :as env} leds]
  (let [scaled-time (/ time 10000)
        brightness-factor (Math/sin scaled-time)]
    (map (fn [led]
           (fmap #(* % brightness-factor) led))
         leds)))

(defn apply-layers [layers env leds]
  (let [env-applied-layers (for [layer layers] (partial layer env))]
    ((apply comp env-applied-layers) leds)))


(defrecord RenderState [env model])


(defn display [{:keys [frame-chan] :as displayer} frame]
  (>!! frame-chan frame))

(defn start-displaying [{:keys [frame-chan] :as displayer}]
  (thread
    (loop [prev-frame nil]
      (let [frame (<!! frame-chan)]
        (if frame
          (do (when (not= frame prev-frame)
                (println frame))
              (recur frame))
          (println "Shutting down displayer"))))))

(defrecord Displayer [frame-chan]
  component/Lifecycle
  
  (start [displayer]
    (let [frame-chan (chan)
          displayer (assoc displayer :frame-chan frame-chan)]
      (start-displaying displayer)
      displayer))

  (stop [displayer]
    (close! frame-chan)
    displayer))


(defn get-env-updates [env]
  (-> env
      (assoc :time (System/currentTimeMillis))))


(defn render-step [{:keys [env model] :as state}]
  (let [{:keys [layers]} model
        updated-env (get-env-updates env)]
    (->> leds
         (apply-layers layers updated-env))))


(defn send-command [{:keys [command-chan] :as renderer} command]
  (>!! command-chan command))

(defmulti execute (fn [command layers status] (:type command)))

(defmethod execute :start [_ state status]
  [state :running])

(defmethod execute :pause [_ state status]
  [state :paused])

(defmethod execute :update [{:keys [new-state]} state status]
  [new-state status])

(defmethod execute :stop [_ _ _]
  nil)

(defn start-rendering [{:keys [command-chan displayer] :as renderer}]
  (thread
    (loop [state (-> RenderState {} {})
           status :running]
      (let [[state status :as loop-result]
            (if (= status :running)
              (alt!! command-chan ([command] (execute command state status)) 
                     :default (let [next-frame (render-step state)]
                                (display displayer next-frame)
                                [state :running]))
              (execute (<!! command-chan) state status))]
        (if loop-result
          (recur state status)
          (println "Shutting down render loop"))))))

(defrecord Renderer [command-chan displayer]
  component/Lifecycle

  (start [renderer]
    (let [command-chan (chan)
          renderer (assoc renderer :command-chan command-chan)]
      (start-rendering renderer)
      renderer))

  (stop [renderer]
    (>!! command-chan {:type :stop})
    renderer))


(defn sparkle-system []
  (component/system-map
   :renderer (component/using (map->Renderer {})
                              [:displayer])
   :displayer (map->Displayer {})))

;;; Below: defs useful for interacting with the running renderer

(def started-system (component/start (sparkle-system)))

(def renderer (:renderer started-system))

(def displayer (:displayer started-system))
<commit_msg>Introduce Displayer protocol, implement for ConsoleDisplayer<commit_after>(ns sparkle.core
  (:require [clojure.core.async :refer [thread chan <!! >!! alt!! close!]]
   [clojure.algo.generic.functor :refer [fmap]]
            [com.stuartsierra.component :as component])
  (:gen-class))

; Let's start by implementing rendering a vec of layers on one static set of LEDs

(def black {:r 0 :g 0 :b 0})
(def white {:r 1 :g 1 :b 1})

(def leds (take 5 (repeat black)))

(defn static-color [color]
  (fn [env leds]
    (map (constantly color) leds)))

(defn scale-brightness [factor]
  (fn [env leds]
    (map (fn [led]
           (fmap #(* factor %) led))
         leds)))

(defn brightness-gradient [start-factor end-factor]
  (fn [env leds]
    (let [step (/ (- end-factor start-factor)
                  (- (count leds) 1))
          factors (iterate #(+ % step) start-factor)]
      (map (fn [led factor]
             (fmap #(* factor %) led))
           leds factors))))

(defn pulse-brightness [{:keys [time] :as env} leds]
  (let [scaled-time (/ time 10000)
        brightness-factor (Math/sin scaled-time)]
    (map (fn [led]
           (fmap #(* % brightness-factor) led))
         leds)))

(defn apply-layers [layers env leds]
  (let [env-applied-layers (for [layer layers] (partial layer env))]
    ((apply comp env-applied-layers) leds)))


(defrecord RenderState [env model])


(defprotocol Displayer
  "A protocol for things that are capable of displaying frames"
  (display [displayer frame] "Display a frame"))

(defn to-displayer [{:keys [frame-chan] :as displayer} frame]
  (>!! frame-chan frame))

(defn start-displayer [{:keys [frame-chan] :as displayer}]
  (thread
    (loop []
      (let [frame (<!! frame-chan)]
        (if frame
          (do (display displayer frame)
              (recur))
          (println "Shutting down displayer"))))))


(defrecord ConsoleDisplayer [frame-chan prev-frame]
  component/Lifecycle
  (start [displayer]
    (let [displayer (-> displayer
                        (assoc :frame-chan (chan))
                        (assoc :prev-frame (atom nil)))]
      (start-displayer displayer)
      displayer))

  (stop [displayer]
    (close! frame-chan)
    displayer)

  Displayer
  (display [{:keys [prev-frame]} frame]
    (when (not= frame @prev-frame)
      (reset! prev-frame frame)
      (println frame))))


(defn get-env-updates [env]
  (-> env
      (assoc :time (System/currentTimeMillis))))

(defn render-step [{:keys [env model] :as state}]
  (let [{:keys [layers]} model
        updated-env (get-env-updates env)]
    (->> leds
         (apply-layers layers updated-env))))


(defn send-command [{:keys [command-chan] :as renderer} command]
  (>!! command-chan command))

(defmulti execute (fn [command layers status] (:type command)))

(defmethod execute :start [_ state status]
  [state :running])

(defmethod execute :pause [_ state status]
  [state :paused])

(defmethod execute :update [{:keys [new-state]} state status]
  [new-state status])

(defmethod execute :stop [_ _ _]
  nil)

(defn start-rendering [{:keys [command-chan displayer] :as renderer}]
  (thread
    (loop [state (-> RenderState {} {})
           status :running]
      (let [[state status :as loop-result]
            (if (= status :running)
              (alt!! command-chan ([command] (execute command state status)) 
                     :default (let [next-frame (render-step state)]
                                (to-displayer displayer next-frame)
                                [state :running]))
              (execute (<!! command-chan) state status))]
        (if loop-result
          (recur state status)
          (println "Shutting down render loop"))))))

(defrecord Renderer [command-chan displayer]
  component/Lifecycle

  (start [renderer]
    (let [command-chan (chan)
          renderer (assoc renderer :command-chan command-chan)]
      (start-rendering renderer)
      renderer))

  (stop [renderer]
    (>!! command-chan {:type :stop})
    renderer))


(defn sparkle-system []
  (component/system-map
   :renderer (component/using (map->Renderer {})
                              [:displayer])
   :displayer (map->ConsoleDisplayer {})))

;;; Below: defs useful for interacting with the running renderer

(def started-system (component/start (sparkle-system)))

(def renderer (:renderer started-system))

(def displayer (:displayer started-system))
{'fraction_non_alphanumeric': 0.14606476010399433, 'fraction_numerical': 0.0030725596785629874, 'mean_word_length': 2.6843709185894644, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 24, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '194840', 'n_tokens_mistral': 2744, 'n_tokens_neox': 2630, 'n_words': 792}