summaryrefslogtreecommitdiff
path: root/src/unheard/time_object.clj
blob: 6f088b662282b998371036e048f2ab83b9f170f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
(ns unheard.time-object
  (:require [missionary.core :as m]
            [helins.interval.map :as imap]))

;; DESIGN
;; A "time object" is any object with a lifetime that is temporally bounded. The
;; goal of the time object abstraction is to allow for for efficient,
;; low-latency allocation and deallocation of an unlimited number of time
;; objects.
;;
;;It is important that a time tree can be efficiently queried by both a range
;;(for UI) and a point (for a song).
;;
;; IMPLEMENTATION
;; Time objects are stored in an interval tree. 

;; Requirements
;; - time objects returned by a timeline range query will include metadata like
;;   start time and end time
;; - Flows associated with time objects should only mount or dismount when
;;   the result of the query changes. I think the best way to accomplish this
;;   is to have the query return a flow of time objects, and then have some
;;   separate function responsible for "playing" these time objects.
;; 
;; Question:
;; Should time objects take an interval tree as an argument,
;; or should they return a flow of interval information (start, end, value)
;; that can be fed into some kind of reactive interval map bookkeeper?
;; I think the latter.
;;

(def id-counter (atom 0))

(defn time-object
  "A time-object takes a start time, and end time, and a value.
  Value is a flow that will be consumed when the corresponding
  time tree is consumed at a point in time within the time-object's
  interval."
  [>start >end >metadata >flow]
  (let [action (atom nil)
        >action (m/watch action)
        id (swap! id-counter inc)]
    (m/ap
     (reset! action
             [:add
              id
              {:start >start
               :end >end
               :metadata >metadata

               :flow >flow}])
     (try
       (m/?< >action)
       (catch missionary.Cancelled _ [:remove id])))))

(comment
  (def cancel
    ((m/reduce prn nil (time-object 1 2 :a (m/ap)))
     prn prn))

  (cancel))

(defn time-object-collection
  "Takes a flow of [diff-action time-object-id time-object], where:
  - diff-action is one of either :add or :remove
  - time-object-id is a unique identifier
  - time-object is the time object in question

  Returns a collection of time objects, represented as a flow."
  [& time-objects]
  (apply m/latest vector time-objects))

(comment
  (def cancel
    ((m/reduce prn nil
               (time-object-collection
                (time-object 1 2 :a (m/ap))
                (time-object 3 4 :a (m/ap)))) prn prn))

  (cancel))

;; m/store is an optimization, allowing diffs to be dropped prior to processing
;; by consumer. Think :add 1, :remove 1

(defn merge-tocs
  "Merge multiple time-object-collections. Returns a new time-object-collection."
  [& time-object-colletion])

(defn timeline
  "Primary timeline bookkeeping mehanism."
  [time-object-collection]
  (m/ap))

(defn point-query
  "Query a timeline. Returns a flow of time objects."
  [>timeline >at]
  (m/ap
   (let [[tl at] (m/?< (m/latest vector >timeline >at))]
     (get tl at))))

(defn range-query
  "Range query. Returns a flow of time objects."
  [timeline >range])
(defn run
  "Runs the flows associated with a collection of time objects."
  ;; TODO: "Running" a time object has different meanings for different objects.
  ;; How should I think about that?
  [>query-result])

;; How should a time tree work?
;; Well, we eventually want to end up with a single time tree
;; Is it important to be able to merge time trees together?
;; Part of me thinks "yes"
;; Time objects cannot be composed together - that is, you can't
;; take two time objects and add them together to get a new time object
;; However, you _can_ add two time objects together to get a time tree
;; The alternative to having intermediate time trees would be to have
;; some kind of time-object-collection abstraction.
;; time-object-collection would have a merge function.
;; A time object collection would be responsible for all of the bookkeeping
;; related to the lifetimes of time objects.
;; My suspicion is that this will