From 4030e43ac1a042d1bf5146bed32d5d26391bcb46 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 17 Apr 2020 15:05:51 +0200 Subject: [PATCH] :tada: Add (web)worker impl with selection index. --- frontend/src/uxbox/main/worker.cljs | 26 +++++ frontend/src/uxbox/util/worker.cljs | 53 ++++++++++ frontend/src/uxbox/util/workers.cljs | 66 ------------ frontend/src/uxbox/worker.cljs | 1 + frontend/src/uxbox/worker/align.cljs | 28 ------ frontend/src/uxbox/worker/selection.cljs | 123 +++++++++++++++++++++++ 6 files changed, 203 insertions(+), 94 deletions(-) create mode 100644 frontend/src/uxbox/main/worker.cljs create mode 100644 frontend/src/uxbox/util/worker.cljs delete mode 100644 frontend/src/uxbox/util/workers.cljs delete mode 100644 frontend/src/uxbox/worker/align.cljs create mode 100644 frontend/src/uxbox/worker/selection.cljs diff --git a/frontend/src/uxbox/main/worker.cljs b/frontend/src/uxbox/main/worker.cljs new file mode 100644 index 000000000..af390d7dc --- /dev/null +++ b/frontend/src/uxbox/main/worker.cljs @@ -0,0 +1,26 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns uxbox.main.worker + (:require + [cljs.spec.alpha :as s] + [uxbox.common.spec :as us] + [uxbox.util.worker :as uw])) + +(defn on-error + [instance error] + (js/console.log "error on worker" error)) + +(defonce instance + (when (not= *target* "nodejs") + (uw/init "js/worker.js" on-error))) + +(defn ask! + [message] + (uw/ask! instance message)) diff --git a/frontend/src/uxbox/util/worker.cljs b/frontend/src/uxbox/util/worker.cljs new file mode 100644 index 000000000..329f43fd9 --- /dev/null +++ b/frontend/src/uxbox/util/worker.cljs @@ -0,0 +1,53 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns uxbox.util.worker + "A lightweight layer on top of webworkers api." + (:require + [beicon.core :as rx] + [uxbox.common.uuid :as uuid] + [uxbox.util.transit :as t])) + +(declare handle-response) +(defrecord Worker [instance stream]) + +(defn ask! + [w message] + (let [sender-id (uuid/next) + data (t/encode {:payload message :sender-id sender-id}) + instance (:instance w)] + (.postMessage instance data) + (->> (:stream w) + (rx/filter #(= (:reply-to %) sender-id)) + (rx/map handle-response) + (rx/first)))) + +(defn init + "Return a initialized webworker instance." + [path on-error] + (let [ins (js/Worker. path) + bus (rx/subject) + wrk (Worker. ins bus)] + (.addEventListener ins "message" + (fn [event] + (let [data (.-data event) + data (t/decode data)] + (rx/push! bus data)))) + (.addEventListener ins "error" + (fn [error] + (on-error wrk error))) + + wrk)) + +(defn- handle-response + [{:keys [payload error] :as response}] + (if-let [{:keys [data message]} error] + (throw (ex-info message data)) + payload)) + diff --git a/frontend/src/uxbox/util/workers.cljs b/frontend/src/uxbox/util/workers.cljs deleted file mode 100644 index 22815d743..000000000 --- a/frontend/src/uxbox/util/workers.cljs +++ /dev/null @@ -1,66 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL - -(ns uxbox.util.workers - "A lightweight layer on top of Web Workers API." - (:require [beicon.core :as rx] - [uxbox.common.uuid :as uuid] - [uxbox.util.transit :as t])) - -;; --- Implementation - -(defprotocol IWorker - (-ask [_ msg] "Send and receive message as rx stream.") - (-send [_ msg] "Send message and forget.")) - -(deftype WebWorker [stream wrk] - IWorker - (-ask [this message] - (let [sender (uuid/next) - data (assoc message :sender sender) - data (t/encode data)] - (.postMessage wrk data) - (->> stream - (rx/filter #(= (:reply-to %) sender)) - (rx/take 1)))) - - (-send [this message] - (let [sender (uuid/next) - data (assoc message :sender sender) - data (t/encode data)] - (.postMessage wrk data) - (->> stream - (rx/filter #(= (:reply-to %) sender)))))) - -;; --- Public Api - -(defn init - "Return a initialized webworker instance." - [path] - (let [wrk (js/Worker. path) - bus (rx/subject)] - (.addEventListener wrk "message" - (fn [event] - (let [data (.-data event) - data (t/decode data)] - (rx/push! bus data)))) - (.addEventListener wrk "error" - (fn [event] - (rx/error! bus event))) - - (WebWorker. (rx/map identity bus) wrk))) - -(defn ask! - [wrk message] - (-ask wrk message)) - -(defn send! - [wrk message] - (-send wrk message)) - diff --git a/frontend/src/uxbox/worker.cljs b/frontend/src/uxbox/worker.cljs index d2fbee3ca..39ea0645f 100644 --- a/frontend/src/uxbox/worker.cljs +++ b/frontend/src/uxbox/worker.cljs @@ -17,6 +17,7 @@ [uxbox.common.spec :as us] [uxbox.common.uuid :as uuid] [uxbox.worker.impl :as impl] + [uxbox.worker.selection] [uxbox.util.transit :as t] [uxbox.util.worker :as w])) diff --git a/frontend/src/uxbox/worker/align.cljs b/frontend/src/uxbox/worker/align.cljs deleted file mode 100644 index d1bf8a83c..000000000 --- a/frontend/src/uxbox/worker/align.cljs +++ /dev/null @@ -1,28 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2016 Andrey Antukh - -(ns uxbox.worker.align - "Workspace aligment indexes worker." - (:require [beicon.core :as rx] - [uxbox.util.kdtree :as kd] - [uxbox.worker.impl :as impl] - [uxbox.util.geom.point :as gpt])) - -(defonce tree (kd/create)) - -(defmethod impl/handler :grid-init - [{:keys [sender width height x-axis y-axis] :as opts}] - (time - (kd/setup! tree width height (or x-axis 10) (or y-axis 10))) - (impl/reply! sender nil)) - -(defmethod impl/handler :grid-align - [{:keys [sender point] :as message}] - (let [point [(:x point) (:y point)] - results (kd/nearest tree point 1) - [x y] (ffirst results) - result (gpt/point x y)] - (impl/reply! sender {:point (gpt/point x y)}))) diff --git a/frontend/src/uxbox/worker/selection.cljs b/frontend/src/uxbox/worker/selection.cljs new file mode 100644 index 000000000..53fb3ffa1 --- /dev/null +++ b/frontend/src/uxbox/worker/selection.cljs @@ -0,0 +1,123 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns uxbox.worker.selection + (:require + [cljs.spec.alpha :as s] + [okulary.core :as l] + [promesa.core :as p] + [beicon.core :as rx] + [cuerdas.core :as str] + [uxbox.common.exceptions :as ex] + [uxbox.common.spec :as us] + [uxbox.common.uuid :as uuid] + [uxbox.main.geom :as geom] + [uxbox.worker.impl :as impl] + [uxbox.util.quadtree :as qdt])) + +(defonce state (l/atom {})) + +(declare resolve-object) +(declare index-object) +(declare retrieve-toplevel-shapes) +(declare calculate-bounds) +(declare create-index) + +(defmethod impl/handler :selection/create-index + [{:keys [file-id pages] :as message}] + (js/console.log :selection/create-index file-id) + (letfn [(index-page [state page] + (let [id (:id page) + objects (get-in page [:data :objects]) + objects (reduce resolve-object {} (vals objects)) + index (create-index objects)] + (assoc state id {:index index + :objects objects}))) + (update-state [state] + (reduce index-page state pages))] + + (time + (swap! state update-state)) + nil)) + +(defmethod impl/handler :selection/update-index + [{:keys [page-id objects] :as message}] + (js/console.log :selection/update-index page-id) + (letfn [(update-page [_] + (let [objects (reduce resolve-object {} (vals objects)) + index (create-index objects)] + {:index index :objects objects}))] + (time + (swap! state update page-id update-page)) + nil)) + +(defmethod impl/handler :selection/query + [{:keys [page-id rect] :as message}] + (time + (when-let [{:keys [index objects]} (get @state page-id)] + (let [lookup #(get objects %) + result (-> (qdt/search index (clj->js rect)) + (es6-iterator-seq)) + matches? #(geom/overlaps? % rect)] + (into #{} (comp (map #(unchecked-get % "data")) + (filter matches?) + (map :id)) + result))))) + +(defn- calculate-bounds + [objects] + #js {:x 0 + :y 0 + :width (::width objects) + :height (::height objects)}) + +(defn- create-index + [objects] + (let [bounds (calculate-bounds objects)] + (reduce index-object + (qdt/create bounds) + (->> (retrieve-toplevel-shapes objects) + (map #(get objects %)))))) + +(defn- index-object + [index {:keys [id x y width height] :as obj}] + (let [rect #js {:x x :y y :width width :height height}] + (qdt/insert index rect obj))) + +(defn- resolve-object + [state {:keys [id] :as item}] + (let [item (-> item + (geom/shape->rect-shape) + (geom/resolve-rotation) + (geom/shape->rect-shape)) + width (+ (:x item 0) (:width item 0)) + height (+ (:y item 0) (:height item 0)) + max (fnil max 0)] + (-> state + (assoc id item) + (update ::width max width) + (update ::height max height)))) + +(defn- retrieve-toplevel-shapes + [objects] + (let [lookup #(get objects %) + root (lookup uuid/zero) + childs (:shapes root)] + (loop [id (first childs) + ids (rest childs) + res []] + (if (nil? id) + res + (let [obj (lookup id) + typ (:type obj)] + (recur (first ids) + (rest ids) + (if (= :frame typ) + (into res (:shapes obj)) + (conj res id))))))))