== Optimizing Sends

The internals of the tx processing splits the original transactions so that there is one tx-node per element of the top-level transaction.
Loads use an `internal-load` mutation, so they are always a single top-level tx.

To Do:

- There should be a function that takes the nodes with the same ID that are queued and re-combines them.
This is the current combine-sends, but the logic needs to not do reordering or even return what to send.
- The next step of processing the send queue should be optional write reordering
- The next step should then be potentially creating a multi-send send node so that the entire queue can be sent on one network request.

So, desired send processing:

1. restore transaction semantics: fn of send-queue -> send-queue.
Combines send nodes with a common tx ID into a single send node so that as much of the original tx will be sent as a unit as is possible.
2. Reorder the queue: Pluggable and optional.
Put writes first, enable customization like "tx priorities"
3. Merge the queue (optional, requires enabling on client and server): For a given remote it is possible for us to encode a "multi-send", where the send nodes from (1/2) are combined into a data structure that allows the lower-level networking to send the entire queue in one network round-trip.
Something like a vector of maps?
   `[{::id id ::tx tx} {::id id2 ::tx tx}]`, where the server returns `{id result id2 result ...}`.

[source,clojure]
-----
(defmutation f [_]
  (action ..opt...)
  (result-action [env] ...)
  (rmt1 ...))

(defmutation g [_]
  (action [env]
    (transact! this [(i)]))
  (result-action [env] ...) ; has a default
  (rmt2 [env]
    (-> env
      (m/with-target env [:x])
      (m/returning env Person))))

;; could execute either true/false on optimistic?:
(transact! this [(f) (g)] {:optimistic? true})
;; f + g opt + f on rmt1 + g one rmt2 (all at once)

;; opt false is same as ptransact!
(ptransact! this [(f) (g)])
;; f opt + f net, then on net result: g opt g net
-----

[ditaa]
-----
                    |
                    | Into submission queue
                    v
            +-------+ +-------+
sub q       |(f) (g)| |       |  ...
            | UUID  | |       |
            +--+----+ +---+---+
               |          |
               +----+-----+
                    | (user releases thread)
                    | Activation (dispatch of f and g, which returns maps like {:action ...})
                    v
            +-----------------------+
            | +-------+ +-------+   +<---------------+
active Q    | | (f)   | | (g)   |  ... (tx nodes)    | RUN A Q STEP
            | | RA    | |       |   |                | Run through active queue and process
            | +-------+ +-------+   +----------------+ Also dispatches results to result-action
            +-----------------------+
            ^  |          |
            |  +----+-----+
     net    |       |
   result   +--+    | (possible re-combining of tx)
               |    v
            +--+----+ +-------+
Send Q Rmt1 |(f)    | |       |  ...
            +-------+ +-------+

            +-------+ +-------+
Send Q Rmt2 |    (g)| |       |  ...
            +-------+ +-------+

-----

== Dan's notes:


As far as I understand things; the entrypoint of a transaction is components/transact!

(ns com.fulcrologic.fulcro.components)

The tx! algorithm is pulled off of the application map:

(let [tx!     (ah/app-algorithm app :tx!)

Some options are setup for the transaction and then tx! is invoked.

This invokes `tx!`: (tx! app tx options)

when you construct a fulcro application tx! is set to com.fulcrologic.fulcro.algorithms.tx-processing/default-tx!
by default.

If it's syncrhonous then this is invoked:

(transact-sync! app tx options)


otherwise :

(schedule-activation! app)

Then a new Transaction node (a spec'd map) is constructed

             node            (tx-node tx options)

tx is the eql vector passed to transact!


Then the tx-node is added to the end of the submission-queue via conj-vec

(swap! runtime-atom
  (fn [s]
    (cond-> (update s ::submission-queue (fn [v n] (conj (vec v) n)) node)
       ;; refresh sets are cumulative because rendering is debounced
       (seq refresh) (update :com.fulcrologic.fulcro.application/to-refresh accumulate refresh)
       (seq only-refresh) (update :com.fulcrologic.fulcro.application/only-refresh accumulate only-refresh))))



default-tx ends there - returning the id of the tx-node (a newly constructed uuid)

The rest of the work picks up in schedule-activation! which calls com.fulcrologic.fulcro.algorithms.scheduling/schedule!
with a delay of 0ms:

   (schedule! app ::activation-scheduled? activate-submissions! tm))

(defn schedule!

  "Schedule the processing of a specific action in the runtime atom. This is a no-op if the item is already scheduled.
  When the timeout arrives it runs the given action and sets the given flag back to false.

  - `scheduled-key` - The runtime flag that tracks scheduling for the processing.
  - `action` - The function to run when the scheduled time comes.
  - `tm` - Number of ms to delay (default 0)."

  [app scheduled-key action tm]
   [:com.fulcrologic.fulcro.application/app keyword? fn? int? => any?]
   (let [{:com.fulcrologic.fulcro.application/keys [runtime-atom]} app]
     (when-not (get @runtime-atom scheduled-key)
       (swap! runtime-atom assoc scheduled-key true)
       (defer
         (fn [] (swap! runtime-atom assoc scheduled-key false)
                (action app))
         tm))))

(defer just calls js/setTimeout - it's wrapped for cljc support.


The next piece is what is action in schedule! ? - it is a fn reference passed in above to `schedule!` - in this case `activate-submissions!`


It is this:

(>defn activate-submissions!
  "Activate all of the transactions that have been submitted since the last activation. After the items are activated
  a single processing step will run for the active queue.

  Activation can be blocked by the tx-node options for things like waiting for the next render frame."
  [{:keys [:com.fulcrologic.fulcro.application/runtime-atom] :as app}]
  [:com.fulcrologic.fulcro.application/app => any?]

  (let [{blocked true ready false} (group-by (comp boolean :after-render? ::options) (::submission-queue @runtime-atom))
        dispatched-nodes (mapv #(dispatch-elements % (build-env app %) m/mutate) ready)]
    (swap! runtime-atom (fn [a]
                          (-> a
                            (update ::active-queue #(reduce conj % dispatched-nodes))
                            (assoc ::submission-queue (vec blocked)))))
    (process-queue! app)))

== Server side

So the eql of the tx is sent over the http to the server. The server eventually gets a pathom tx as the body of a POST
request.
