0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-26 06:31:26 -05:00

🎉 Add first impl of wasm-friendly for Shape data structure

This commit is contained in:
Andrey Antukh 2024-10-24 18:35:57 +02:00
parent e7d7291947
commit 043c23899a
7 changed files with 300 additions and 78 deletions

View file

@ -280,7 +280,7 @@
transform (calculate-transform points center selrect)]
[selrect transform (when (some? transform) (gmt/inverse transform))]))
(defn- adjust-shape-flips!
(defn- adjust-shape-flips
"After some tranformations the flip-x/flip-y flags can change we need
to check this before adjusting the selrect"
[shape points]
@ -299,16 +299,16 @@
(cond-> shape
(neg? dot-x)
(cr/update! :flip-x not)
(update :flip-x not)
(neg? dot-x)
(cr/update! :rotation -)
(update :rotation -)
(neg? dot-y)
(cr/update! :flip-y not)
(update :flip-y not)
(neg? dot-y)
(cr/update! :rotation -))))
(update :rotation -))))
(defn- apply-transform-move
"Given a new set of points transformed, set up the rectangle so it keeps
@ -318,9 +318,6 @@
points (gco/transform-points (dm/get-prop shape :points) transform-mtx)
selrect (gco/transform-selrect (dm/get-prop shape :selrect) transform-mtx)
;; NOTE: ensure we start with a fresh copy of shape for mutabilty
shape (cr/clone shape)
shape (if (= type :bool)
(update shape :bool-content gpa/transform-content transform-mtx)
shape)
@ -329,14 +326,14 @@
shape)
shape (if (= type :path)
(update shape :content gpa/transform-content transform-mtx)
(cr/assoc! shape
:x (dm/get-prop selrect :x)
:y (dm/get-prop selrect :y)
:width (dm/get-prop selrect :width)
:height (dm/get-prop selrect :height)))]
(assoc shape
:x (dm/get-prop selrect :x)
:y (dm/get-prop selrect :y)
:width (dm/get-prop selrect :width)
:height (dm/get-prop selrect :height)))]
(-> shape
(cr/assoc! :selrect selrect)
(cr/assoc! :points points))))
(assoc :selrect selrect)
(assoc :points points))))
(defn- apply-transform-generic
@ -346,9 +343,7 @@
(let [points (-> (dm/get-prop shape :points)
(gco/transform-points transform-mtx))
;; NOTE: ensure we have a fresh shallow copy of shape
shape (cr/clone shape)
shape (adjust-shape-flips! shape points)
shape (adjust-shape-flips shape points)
center (gco/points->center points)
selrect (calculate-selrect points center)
@ -367,17 +362,17 @@
shape (if (= type :path)
(update shape :content gpa/transform-content transform-mtx)
(cr/assoc! shape
:x (dm/get-prop selrect :x)
:y (dm/get-prop selrect :y)
:width (dm/get-prop selrect :width)
:height (dm/get-prop selrect :height)))]
(assoc shape
:x (dm/get-prop selrect :x)
:y (dm/get-prop selrect :y)
:width (dm/get-prop selrect :width)
:height (dm/get-prop selrect :height)))]
(-> shape
(cr/assoc! :transform transform)
(cr/assoc! :transform-inverse inverse)
(cr/assoc! :selrect selrect)
(cr/assoc! :points points)
(cr/assoc! :rotation rotation))))))
(assoc :transform transform)
(assoc :transform-inverse inverse)
(assoc :selrect selrect)
(assoc :points points)
(assoc :rotation rotation))))))
(defn- apply-transform
"Given a new set of points transformed, set up the rectangle so it keeps

View file

@ -403,30 +403,30 @@
nil)))
~rsym)))
(defmacro clone
[ssym]
(if (:ns &env)
`(cljs.core/clone ~ssym)
ssym))
;; (defmacro clone
;; [ssym]
;; (if (:ns &env)
;; `(cljs.core/clone ~ssym)
;; ssym))
(defmacro assoc!
"A record specific update operation"
[ssym & pairs]
(if (:ns &env)
(let [pairs (partition-all 2 pairs)]
`(-> ~ssym
~@(map (fn [[ks vs]]
`(cljs.core/-assoc! ~ks ~vs))
pairs)))
`(assoc ~ssym ~@pairs)))
;; (defmacro assoc!
;; "A record specific update operation"
;; [ssym & pairs]
;; (if (:ns &env)
;; (let [pairs (partition-all 2 pairs)]
;; `(-> ~ssym
;; ~@(map (fn [[ks vs]]
;; `(cljs.core/-assoc! ~ks ~vs))
;; pairs)))
;; `(assoc ~ssym ~@pairs)))
(defmacro update!
"A record specific update operation"
[ssym ksym f & params]
(if (:ns &env)
(let [ssym (with-meta ssym {:tag 'js})]
`(cljs.core/assoc! ~ssym ~ksym (~f (. ~ssym ~(property-symbol ksym)) ~@params)))
`(update ~ssym ~ksym ~f ~@params)))
;; (defmacro update!
;; "A record specific update operation"
;; [ssym ksym f & params]
;; (if (:ns &env)
;; (let [ssym (with-meta ssym {:tag 'js})]
;; `(cljs.core/assoc! ~ssym ~ksym (~f (. ~ssym ~(property-symbol ksym)) ~@params)))
;; `(update ~ssym ~ksym ~f ~@params)))
(defmacro define-properties!
"Define properties in the prototype with `.defineProperty`"

View file

@ -19,6 +19,7 @@
[app.common.schema.generators :as sg]
[app.common.transit :as t]
[app.common.types.color :as ctc]
[app.common.types.shape.impl :as impl]
[app.common.types.grid :as ctg]
[app.common.types.plugins :as ctpg]
[app.common.types.shape.attrs :refer [default-color]]
@ -36,7 +37,7 @@
(defn shape?
[o]
(instance? Shape o))
(impl/shape? o))
(def stroke-caps-line #{:round :square})
(def stroke-caps-marker #{:line-arrow :triangle-arrow :square-marker :circle-marker :diamond-marker})
@ -244,7 +245,7 @@
(defn- decode-shape
[o]
(if (map? o)
(map->Shape o)
(impl/map->Shape o)
o))
(defn- shape-generator
@ -268,7 +269,7 @@
(= type :bool))
(merge attrs1 shape attrs3)
(merge attrs1 shape attrs2 attrs3)))))
(sg/fmap map->Shape)))
(sg/fmap impl/map->Shape)))
(def schema:shape
[:and {:title "Shape"
@ -472,7 +473,7 @@
:rotation 0)
:always
(map->Shape))))
(impl/map->Shape))))
(defn setup-rect
"Initializes the selrect and points for a shape."
@ -527,17 +528,3 @@
(assoc :transform-inverse (gmt/matrix)))
(gpr/setup-proportions))))
;; --- SHAPE SERIALIZATION
(t/add-handlers!
{:id "shape"
:class Shape
:wfn #(into {} %)
:rfn map->Shape})
#?(:clj
(fres/add-handlers!
{:name "penpot/shape"
:class Shape
:wfn fres/write-map-like
:rfn (comp map->Shape fres/read-map-like)}))

