introduce priority map

- in future PRs we want to reduce the expensive sort operations
on list of messages while keeping the possibility to get a message
by its ID
- priority map allow to keep a map sorted by it's value which
is what we want to do here. we want to keep the messages ordered
by clock-value

Signed-off-by: yenda <eric@status.im>
This commit is contained in:
yenda 2018-11-19 11:55:29 +01:00
parent 77e9aea755
commit 73ccb44663
No known key found for this signature in database
GPG Key ID: 0095623C0069DCE6
3 changed files with 248 additions and 14 deletions

View File

@ -10,14 +10,14 @@
[status-im.transport.message.public-chat :as public-chat]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.desktop.events :as desktop.events]
[status-im.ui.components.react :as react]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.clocks :as utils.clocks]
[status-im.utils.fx :as fx]
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.utils :as utils]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.utils.platform :as platform]))
[status-im.utils.platform :as platform]
[status-im.utils.priority-map :refer [empty-message-map]]
[status-im.utils.utils :as utils]))
(defn multi-user-chat? [cofx chat-id]
(get-in cofx [:db :chats chat-id :group-chat]))
@ -49,7 +49,8 @@
:is-active true
:timestamp now
:contacts #{chat-id}
:last-clock-value 0}))
:last-clock-value 0
:messages empty-message-map}))
(fx/defn upsert-chat
"Upsert chat when not deleted"
@ -97,7 +98,7 @@
deleted-at-clock-value
(utils.clocks/send 0))]
{:db (update-in db [:chats chat-id] merge
{:messages {}
{:messages empty-message-map
:message-groups {}
:unviewed-messages #{}
:not-loaded-message-ids #{}

View File

@ -4,13 +4,13 @@
[status-im.chat.commands.core :as commands]
[status-im.chat.models :as chat-model]
[status-im.constants :as constants]
[status-im.data-store.contacts :as contacts-store]
[status-im.data-store.user-statuses :as user-statuses-store]
[status-im.utils.contacts :as utils.contacts]
[status-im.utils.datetime :as time]
[status-im.utils.fx :as fx]))
[status-im.utils.fx :as fx]
[status-im.utils.priority-map :refer [empty-message-map]]))
(def index-messages (partial into {} (map (juxt :message-id identity))))
(def index-messages (partial into empty-message-map
(map (juxt :message-id identity))))
(defn- sort-references
"Sorts message-references sequence primary by clock value,
@ -70,10 +70,11 @@
:deduplication-ids (get stored-deduplication-ids chat-id)
:not-loaded-message-ids (set/difference (get stored-message-ids chat-id)
(set message-ids))
:referenced-messages (index-messages
(get-referenced-messages
chat-id
(get-referenced-ids chat-messages)))))))
:referenced-messages (into {}
(map (juxt :message-id identity)
(get-referenced-messages
chat-id
(get-referenced-ids chat-messages))))))))
{}
all-stored-chats)]
(fx/merge cofx

View File

@ -0,0 +1,232 @@
(ns status-im.utils.priority-map
(:require [cljs.core :as core])
(:use [cljs.reader :only [register-tag-parser!]])
(:require-macros [cljs.core :as coreclj]))
;; from https://github.com/tailrecursion/cljs-priority-map/blob/master/src/cljs/tailrecursion/priority_map.cljs
;; fixing `vals` and `keys` function
(deftype PersistentPriorityMap [priority->set-of-items item->priority meta keyfn ^:mutable __hash]
IPrintWithWriter
(-pr-writer [coll writer opts]
(let [pr-pair (fn [keyval] (pr-sequential-writer writer pr-writer "" " " "" opts keyval))]
(pr-sequential-writer writer pr-pair "#status-im.utils.priority-map {" ", " "}" opts coll)))
IWithMeta
(-with-meta [this meta]
(PersistentPriorityMap. priority->set-of-items item->priority meta keyfn __hash))
IMeta
(-meta [this] meta)
ICollection
(-conj [this entry]
(if (vector? entry)
(-assoc this (-nth entry 0) (-nth entry 1))
(reduce -conj this entry)))
IEmptyableCollection
(-empty [this] (with-meta
status-im.utils.priority-map.PersistentPriorityMap.EMPTY
meta))
IEquiv
(-equiv [this other]
(-equiv item->priority other))
IHash
(-hash [this]
(coreclj/caching-hash this core/hash-unordered-coll __hash))
ISeqable
(-seq [this]
(if keyfn
(seq (for [[priority item-set] priority->set-of-items, item item-set]
(MapEntry. item (item->priority item) nil)))
(seq (for [[priority item-set] priority->set-of-items, item item-set]
(MapEntry. item priority nil)))))
IReversible
(-rseq [coll]
(if keyfn
(seq (for [[priority item-set] (rseq priority->set-of-items), item item-set]
(MapEntry. item (item->priority item) nil)))
(seq (for [[priority item-set] (rseq priority->set-of-items), item item-set]
(MapEntry. item priority nil)))))
ICounted
(-count [this]
(count item->priority))
ILookup
(-lookup [this item]
(get item->priority item))
(-lookup [coll item not-found]
(get item->priority item not-found))
IStack
(-peek [this]
(when-not (zero? (count item->priority))
(let [f (first priority->set-of-items)
item (first (val f))]
(if keyfn
[item (item->priority item)]
[item (key f)]))))
(-pop [this]
(if (zero? (count item->priority))
(throw (js/Error. "Can't pop empty priority map"))
(let [f (first priority->set-of-items)
item-set (val f)
item (first item-set)
priority-key (key f)]
(if (= (count item-set) 1)
(PersistentPriorityMap.
(dissoc priority->set-of-items priority-key)
(dissoc item->priority item)
meta
keyfn
nil)
(PersistentPriorityMap.
(assoc priority->set-of-items priority-key (disj item-set item)),
(dissoc item->priority item)
meta
keyfn
nil)))))
IAssociative
(-assoc [this item priority]
(if-let [current-priority (get item->priority item nil)]
(if (= current-priority priority)
this
(let [priority-key (keyfn priority)
current-priority-key (keyfn current-priority)
item-set (get priority->set-of-items current-priority-key)]
(if (= (count item-set) 1)
(PersistentPriorityMap.
(assoc (dissoc priority->set-of-items current-priority-key)
priority-key (conj (get priority->set-of-items priority-key #{}) item))
(assoc item->priority item priority)
meta
keyfn
nil)
(PersistentPriorityMap.
(assoc priority->set-of-items
current-priority-key (disj (get priority->set-of-items current-priority-key) item)
priority-key (conj (get priority->set-of-items priority-key #{}) item))
(assoc item->priority item priority)
meta
keyfn
nil))))
(let [priority-key (keyfn priority)]
(PersistentPriorityMap.
(assoc priority->set-of-items
priority-key (conj (get priority->set-of-items priority-key #{}) item))
(assoc item->priority item priority)
meta
keyfn
nil))))
(-contains-key? [this item]
(contains? item->priority item))
IMap
(-dissoc [this item]
(let [priority (item->priority item ::not-found)]
(if (= priority ::not-found)
this
(let [priority-key (keyfn priority)
item-set (priority->set-of-items priority-key)]
(if (= (count item-set) 1)
(PersistentPriorityMap.
(dissoc priority->set-of-items priority-key)
(dissoc item->priority item)
meta
keyfn
nil)
(PersistentPriorityMap.
(assoc priority->set-of-items priority-key (disj item-set item)),
(dissoc item->priority item)
meta
keyfn
nil))))))
ISorted
(-sorted-seq [this ascending?]
((if ascending? seq rseq) this))
(-sorted-seq-from [this k ascending?]
(let [sets (if ascending?
(subseq priority->set-of-items >= k)
(rsubseq priority->set-of-items <= k))]
(if keyfn
(seq (for [[priority item-set] sets, item item-set]
[item (item->priority item)]))
(seq (for [[priority item-set] sets, item item-set]
[item priority])))))
(-entry-key [this entry]
(keyfn (val entry)))
(-comparator [this] compare)
IFn
(-invoke [this item]
(-lookup this item))
(-invoke [this item not-found]
(-lookup this item not-found)))
(set! status-im.utils.priority-map.PersistentPriorityMap.EMPTY
(PersistentPriorityMap. (sorted-map) {} {} identity nil))
(defn- pm-empty-by [comparator]
(PersistentPriorityMap. (sorted-map-by comparator) {} {} identity nil))
(defn- pm-empty-keyfn
([keyfn] (PersistentPriorityMap. (sorted-map) {} {} keyfn nil))
([keyfn comparator] (PersistentPriorityMap. (sorted-map-by comparator) {} {} keyfn nil)))
(defn- read-priority-map [elems]
(if (map? elems)
(into status-im.utils-map.PersistentPriorityMap.EMPTY elems)
(throw (js/Error "Priority map literal expects a map for its elements."))))
(register-tag-parser! "status-im.utils.priority-map" read-priority-map)
(defn priority-map
"keyval => key val
Returns a new priority map with supplied mappings."
([& keyvals]
(loop [in (seq keyvals) out status-im.utils.priority-map.PersistentPriorityMap.EMPTY]
(if in
(recur (nnext in) (assoc out (first in) (second in)))
out))))
(defn priority-map-by
"keyval => key val
Returns a new priority map with supplied
mappings, using the supplied comparator."
([comparator & keyvals]
(loop [in (seq keyvals) out (pm-empty-by comparator)]
(if in
(recur (nnext in) (assoc out (first in) (second in)))
out))))
(defn priority-map-keyfn
"keyval => key val
Returns a new priority map with supplied
mappings, using the supplied keyfn."
([keyfn & keyvals]
(loop [in (seq keyvals) out (pm-empty-keyfn keyfn)]
(if in
(recur (nnext in) (assoc out (first in) (second in)))
out))))
(defn priority-map-keyfn-by
"keyval => key val
Returns a new priority map with supplied
mappings, using the supplied keyfn and comparator."
([keyfn comparator & keyvals]
(loop [in (seq keyvals) out (pm-empty-keyfn keyfn comparator)]
(if in
(recur (nnext in) (assoc out (first in) (second in)))
out))))
(def empty-message-map
(priority-map-keyfn-by :clock-value >))