mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 14:00:37 +00:00
🚧 WIP Step 11, browser bridge
- New schema:viewport - Browser bridge (with some temp code) - Move prepare-scene out measured regions - AI-assisted-by: muse-spark-1.3, deepseek-flash, glm-5.3
This commit is contained in:
parent
4e9d83b4ad
commit
5afac4a330
@ -5,29 +5,379 @@
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns benches.render-wasm.browser
|
||||
"Browser entry for the CLJS renderer benchmark suite.
|
||||
"Browser bridge for the benchmarks.
|
||||
|
||||
Requiring `app.render-wasm.api.enums` binds the enum table from the
|
||||
prepared renderer build's `shared.js` at namespace load time. A missing
|
||||
`shared.js` fails the compile; a stale or mismatched one throws at load.
|
||||
Lifecycle here mirrors the editor cold-load path
|
||||
(`app.render-wasm.api/initialize-viewport` + `set-objects` sync).
|
||||
|
||||
The exported `bridge` is the only handle the Node runner uses inside the
|
||||
page. Keep the boundary plain data: never pass a compiled CLJS closure
|
||||
through `page.evaluate`."
|
||||
This ns exports `bridge`, which is what the Node 'pilot' uses inside the page.
|
||||
We only ever pass data through `page.evaluate`.
|
||||
|
||||
All `bridge` entries resolve, that is they never return an unhandled rejected
|
||||
promise.
|
||||
|
||||
Failures are returned as `{\"status\" \"failed\" ...}` maps."
|
||||
(:require
|
||||
[app.render-wasm.api.enums]))
|
||||
[app.common.render-wasm.helpers :as h]
|
||||
[app.common.render-wasm.serialize-shape :as serialize-shape]
|
||||
[app.common.render-wasm.serializers :as sr]
|
||||
[app.common.render-wasm.wasm :as wasm]
|
||||
[app.common.schema :as sm]
|
||||
;; bind the enum table from the prepared renderer build's `shared.js` so
|
||||
;; a missing `shared.js` fails the compile. A stale or mismatched one throws
|
||||
;; at load
|
||||
[app.render-wasm.api.enums]
|
||||
[app.render-wasm.api.webgl :as webgl]
|
||||
[benches.render-wasm.scenes.common :as scenes]
|
||||
[benches.render-wasm.scenes.core :as core]
|
||||
[benches.render-wasm.scenes.rects :as rects]
|
||||
[benches.render-wasm.upload :as upload]))
|
||||
|
||||
;; TODO(mem:render-wasm/performance/cljs-rewrite/06-render-and-interaction-protocol):
|
||||
;; move these to the shared WASM layer and delete them here. They mirror
|
||||
;; `app.render-wasm.api/FRAME_TYPE_*` until ticket 06 owns the protocol.
|
||||
(def ^:private frame-full 2)
|
||||
|
||||
(def ^:private default-view
|
||||
{:scale 1 :x 0 :y 0})
|
||||
|
||||
(def ^:private poll-frame-budget
|
||||
"Own deadline for the interim Full poll: at most this many rAF frames."
|
||||
600)
|
||||
|
||||
(def ^:private poll-ms-budget
|
||||
"Own deadline for the interim Full poll: at most this many wall ms."
|
||||
15000)
|
||||
|
||||
(def schema:load-args
|
||||
"Bridge-boundary schema for `load-rects`. Invalid args produce a failure
|
||||
map before any timer starts, never inside timed bodies."
|
||||
[:map {:closed true}
|
||||
[:seed [:int {:min 0 :max 4294967295}]]
|
||||
[:view {:optional true} core/schema:view]
|
||||
[:module-url {:optional true} string?]
|
||||
[:wasm-url {:optional true} string?]])
|
||||
|
||||
;; Monotonic owner generation. Every async continuation compares its
|
||||
;; captured epoch and drops stale results silently; dispose/load bump it.
|
||||
(defonce ^:private owner-epoch*
|
||||
(atom 0))
|
||||
|
||||
;; Cached Emscripten import. Import precedes module-init timing (untimed).
|
||||
(defonce ^:private module-promise*
|
||||
(atom nil))
|
||||
|
||||
;; Page-scoped canvas owned by the current load. Fresh page per attempt in
|
||||
;; the future runner; the pilot loads once.
|
||||
(defonce ^:private canvas*
|
||||
(atom nil))
|
||||
|
||||
(defn- fail-data
|
||||
[m]
|
||||
(clj->js (assoc m "status" "failed")))
|
||||
|
||||
(defn- mark-live!
|
||||
[]
|
||||
(reset! wasm/context-lost? false)
|
||||
(set! wasm/context-initialized? true))
|
||||
|
||||
(defn- now
|
||||
[]
|
||||
(.now js/performance))
|
||||
|
||||
(defn- ensure-module!
|
||||
"Imports the Emscripten glue (cached per page, untimed: script import
|
||||
precedes module-init timing) and instantiates it with `locateFile`
|
||||
pointing at the prepared `.wasm` bytes. Resolves
|
||||
`{:module instance :factory-ms ms}` where factory-ms covers the
|
||||
instantiate call only."
|
||||
[module-url wasm-url]
|
||||
(when (nil? @module-promise*)
|
||||
(reset! module-promise*
|
||||
(-> (js/dynamicImport module-url)
|
||||
(.then (fn [module]
|
||||
(let [t0 (now)
|
||||
default-fn (unchecked-get module "default")]
|
||||
(-> (default-fn #js {:locateFile (constantly wasm-url)})
|
||||
(.then (fn [instance]
|
||||
{:module instance
|
||||
:factory-ms (- (now) t0)})))))))))
|
||||
@module-promise*)
|
||||
|
||||
(defn- renderer-string
|
||||
"Reads the unmasked renderer (SwiftShader/llvmpipe detection for the
|
||||
software-renderer warning). The init helper forces the debug extension."
|
||||
[context]
|
||||
(try
|
||||
(let [ext (.getExtension ^js context "WEBGL_debug_renderer_info")]
|
||||
(if (some? ext)
|
||||
(str (.getParameter ^js context (unchecked-get ext "UNMASKED_RENDERER_WEBGL")))
|
||||
"unknown"))
|
||||
(catch :default _ "unknown")))
|
||||
|
||||
(defn- software-renderer?
|
||||
[s]
|
||||
(boolean (re-find #"(?i)swiftshader|llvmpipe|software|basic render" (or s ""))))
|
||||
|
||||
(defn- poll-full
|
||||
"TODO(mem:render-wasm/performance/cljs-rewrite/06-render-and-interaction-protocol):
|
||||
interim inline drain. Ticket 06 replaces this with `protocol.cljs`; do not
|
||||
extend it. Resolves `{:ms [...] :frames n}` at Full, `{:full false ...}` on
|
||||
budget exhaustion, and checks the owner epoch every frame. A synchronous
|
||||
throw inside a frame rejects: rAF callbacks run outside the executor, so
|
||||
without this the bridge promise would hang."
|
||||
[epoch started-frames started-ms]
|
||||
(js/Promise.
|
||||
(fn [resolve reject]
|
||||
(letfn [(step [frames]
|
||||
(if (not= epoch @owner-epoch*)
|
||||
(resolve #js {"stale" true})
|
||||
(let [frame-type (try
|
||||
(h/call wasm/internal-module "_render" (now) 0)
|
||||
(catch :default cause
|
||||
(reject cause)
|
||||
::thrown))]
|
||||
(when-not (= frame-type ::thrown)
|
||||
(cond
|
||||
(= frame-type frame-full)
|
||||
(resolve #js {"full" true
|
||||
"frames" (- frames started-frames)
|
||||
"ms" (- (now) started-ms)})
|
||||
|
||||
(or (>= frames (+ started-frames poll-frame-budget))
|
||||
(>= (- (now) started-ms) poll-ms-budget))
|
||||
(resolve #js {"full" false
|
||||
"frames" (- frames started-frames)
|
||||
"ms" (- (now) started-ms)
|
||||
"frameType" frame-type})
|
||||
|
||||
:else
|
||||
(.requestAnimationFrame
|
||||
js/window
|
||||
(fn [] (step (inc frames)))))))))]
|
||||
(.requestAnimationFrame js/window (fn [] (step (inc started-frames))))))))
|
||||
|
||||
(defn- read-graphics
|
||||
"Records effective graphics settings from the declared helper authority
|
||||
plus the observed renderer string."
|
||||
[width height dpr renderer]
|
||||
(let [opts webgl/default-context-options]
|
||||
{"width" width
|
||||
"height" height
|
||||
"dpr" dpr
|
||||
"webgl2" true
|
||||
"antialias" (unchecked-get opts "antialias")
|
||||
"depth" (unchecked-get opts "depth")
|
||||
"stencil" (unchecked-get opts "stencil")
|
||||
"alpha" (unchecked-get opts "alpha")
|
||||
"preserveDrawingBuffer" (unchecked-get opts "preserveDrawingBuffer")
|
||||
"renderer" renderer
|
||||
"software" (software-renderer? renderer)}))
|
||||
|
||||
(defn- render-ok
|
||||
[module-ms graphics-ms upload-ms alive? poll snapshot seed width height dpr renderer]
|
||||
(let [pm (js->clj poll :keywordize-keys true)]
|
||||
(cond
|
||||
(:stale pm)
|
||||
;; dispose! resets loss state, so stale-plus-lost means genuine
|
||||
;; context loss, not disposal.
|
||||
(if @wasm/context-lost?
|
||||
(fail-data {:phase "context-lost"
|
||||
:message "WebGL context lost during render"})
|
||||
#js {"status" "stale"})
|
||||
|
||||
;; Safety net: every loss writer bumps the epoch, so staleness
|
||||
;; normally fires first.
|
||||
(not alive?)
|
||||
(fail-data {:phase "first-render"
|
||||
:message "disposed or context lost during render"})
|
||||
|
||||
(:full pm)
|
||||
(clj->js
|
||||
{"status" "ok"
|
||||
"moduleInitMs" module-ms
|
||||
"graphicsInitMs" graphics-ms
|
||||
"uploadMs" upload-ms
|
||||
"firstRenderMs" (:ms pm)
|
||||
"renderFrames" (:frames pm)
|
||||
"scene" {"shapes" (count (:objects snapshot))
|
||||
"seed" seed}
|
||||
"effectiveGraphics" (read-graphics width height dpr renderer)})
|
||||
|
||||
:else
|
||||
(fail-data {:phase "first-render"
|
||||
:message "Full not reached in budget"
|
||||
:detail pm}))))
|
||||
|
||||
(defn- install-listeners!
|
||||
"Wires context lost/restored handling for `canvas`, epoch-guarded: a stale
|
||||
canvas (removed but not yet GC'd) must not kill the live owner."
|
||||
[canvas epoch]
|
||||
(.addEventListener
|
||||
canvas "webglcontextlost"
|
||||
(let [installed epoch]
|
||||
(fn [e]
|
||||
(.preventDefault ^js e)
|
||||
(when (= installed @owner-epoch*)
|
||||
(reset! wasm/context-lost? true)
|
||||
(swap! owner-epoch* inc)))))
|
||||
(.addEventListener
|
||||
canvas "webglcontextrestored"
|
||||
(let [installed epoch]
|
||||
(fn [_]
|
||||
(when (= installed @owner-epoch*)
|
||||
(swap! owner-epoch* inc))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Public interface
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn dispose!
|
||||
"Closes the current owner: bumps the epoch (in-flight continuations go
|
||||
stale), tears down WASM context state and drops the module handle so a
|
||||
later load re-imports cleanly. Idempotent."
|
||||
[]
|
||||
(swap! owner-epoch* inc)
|
||||
(wasm/reset-context-state!)
|
||||
;; reset-context-state! deliberately preserves loss state for the editor
|
||||
;; reload path; bench disposal owns it explicitly.
|
||||
(reset! wasm/context-lost? false)
|
||||
(set! wasm/internal-module nil)
|
||||
(reset! module-promise* nil)
|
||||
(when-let [canvas @canvas*]
|
||||
(.remove ^js canvas)
|
||||
(reset! canvas* nil))
|
||||
#js {"status" "disposed"})
|
||||
|
||||
;; TODO: remove in Ticket 06
|
||||
(defn load-rects
|
||||
"Loads the `:rects/load` scene and renders it to Full. Takes plain data:
|
||||
`{\"seed\" int, \"view\" {scale x y viewport?}?, \"module-url\"?, \"wasm-url\"?}`.
|
||||
Viewport defaults to 1920x1080@2. Always resolves (failure maps included)."
|
||||
[args]
|
||||
(try
|
||||
(let [params (js->clj (or args #js {}) :keywordize-keys true)
|
||||
view (merge default-view (:view params))
|
||||
seed (:seed params)]
|
||||
(if-not (sm/validate schema:load-args
|
||||
(cond-> {:seed seed}
|
||||
(:view params) (assoc :view (:view params))
|
||||
(:module-url params) (assoc :module-url (:module-url params))
|
||||
(:wasm-url params) (assoc :wasm-url (:wasm-url params))))
|
||||
(js/Promise.resolve
|
||||
(fail-data {:phase "invalid-args"
|
||||
:message "load-rects needs {seed int, view?}"}))
|
||||
(let [epoch (swap! owner-epoch* inc)]
|
||||
;; Supersede: drop the previous owner's canvas now; its
|
||||
;; continuations go stale through the epoch bump above.
|
||||
;; The old GL context frees with the removed canvas.
|
||||
(when-let [old @canvas*]
|
||||
(.remove ^js old))
|
||||
(let [viewport (core/resolve-viewport view)
|
||||
width (:width viewport)
|
||||
height (:height viewport)
|
||||
dpr (:dpr viewport)
|
||||
;; Epoch-only until mark-live!: liveness requires an
|
||||
;; initialized context, which does not exist yet.
|
||||
current? (fn [] (= epoch @owner-epoch*))
|
||||
live? (fn [] (and (= epoch @owner-epoch*) (wasm/live?)))]
|
||||
(-> (ensure-module! (or (:module-url params) "./render-wasm.js")
|
||||
(or (:wasm-url params) "./render-wasm.wasm"))
|
||||
(.then
|
||||
(fn [{:keys [module factory-ms]}]
|
||||
(when-not (current?)
|
||||
(throw (ex-info "stale" {})))
|
||||
(set! wasm/internal-module module)
|
||||
(let [module-ms factory-ms
|
||||
canvas (doto (.createElement js/document "canvas")
|
||||
(aset "width" (* width dpr))
|
||||
(aset "height" (* height dpr)))
|
||||
_ (.appendChild (.-body js/document) canvas)
|
||||
_ (reset! canvas* canvas)
|
||||
g0 (now)
|
||||
ctx (webgl/init-context!
|
||||
canvas
|
||||
{:module module
|
||||
:context-id "webgl2"
|
||||
:css-width width
|
||||
:css-height height
|
||||
:dpr dpr
|
||||
:flags 0
|
||||
:browser (sr/translate-browser :chrome)
|
||||
:params {}})]
|
||||
(if (nil? ctx)
|
||||
(do
|
||||
(dispose!)
|
||||
(fail-data {:phase "graphics-init"
|
||||
:message "WebGL2 context unavailable"}))
|
||||
(do
|
||||
(set! wasm/gl-context-handle (:handle ctx))
|
||||
(set! wasm/gl-context (:context ctx))
|
||||
(set! wasm/canvas canvas)
|
||||
(mark-live!)
|
||||
;; Mirror the editor DOM sizing choice.
|
||||
(h/call wasm/internal-module "_resize_viewbox" width height)
|
||||
(install-listeners! canvas epoch)
|
||||
(let [graphics-ms (- (now) g0)
|
||||
renderer (renderer-string (:context ctx))
|
||||
;; Generation, validation and order derivation
|
||||
;; stay outside timers. rects/build fills
|
||||
;; workload defaults; only the seed varies per
|
||||
;; attempt.
|
||||
snapshot (-> (rects/build {:seed seed})
|
||||
(scenes/validate!))
|
||||
ordered (upload/prepare-scene snapshot)]
|
||||
(when-not (current?)
|
||||
(throw (ex-info "stale" {})))
|
||||
;; Editor order: view before pool. The timed window
|
||||
;; covers view, pool, loading lifecycle, batch and
|
||||
;; tile preparation.
|
||||
(let [u0 (now)]
|
||||
(h/call wasm/internal-module "_set_view"
|
||||
(:scale view) (- (:x view)) (- (:y view)))
|
||||
(h/call wasm/internal-module "_init_shapes_pool"
|
||||
(count (:objects snapshot)))
|
||||
(h/call wasm/internal-module "_begin_loading")
|
||||
(try
|
||||
(serialize-shape/serialize-shapes-batch!
|
||||
ordered
|
||||
{:include-layout? false
|
||||
:include-fills-strokes? true})
|
||||
(finally
|
||||
(h/call wasm/internal-module "_end_loading")))
|
||||
(h/call wasm/internal-module "_set_view_end")
|
||||
(let [upload-ms (- (now) u0)
|
||||
r0 (now)]
|
||||
(-> (poll-full epoch 0 r0)
|
||||
(.then
|
||||
(fn [poll]
|
||||
(render-ok module-ms graphics-ms upload-ms
|
||||
(live?) poll snapshot seed
|
||||
width height dpr renderer))
|
||||
(fn [_]
|
||||
(dispose!)
|
||||
(fail-data {:phase "first-render"
|
||||
:message "render threw"})))))))))))
|
||||
(fn [result]
|
||||
result)
|
||||
(fn [cause]
|
||||
(if (= "stale" (ex-message cause))
|
||||
#js {"status" "stale"}
|
||||
(do
|
||||
(dispose!)
|
||||
(fail-data {:phase "aborted"
|
||||
:message (ex-message cause)}))))))))))
|
||||
(catch :default cause
|
||||
(js/Promise.resolve
|
||||
(fail-data {:phase "setup" :message (ex-message cause)})))))
|
||||
|
||||
(defn ping
|
||||
"Bridge placeholder that checks the Node/browser boundary.
|
||||
|
||||
TODO(mem:render-wasm/performance/cljs-rewrite/05-browser-load-pilot):
|
||||
replace with the renderer load and measurement protocol."
|
||||
"Bridge placeholder that checks the Node/browser boundary."
|
||||
[]
|
||||
#js {"status" "ok"})
|
||||
|
||||
(def bridge
|
||||
"Browser-side bridge exported from the compiled module.
|
||||
|
||||
TODO(mem:render-wasm/performance/cljs-rewrite/06-render-and-interaction-protocol):
|
||||
add `runAttempt`, `dispose` and the render completion protocol."
|
||||
#js {"ping" ping})
|
||||
"Browser-side bridge exported from the compiled module."
|
||||
#js {"ping" ping
|
||||
"loadRects" load-rects
|
||||
"dispose" dispose!})
|
||||
|
||||
@ -51,11 +51,30 @@
|
||||
;; Schemas
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def default-viewport
|
||||
"Viewport used when a case declares no `:viewport`: 1920x1080 at DPR 2.
|
||||
Per-case config; run-level CLI overrides belong to ticket 11."
|
||||
{:width 1920 :height 1080 :dpr 2})
|
||||
|
||||
(def schema:viewport
|
||||
[:map {:closed true}
|
||||
[:width {:optional true} ::sm/positive-safe-number]
|
||||
[:height {:optional true} ::sm/positive-safe-number]
|
||||
[:dpr {:optional true} ::sm/positive-safe-number]])
|
||||
|
||||
(def schema:view
|
||||
[:map {:closed true}
|
||||
[:scale [:and number? pos?]]
|
||||
[:x number?]
|
||||
[:y number?]])
|
||||
[:y number?]
|
||||
[:viewport {:optional true} schema:viewport]])
|
||||
|
||||
(defn resolve-viewport
|
||||
"Merges `default-viewport` under a case view's declared `:viewport`.
|
||||
Collected descriptors keep the declared view; callers resolve at use so
|
||||
existing cases need no edits."
|
||||
[view]
|
||||
(merge default-viewport (:viewport view)))
|
||||
|
||||
(def schema:scene
|
||||
[:map {:closed true}
|
||||
|
||||
@ -20,6 +20,13 @@
|
||||
[app.common.render-wasm.serialize-shape :as serialize-shape]
|
||||
[benches.render-wasm.scenes.common :as common]))
|
||||
|
||||
(defn prepare-scene
|
||||
"Derives the parent-before-child shape vector from a validated `snapshot`.
|
||||
Call it outside measured regions, then pass the result to
|
||||
`serialize-shapes-batch!` inside."
|
||||
[snapshot]
|
||||
(common/upload-order snapshot))
|
||||
|
||||
(defn upload-scene!
|
||||
"Uploads validated `snapshot` with `opts`.
|
||||
|
||||
@ -31,5 +38,4 @@
|
||||
|
||||
Returns the prepared shape vector."
|
||||
[snapshot opts]
|
||||
(let [ordered (common/upload-order snapshot)]
|
||||
(serialize-shape/serialize-shapes-batch! ordered opts)))
|
||||
(serialize-shape/serialize-shapes-batch! (prepare-scene snapshot) opts))
|
||||
|
||||
92
frontend/test/frontend_tests/benches/lifecycle_test.cljs
Normal file
92
frontend/test/frontend_tests/benches/lifecycle_test.cljs
Normal file
@ -0,0 +1,92 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
|
||||
(ns frontend-tests.benches.lifecycle-test
|
||||
"Boundary-fake coverage for the ticket-05 browser lifecycle.
|
||||
|
||||
Full load/render runs headless in the pilot (real WASM + DOM). Here every
|
||||
DOM/FFI boundary is faked or avoided: bridge arg validation (seeds, views,
|
||||
viewports), viewport resolution, disposal state reset, epoch bumps,
|
||||
context-loss flags and bridge shape. No canvas, rAF or module import is
|
||||
exercised: partial-init rendering, live context loss and stale-callback
|
||||
races run headless in the pilot; exhaustive stale-callback unit coverage
|
||||
belongs to ticket 06's protocol-test with its injected clock."
|
||||
(:require
|
||||
[app.common.render-wasm.wasm :as wasm]
|
||||
[benches.render-wasm.browser :as browser]
|
||||
[benches.render-wasm.scenes.core :as core]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
|
||||
(defn- expect-failed-args
|
||||
"Calls `load-rects` with `args` (never touching DOM/FFI: validation runs
|
||||
first) and asserts a failure map. Calls `done` when finished."
|
||||
[args check-phase? done]
|
||||
(-> (.then (browser/load-rects args)
|
||||
(fn [result]
|
||||
(let [m (js->clj result :keywordize-keys true)]
|
||||
(t/is (= "failed" (:status m)))
|
||||
(when check-phase?
|
||||
(t/is (= "invalid-args" (:phase m))))
|
||||
(done))))
|
||||
(.catch (fn [cause]
|
||||
(t/is false (str "must resolve, threw: " cause))
|
||||
(done)))))
|
||||
|
||||
(t/deftest invalid-seed-fails-before-timers
|
||||
(t/async done
|
||||
(expect-failed-args #js {"seed" -1} true done)))
|
||||
|
||||
(t/deftest missing-seed-fails-before-timers
|
||||
(t/async done
|
||||
(expect-failed-args #js {} true done)))
|
||||
|
||||
(t/deftest invalid-view-fails-before-timers
|
||||
(t/async done
|
||||
(expect-failed-args
|
||||
#js {"seed" 42
|
||||
"view" #js {"scale" -1 "x" 0 "y" 0}}
|
||||
true
|
||||
done)))
|
||||
|
||||
(t/deftest invalid-viewport-fails-before-timers
|
||||
(t/async done
|
||||
(expect-failed-args
|
||||
#js {"seed" 42
|
||||
"view" #js {"scale" 1 "x" 0 "y" 0
|
||||
"viewport" #js {"width" -5}}}
|
||||
true
|
||||
done)))
|
||||
|
||||
(t/deftest viewport-defaults-apply-at-use
|
||||
(t/is (= {:width 1920 :height 1080 :dpr 2}
|
||||
(core/resolve-viewport {:scale 1 :x 0 :y 0})))
|
||||
(t/is (= {:width 800 :height 600 :dpr 2}
|
||||
(core/resolve-viewport {:scale 1 :x 0 :y 0
|
||||
:viewport {:width 800 :height 600}}))))
|
||||
|
||||
(t/deftest dispose-resets-lifecycle-state
|
||||
(set! wasm/internal-module #js {})
|
||||
(set! wasm/context-initialized? true)
|
||||
(reset! wasm/context-lost? true)
|
||||
(let [result (browser/dispose!)]
|
||||
(t/is (= "disposed" (unchecked-get result "status")))
|
||||
(t/is (nil? wasm/internal-module))
|
||||
(t/is (false? wasm/context-initialized?))
|
||||
(t/is (false? @wasm/context-lost?)))
|
||||
(t/testing "double dispose is idempotent"
|
||||
(let [result (browser/dispose!)]
|
||||
(t/is (= "disposed" (unchecked-get result "status")))
|
||||
(t/is (nil? wasm/internal-module))))
|
||||
(t/testing "dispose bumps the owner epoch"
|
||||
(let [before @@#'browser/owner-epoch*]
|
||||
(browser/dispose!)
|
||||
(t/is (< before @@#'browser/owner-epoch*)))))
|
||||
|
||||
(t/deftest bridge-exposes-pilot-entries
|
||||
(let [keys (js/Object.keys browser/bridge)]
|
||||
(t/is (some #{"ping"} keys))
|
||||
(t/is (some #{"loadRects"} keys))
|
||||
(t/is (some #{"dispose"} keys))))
|
||||
@ -37,6 +37,11 @@
|
||||
{:objects {uuid/zero root, r1-id r1, r2-id r2}
|
||||
:refs {:r1 r1-id :r2 r2-id}}))
|
||||
|
||||
(t/deftest prepare-scene-derives-upload-order
|
||||
(let [snapshot (common/validate! (sample-snapshot))]
|
||||
(t/is (= (common/upload-order snapshot)
|
||||
(bench-upload/prepare-scene snapshot)))))
|
||||
|
||||
(t/deftest upload-scene-uses-snapshot-order-and-caller-opts
|
||||
(let [snapshot (common/validate! (sample-snapshot))
|
||||
opts {:include-layout? false :include-fills-strokes? true}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user