View file

@ -0,0 +1,228 @@
;; 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) KALEIDOS INC
(ns app.common.types.shape.impl
(:require
#?(:clj [app.common.fressian :as fres])
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.record :as cr]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.transit :as t]
[app.common.uuid :as uuid]
[app.common.geom.rect :as grc]
[cuerdas.core :as str]
[clojure.core :as c]
[clojure.set :as set]))
(def ArrayBuffer js/ArrayBuffer)
(def Float32Array js/Float32Array)
(cr/defrecord Shape [id name type x y width height rotation selrect points
transform transform-inverse parent-id frame-id flip-x flip-y])
(declare ^:private clone-f32-array)
(declare ^:private impl-assoc)
(declare ^:private impl-conj)
(declare ^:private impl-dissoc)
(declare ^:private read-selrect)
(declare ^:private write-selrect)
;; TODO: implement lazy MapEntry
#?(:cljs
(deftype ShapeWithBuffer [buffer delegate]
Object
(toString [coll]
(str "{" (str/join ", " (for [[k v] coll] (str k " " v))) "}"))
(equiv [this other]
(-equiv this other))
;; ICloneable
;; (-clone [_]
;; (let [bf32 (clone-float32-array buffer)]
;; (ShapeWithBuffer. bf32 delegate)))
IWithMeta
(-with-meta [coll meta]
(ShapeWithBuffer. buffer (with-meta delegate meta)))
IMeta
(-meta [coll] (meta delegate))
ICollection
(-conj [coll entry]
(impl-conj coll entry))
IEquiv
(-equiv [coll other]
(c/equiv-map coll other))
IHash
(-hash [coll] (hash (into {} coll)))
ISequential
ISeqable
(-seq [coll]
(cons (find coll :selrect)
(seq delegate)))
ICounted
(-count [coll]
(+ 1 (count delegate)))
ILookup
(-lookup [coll k]
(-lookup coll k nil))
(-lookup [coll k not-found]
(if (= k :selrect)
(read-selrect buffer)
(c/-lookup delegate k not-found)))
IFind
(-find [coll k]
(if (= k :selrect)
(c/MapEntry. k (read-selrect buffer) nil) ; Replace with lazy MapEntry
(c/-find delegate k)))
IAssociative
(-assoc [coll k v]
(impl-assoc coll k v))
(-contains-key? [coll k]
(or (= k :selrect)
(contains? delegate k)))
IMap
(-dissoc [coll k]
(impl-dissoc coll k))
IFn
(-invoke [coll k]
(-lookup coll k))
(-invoke [coll k not-found]
(-lookup coll k not-found))
IPrintWithWriter
(-pr-writer [coll writer opts]
(-write writer (str "#penpot/shape " (:id delegate))))))
(defn shape?
[o]
(or (instance? Shape o)
(instance? ShapeWithBuffer o)))
;; --- SHAPE IMPL
#?(:cljs
(defn- clone-f32-array
[^Float32Array src]
(let [copy (new Float32Array (.-length src))]
(.set copy src)
copy)))
#?(:cljs
(defn- write-selrect
"Write the selrect into the buffer"
[data selrect]
(assert (instance? Float32Array data) "expected instance of float32array")
(aset data 0 (dm/get-prop selrect :x1))
(aset data 1 (dm/get-prop selrect :y1))
(aset data 2 (dm/get-prop selrect :x2))
(aset data 3 (dm/get-prop selrect :y2))))
#?(:cljs
(defn- read-selrect
"Read selrect from internal buffer"
[^Float32Array buffer]
(let [x1 (aget buffer 0)
y1 (aget buffer 1)
x2 (aget buffer 2)
y2 (aget buffer 3)]
(grc/make-rect x1 y1
(- x2 x1)
(- y2 y1)))))
#?(:cljs
(defn- impl-assoc
[coll k v]
(if (= k :selrect)
(let [buffer (clone-f32-array (.-buffer coll))]
(write-selrect buffer v)
(ShapeWithBuffer. buffer (.-delegate coll)))
(let [delegate (.-delegate coll)
delegate' (assoc delegate k v)]
(if (identical? delegate' delegate)
coll
(let [buffer (clone-f32-array (.-buffer coll))]
(ShapeWithBuffer. buffer delegate')))))))
#?(:cljs
(defn- impl-dissoc
[coll k]
(let [delegate (.-delegate coll)
delegate' (dissoc delegate k)]
(if (identical? delegate delegate')
coll
(let [buffer (clone-f32-array (.-buffer coll))]
(ShapeWithBuffer. buffer delegate'))))))
#?(:cljs
(defn- impl-conj
[coll entry]
(if (vector? entry)
(-assoc coll (-nth entry 0) (-nth entry 1))
(loop [ret coll es (seq entry)]
(if (nil? es)
ret
(let [e (first es)]
(if (vector? e)
(recur (-assoc ret (-nth e 0) (-nth e 1))
(next es))
(throw (js/Error. "conj on a map takes map entries or seqables of map entries")))))))))
#?(:cljs
(defn create-shape
"Instanciate a shape from a map"
[data]
(let [selrect (:selrect data)
buffer (new Float32Array 4)]
(write-selrect buffer selrect)
(ShapeWithBuffer. buffer (dissoc data :selrect)))))
;; --- SHAPE SERIALIZATION
(t/add-handlers!
{:id "shape"
:class Shape
:wfn #(into {} %)
:rfn #?(:cljs create-shape
:clj map->Shape)})
(t/add-handlers!
{:id "shape"
:class ShapeWithBuffer
:wfn #(into {} %)
:rfn #?(:cljs create-shape
:clj map->Shape)})
#?(:clj
(fres/add-handlers!
{:name "penpot/shape"
:class Shape
:wfn fres/write-map-like
:rfn (comp map->Shape fres/read-map-like)}))

View file

@ -276,18 +276,20 @@
(when ^boolean render.wasm/enabled?
(mf/with-effect []
(when-let [canvas (mf/ref-val canvas-ref)]
(time (when-let [canvas (mf/ref-val canvas-ref)]
(->> render.wasm/module
(p/fmap (fn [ready?]
(when ready?
(mf/set-ref-val! canvas-init true)
(render.wasm/assign-canvas canvas)))))
(fn []
(render.wasm/clear-canvas))))
(render.wasm/clear-canvas)))))
(mf/with-effect [vbox' base-objects]
(when (mf/ref-val canvas-init)
(render.wasm/draw-objects base-objects zoom vbox'))))
(mf/with-effect [vbox objects-modified]
(let [sem (when (mf/ref-val canvas-init)
(render.wasm/draw-objects objects-modified zoom vbox))]
(partial render.wasm/cancel-draw sem)))
)
(hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool drawing-path?)
(hooks/setup-viewport-size vport viewport-ref)

View file

@ -17,7 +17,8 @@
(defonce ^:dynamic internal-module #js {})
(defonce ^:dynamic internal-gpu-state #js {})
(defn draw-objects [objects zoom vbox]
(defn draw-objects
[objects zoom vbox]
(let [draw-rect (unchecked-get internal-module "_draw_rect")
translate (unchecked-get internal-module "_translate")
reset-canvas (unchecked-get internal-module "_reset_canvas")
@ -35,17 +36,24 @@
(translate gpu-state (- x) (- y)))
(run! (fn [shape]
;; (js/console.log "render-shape" (.-buffer shape))
(let [selrect (dm/get-prop shape :selrect)
x1 (dm/get-prop selrect :x1)
y1 (dm/get-prop selrect :y1)
x2 (dm/get-prop selrect :x2)
y2 (dm/get-prop selrect :y2)]
;; (prn (:id shape) selrect)
(draw-rect gpu-state x1 y1 x2 y2)))
(vals objects))
(flush gpu-state)))))
(def canvas-options
(defn cancel-draw
[sem]
(when (some? sem)
(js/cancelAnimationFrame sem)))
(def ^:private canvas-options
#js {:antialias true
:depth true
:stencil true

View file

@ -181,10 +181,12 @@
[state name]
(let [page-id (get state :current-page-id)
objects (get-in state [:workspace-data :pages-index page-id :objects])
result (or (d/seek (fn [[_ shape]] (= name (:name shape))) objects)
result (or (d/seek (fn [shape] (= name (:name shape))) (vals objects))
(get objects (uuid/uuid name)))]
(logjs name result)
nil))
#_(logjs name result)
result
#_nil))
(defn ^:export dump-object
[name]