fix: stabilize web loading and paper rendering
This commit is contained in:
parent
fe49cc8eb5
commit
bca65600cd
25 changed files with 109 additions and 62 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,7 +5,6 @@ target
|
||||||
|
|
||||||
# Trunk web build output (wasm/js bundle)
|
# Trunk web build output (wasm/js bundle)
|
||||||
dist
|
dist
|
||||||
web/worker_pkg
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
|
||||||
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3674,6 +3674,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"acadrust",
|
"acadrust",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
"console_error_panic_hook",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ target = "index.html"
|
||||||
# cross-compile to wasm).
|
# cross-compile to wasm).
|
||||||
|
|
||||||
[[hooks]]
|
[[hooks]]
|
||||||
stage = "pre_build"
|
stage = "post_build"
|
||||||
command = "sh"
|
command = "sh"
|
||||||
command_arguments = ["scripts/build-web-worker.sh"]
|
command_arguments = ["scripts/build-web-worker.sh"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ crate-type = ["cdylib"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acadrust = { version = "0.4", features = ["serde"] }
|
acadrust = { version = "0.4", features = ["serde"] }
|
||||||
bincode = "1.3"
|
bincode = "1.3"
|
||||||
|
console_error_panic_hook = "0.1"
|
||||||
getrandom = { version = "0.3", features = ["wasm_js"] }
|
getrandom = { version = "0.3", features = ["wasm_js"] }
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,22 @@ use std::io::Cursor;
|
||||||
|
|
||||||
use acadrust::io::dwg::DwgReader;
|
use acadrust::io::dwg::DwgReader;
|
||||||
use acadrust::DxfReader;
|
use acadrust::DxfReader;
|
||||||
use js_sys::Uint8Array;
|
use js_sys::{Function, Uint8Array};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
/// Parse DWG/DXF on a dedicated browser worker and return a compact serialized
|
/// Parse DWG/DXF on a dedicated browser worker and return a compact serialized
|
||||||
/// document. The main wasm instance only deserializes and installs it, so the
|
/// document. The main wasm instance only deserializes and installs it, so the
|
||||||
/// expensive bit/handle/object decode never occupies the browser UI thread.
|
/// expensive bit/handle/object decode never occupies the browser UI thread.
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn parse_document(name: String, bytes: Uint8Array) -> Result<Uint8Array, JsValue> {
|
pub fn parse_document(
|
||||||
|
name: String,
|
||||||
|
bytes: Uint8Array,
|
||||||
|
report_stage: &Function,
|
||||||
|
) -> Result<Uint8Array, JsValue> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
report_stage.call1(&JsValue::NULL, &JsValue::from_str("copy input"))?;
|
||||||
let bytes = bytes.to_vec();
|
let bytes = bytes.to_vec();
|
||||||
|
report_stage.call1(&JsValue::NULL, &JsValue::from_str("parse document"))?;
|
||||||
let ext = name.rsplit('.').next().unwrap_or_default().to_lowercase();
|
let ext = name.rsplit('.').next().unwrap_or_default().to_lowercase();
|
||||||
let document = match ext.as_str() {
|
let document = match ext.as_str() {
|
||||||
"dwg" => DwgReader::from_stream(Cursor::new(bytes))
|
"dwg" => DwgReader::from_stream(Cursor::new(bytes))
|
||||||
|
|
@ -26,7 +33,9 @@ pub fn parse_document(name: String, bytes: Uint8Array) -> Result<Uint8Array, JsV
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
report_stage.call1(&JsValue::NULL, &JsValue::from_str("serialize document"))?;
|
||||||
let encoded =
|
let encoded =
|
||||||
bincode::serialize(&document).map_err(|error| JsValue::from_str(&error.to_string()))?;
|
bincode::serialize(&document).map_err(|error| JsValue::from_str(&error.to_string()))?;
|
||||||
|
report_stage.call1(&JsValue::NULL, &JsValue::from_str("copy output"))?;
|
||||||
Ok(Uint8Array::from(encoded.as_slice()))
|
Ok(Uint8Array::from(encoded.as_slice()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@
|
||||||
file) so CAD text renders non-Latin scripts on the web. (#141) -->
|
file) so CAD text renders non-Latin scripts on the web. (#141) -->
|
||||||
<link data-trunk rel="copy-dir" href="web/fonts" />
|
<link data-trunk rel="copy-dir" href="web/fonts" />
|
||||||
<link data-trunk rel="copy-file" href="web/ocs-parse-worker.js" />
|
<link data-trunk rel="copy-file" href="web/ocs-parse-worker.js" />
|
||||||
<link data-trunk rel="copy-dir" href="web/worker_pkg" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="loading">
|
<div id="loading">
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
cargo build --release --target wasm32-unknown-unknown --package ocs_web_worker
|
cargo build --release --target wasm32-unknown-unknown --package ocs_web_worker
|
||||||
mkdir -p web/worker_pkg
|
worker_out="${TRUNK_STAGING_DIR:?}/worker_pkg"
|
||||||
|
mkdir -p "$worker_out"
|
||||||
wasm-bindgen \
|
wasm-bindgen \
|
||||||
--target web \
|
--target web \
|
||||||
--out-dir web/worker_pkg \
|
--out-dir "$worker_out" \
|
||||||
--out-name ocs_web_worker \
|
--out-name ocs_web_worker \
|
||||||
target/wasm32-unknown-unknown/release/ocs_web_worker.wasm
|
target/wasm32-unknown-unknown/release/ocs_web_worker.wasm
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,7 @@ pub(super) struct DocumentTab {
|
||||||
pub(super) last_synced_camera_gen: u64,
|
pub(super) last_synced_camera_gen: u64,
|
||||||
/// Render-state key of `scene.document.preview`. Matching saves reuse the
|
/// Render-state key of `scene.document.preview`. Matching saves reuse the
|
||||||
/// encoded DWG thumbnail instead of rescanning every resident wire.
|
/// encoded DWG thumbnail instead of rescanning every resident wire.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub(super) thumbnail_cache_key: Option<super::ThumbnailCacheKey>,
|
pub(super) thumbnail_cache_key: Option<super::ThumbnailCacheKey>,
|
||||||
/// Sentinel "Welcome / Start" tab. Always at index 0 when present.
|
/// Sentinel "Welcome / Start" tab. Always at index 0 when present.
|
||||||
/// Cannot be closed; the viewport area renders a welcome page instead
|
/// Cannot be closed; the viewport area renders a welcome page instead
|
||||||
|
|
@ -463,6 +464,7 @@ impl DocumentTab {
|
||||||
block_edit: None,
|
block_edit: None,
|
||||||
active_mleader_style: "Standard".to_string(),
|
active_mleader_style: "Standard".to_string(),
|
||||||
last_synced_camera_gen: 0,
|
last_synced_camera_gen: 0,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
thumbnail_cache_key: None,
|
thumbnail_cache_key: None,
|
||||||
is_start: false,
|
is_start: false,
|
||||||
pan_mode: false,
|
pan_mode: false,
|
||||||
|
|
|
||||||
|
|
@ -755,7 +755,9 @@ pub(super) struct OpenCADStudio {
|
||||||
pending_close: Option<PendingClose>,
|
pending_close: Option<PendingClose>,
|
||||||
/// Latest save job per stable tab id. Older completions may finish, but
|
/// Latest save job per stable tab id. Older completions may finish, but
|
||||||
/// cannot mark a newer document state clean or redirect its path.
|
/// cannot mark a newer document state clean or redirect its path.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
active_save_jobs: std::collections::HashMap<u64, u64>,
|
active_save_jobs: std::collections::HashMap<u64, u64>,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
save_job_serial: u64,
|
save_job_serial: u64,
|
||||||
/// OS window for the unsaved-changes confirmation dialog.
|
/// OS window for the unsaved-changes confirmation dialog.
|
||||||
|
|
||||||
|
|
@ -866,6 +868,7 @@ pub(super) enum PendingClose {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub(super) enum SavePurpose {
|
pub(super) enum SavePurpose {
|
||||||
Manual,
|
Manual,
|
||||||
SaveAs,
|
SaveAs,
|
||||||
|
|
@ -873,6 +876,7 @@ pub(super) enum SavePurpose {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub(super) enum SaveContinuation {
|
pub(super) enum SaveContinuation {
|
||||||
None,
|
None,
|
||||||
CloseTab,
|
CloseTab,
|
||||||
|
|
@ -880,6 +884,7 @@ pub(super) enum SaveContinuation {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub(super) struct ThumbnailCacheKey {
|
pub(super) struct ThumbnailCacheKey {
|
||||||
epoch: u64,
|
epoch: u64,
|
||||||
camera_generation: u64,
|
camera_generation: u64,
|
||||||
|
|
@ -889,6 +894,7 @@ pub(super) struct ThumbnailCacheKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub struct SaveOutcome {
|
pub struct SaveOutcome {
|
||||||
job_id: u64,
|
job_id: u64,
|
||||||
tab_id: u64,
|
tab_id: u64,
|
||||||
|
|
@ -1463,6 +1469,7 @@ pub enum Message {
|
||||||
/// Periodic autosave tick — write `.sv$` recovery files for dirty tabs.
|
/// Periodic autosave tick — write `.sv$` recovery files for dirty tabs.
|
||||||
AutoSave,
|
AutoSave,
|
||||||
/// Native background save/autosave completed.
|
/// Native background save/autosave completed.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
SaveFinished(SaveOutcome),
|
SaveFinished(SaveOutcome),
|
||||||
// ─────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
CommandInput(String),
|
CommandInput(String),
|
||||||
|
|
@ -2505,7 +2512,9 @@ impl OpenCADStudio {
|
||||||
active_interaction_index: None,
|
active_interaction_index: None,
|
||||||
queued_interaction_indices: std::collections::VecDeque::new(),
|
queued_interaction_indices: std::collections::VecDeque::new(),
|
||||||
pending_close: None,
|
pending_close: None,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
active_save_jobs: std::collections::HashMap::new(),
|
active_save_jobs: std::collections::HashMap::new(),
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
save_job_serial: 0,
|
save_job_serial: 0,
|
||||||
save_dialog_format: "DWG 2018".to_string(),
|
save_dialog_format: "DWG 2018".to_string(),
|
||||||
save_dialog_filename: "drawing.dwg".to_string(),
|
save_dialog_filename: "drawing.dwg".to_string(),
|
||||||
|
|
|
||||||
|
|
@ -783,7 +783,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let clone_started = std::time::Instant::now();
|
let clone_started = iced::time::Instant::now();
|
||||||
let mut snapshot = self.tabs[i].scene.document.clone();
|
let mut snapshot = self.tabs[i].scene.document.clone();
|
||||||
let clone_ms = clone_started.elapsed().as_secs_f64() * 1000.0;
|
let clone_ms = clone_started.elapsed().as_secs_f64() * 1000.0;
|
||||||
if crate::perf::enabled() {
|
if crate::perf::enabled() {
|
||||||
|
|
@ -807,7 +807,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
let (result, refreshed_preview) = std::thread::spawn(move || {
|
let (result, refreshed_preview) = std::thread::spawn(move || {
|
||||||
let mut refreshed_preview = None;
|
let mut refreshed_preview = None;
|
||||||
if let Some((wires, camera, bg_color, png, viewport)) = thumbnail {
|
if let Some((wires, camera, bg_color, png, viewport)) = thumbnail {
|
||||||
let started = std::time::Instant::now();
|
let started = iced::time::Instant::now();
|
||||||
snapshot.preview = crate::io::thumbnail::from_snapshot(
|
snapshot.preview = crate::io::thumbnail::from_snapshot(
|
||||||
&wires,
|
&wires,
|
||||||
&camera,
|
&camera,
|
||||||
|
|
|
||||||
|
|
@ -3954,9 +3954,6 @@ impl OpenCADStudio {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
Message::SaveFinished(outcome) => self.on_save_finished(outcome),
|
Message::SaveFinished(outcome) => self.on_save_finished(outcome),
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
Message::SaveFinished(_) => Task::none(),
|
|
||||||
|
|
||||||
// ── Page Setup ────────────────────────────────────────────────
|
// ── Page Setup ────────────────────────────────────────────────
|
||||||
Message::UpdateCheckResult(latest) => {
|
Message::UpdateCheckResult(latest) => {
|
||||||
let Some(info) = latest else {
|
let Some(info) = latest else {
|
||||||
|
|
|
||||||
|
|
@ -3639,7 +3639,7 @@ impl OpenCADStudio {
|
||||||
let weak = Arc::downgrade(&wires);
|
let weak = Arc::downgrade(&wires);
|
||||||
Some(Task::perform(
|
Some(Task::perform(
|
||||||
async move {
|
async move {
|
||||||
let started = std::time::Instant::now();
|
let started = iced::time::Instant::now();
|
||||||
let index = Arc::new(
|
let index = Arc::new(
|
||||||
crate::scene::pick::interaction_index::InteractionIndex::build(&wires),
|
crate::scene::pick::interaction_index::InteractionIndex::build(&wires),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,12 @@ pub fn config_dir() -> Option<PathBuf> {
|
||||||
|
|
||||||
// ── Last file-dialog directory ───────────────────────────────────────────────
|
// ── Last file-dialog directory ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn last_dir_store() -> &'static Mutex<Option<PathBuf>> {
|
fn last_dir_store() -> &'static Mutex<Option<PathBuf>> {
|
||||||
static STORE: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
|
static STORE: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
|
||||||
STORE.get_or_init(|| {
|
STORE.get_or_init(|| {
|
||||||
|
|
@ -50,11 +53,13 @@ fn last_dir_store() -> &'static Mutex<Option<PathBuf>> {
|
||||||
/// The directory the last file dialog picked or saved into, if it still
|
/// The directory the last file dialog picked or saved into, if it still
|
||||||
/// exists — used to seed the next dialog so pickers reopen where the user
|
/// exists — used to seed the next dialog so pickers reopen where the user
|
||||||
/// left off. Persisted across runs.
|
/// left off. Persisted across runs.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn last_dialog_dir() -> Option<PathBuf> {
|
pub fn last_dialog_dir() -> Option<PathBuf> {
|
||||||
last_dir_store().lock().ok()?.clone().filter(|p| p.is_dir())
|
last_dir_store().lock().ok()?.clone().filter(|p| p.is_dir())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Record the directory of a path a file dialog just returned.
|
/// Record the directory of a path a file dialog just returned.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn remember_dialog_dir(file_path: &Path) {
|
pub fn remember_dialog_dir(file_path: &Path) {
|
||||||
let Some(dir) = file_path.parent().filter(|d| d.is_dir()) else {
|
let Some(dir) = file_path.parent().filter(|d| d.is_dir()) else {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,7 @@ pub async fn pick_and_load_web(
|
||||||
/// a browser file picker (no filesystem path). Shares the post-load fixups with
|
/// a browser file picker (no filesystem path). Shares the post-load fixups with
|
||||||
/// [`load_file`]; raster-image path resolution is skipped (there is no sibling
|
/// [`load_file`]; raster-image path resolution is skipped (there is no sibling
|
||||||
/// directory to search on the web).
|
/// directory to search on the web).
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn load_bytes(name: &str, bytes: Vec<u8>) -> Result<CadDocument, String> {
|
pub fn load_bytes(name: &str, bytes: Vec<u8>) -> Result<CadDocument, String> {
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
let ext = name.rsplit('.').next().unwrap_or_default().to_lowercase();
|
let ext = name.rsplit('.').next().unwrap_or_default().to_lowercase();
|
||||||
|
|
@ -284,6 +285,7 @@ fn sniff_dwg_or_dxf(path: &Path) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn load_file(path: &Path) -> Result<CadDocument, String> {
|
pub fn load_file(path: &Path) -> Result<CadDocument, String> {
|
||||||
load_file_with_progress(path, None)
|
load_file_with_progress(path, None)
|
||||||
}
|
}
|
||||||
|
|
@ -553,7 +555,7 @@ pub fn save_as_version(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
version: acadrust::DxfVersion,
|
version: acadrust::DxfVersion,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clone_started = std::time::Instant::now();
|
let clone_started = iced::time::Instant::now();
|
||||||
let snapshot = doc.clone();
|
let snapshot = doc.clone();
|
||||||
let clone_ms = clone_started.elapsed().as_secs_f64() * 1000.0;
|
let clone_ms = clone_started.elapsed().as_secs_f64() * 1000.0;
|
||||||
save_owned_as_version_inner(snapshot, path, version, false, clone_ms)
|
save_owned_as_version_inner(snapshot, path, version, false, clone_ms)
|
||||||
|
|
@ -581,12 +583,12 @@ fn save_owned_as_version_inner(
|
||||||
clone_ms: f64,
|
clone_ms: f64,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let perf = crate::perf::enabled();
|
let perf = crate::perf::enabled();
|
||||||
let total_started = std::time::Instant::now();
|
let total_started = iced::time::Instant::now();
|
||||||
doc.version = version;
|
doc.version = version;
|
||||||
let styles_started = std::time::Instant::now();
|
let styles_started = iced::time::Instant::now();
|
||||||
sync_current_styles_on_save(&mut doc);
|
sync_current_styles_on_save(&mut doc);
|
||||||
let styles_ms = styles_started.elapsed().as_secs_f64() * 1000.0;
|
let styles_ms = styles_started.elapsed().as_secs_f64() * 1000.0;
|
||||||
let dimensions_started = std::time::Instant::now();
|
let dimensions_started = iced::time::Instant::now();
|
||||||
crate::modules::draw::modify::explode::bake_dimension_blocks(&mut doc);
|
crate::modules::draw::modify::explode::bake_dimension_blocks(&mut doc);
|
||||||
let dimensions_ms = dimensions_started.elapsed().as_secs_f64() * 1000.0;
|
let dimensions_ms = dimensions_started.elapsed().as_secs_f64() * 1000.0;
|
||||||
let temp_path = save_temp_path(path);
|
let temp_path = save_temp_path(path);
|
||||||
|
|
@ -594,7 +596,7 @@ fn save_owned_as_version_inner(
|
||||||
.extension()
|
.extension()
|
||||||
.map(|e| e.to_string_lossy().to_lowercase())
|
.map(|e| e.to_string_lossy().to_lowercase())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let write_started = std::time::Instant::now();
|
let write_started = iced::time::Instant::now();
|
||||||
let result = match ext.as_str() {
|
let result = match ext.as_str() {
|
||||||
"dxf" => DxfWriter::new(&doc)
|
"dxf" => DxfWriter::new(&doc)
|
||||||
.write_to_file(&temp_path)
|
.write_to_file(&temp_path)
|
||||||
|
|
@ -697,18 +699,18 @@ pub fn save_to_bytes(
|
||||||
version: acadrust::DxfVersion,
|
version: acadrust::DxfVersion,
|
||||||
) -> Result<Vec<u8>, String> {
|
) -> Result<Vec<u8>, String> {
|
||||||
let perf = crate::perf::enabled();
|
let perf = crate::perf::enabled();
|
||||||
let total_started = std::time::Instant::now();
|
let total_started = iced::time::Instant::now();
|
||||||
let clone_started = std::time::Instant::now();
|
let clone_started = iced::time::Instant::now();
|
||||||
let mut doc = doc.clone();
|
let mut doc = doc.clone();
|
||||||
let clone_ms = clone_started.elapsed().as_secs_f64() * 1000.0;
|
let clone_ms = clone_started.elapsed().as_secs_f64() * 1000.0;
|
||||||
doc.version = version;
|
doc.version = version;
|
||||||
let styles_started = std::time::Instant::now();
|
let styles_started = iced::time::Instant::now();
|
||||||
sync_current_styles_on_save(&mut doc);
|
sync_current_styles_on_save(&mut doc);
|
||||||
let styles_ms = styles_started.elapsed().as_secs_f64() * 1000.0;
|
let styles_ms = styles_started.elapsed().as_secs_f64() * 1000.0;
|
||||||
let dimensions_started = std::time::Instant::now();
|
let dimensions_started = iced::time::Instant::now();
|
||||||
crate::modules::draw::modify::explode::bake_dimension_blocks(&mut doc);
|
crate::modules::draw::modify::explode::bake_dimension_blocks(&mut doc);
|
||||||
let dimensions_ms = dimensions_started.elapsed().as_secs_f64() * 1000.0;
|
let dimensions_ms = dimensions_started.elapsed().as_secs_f64() * 1000.0;
|
||||||
let write_started = std::time::Instant::now();
|
let write_started = iced::time::Instant::now();
|
||||||
let result = match ext.to_lowercase().as_str() {
|
let result = match ext.to_lowercase().as_str() {
|
||||||
"dxf" => DxfWriter::new(&doc).write_to_vec().map_err(|e| e.to_string()),
|
"dxf" => DxfWriter::new(&doc).write_to_vec().map_err(|e| e.to_string()),
|
||||||
_ => {
|
_ => {
|
||||||
|
|
|
||||||
|
|
@ -49,12 +49,14 @@ pub async fn pick_pdf_path_owned(_stem: String) -> Option<std::path::PathBuf> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// mm to PDF points (1 mm = 2.834645 pt).
|
/// mm to PDF points (1 mm = 2.834645 pt).
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
const MM_TO_PT: f32 = 2.834645;
|
const MM_TO_PT: f32 = 2.834645;
|
||||||
/// `wire.line_weight_px` is the on-screen pixel weight: mm × (96/25.4) × 2.0,
|
/// `wire.line_weight_px` is the on-screen pixel weight: mm × (96/25.4) × 2.0,
|
||||||
/// where the ×2 is a screen-legibility boost (see render.rs). Print wants the
|
/// where the ×2 is a screen-legibility boost (see render.rs). Print wants the
|
||||||
/// true physical weight, so undo both the 96-dpi scaling and the boost before
|
/// true physical weight, so undo both the 96-dpi scaling and the boost before
|
||||||
/// converting to points — otherwise weights export ~2× too heavy in pixels
|
/// converting to points — otherwise weights export ~2× too heavy in pixels
|
||||||
/// (and the old `× 0.35278` left them inconsistent with the physical mm).
|
/// (and the old `× 0.35278` left them inconsistent with the physical mm).
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
const LW_PX_TO_PT: f32 = MM_TO_PT / ((96.0 / 25.4) * 2.0);
|
const LW_PX_TO_PT: f32 = MM_TO_PT / ((96.0 / 25.4) * 2.0);
|
||||||
|
|
||||||
// ── Public entry point ────────────────────────────────────────────────────
|
// ── Public entry point ────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -420,7 +420,7 @@ mod tests {
|
||||||
std::thread::sleep(IO_TIMEOUT * 3);
|
std::thread::sleep(IO_TIMEOUT * 3);
|
||||||
});
|
});
|
||||||
let s = TcpStream::connect((Ipv4Addr::LOCALHOST, port)).unwrap();
|
let s = TcpStream::connect((Ipv4Addr::LOCALHOST, port)).unwrap();
|
||||||
let start = std::time::Instant::now();
|
let start = iced::time::Instant::now();
|
||||||
assert!(!handoff(s, &[PathBuf::from("/tmp/a.dwg")]));
|
assert!(!handoff(s, &[PathBuf::from("/tmp/a.dwg")]));
|
||||||
assert!(
|
assert!(
|
||||||
start.elapsed() < IO_TIMEOUT * 2,
|
start.elapsed() < IO_TIMEOUT * 2,
|
||||||
|
|
|
||||||
|
|
@ -388,6 +388,7 @@ pub fn extract_to_png(input: &std::path::Path, output: &std::path::Path, size: u
|
||||||
/// Read a DWG's embedded preview and decode it to an iced image handle for the
|
/// Read a DWG's embedded preview and decode it to an iced image handle for the
|
||||||
/// Start page's recent-file thumbnails. `None` for DXF/other files, a missing
|
/// Start page's recent-file thumbnails. `None` for DXF/other files, a missing
|
||||||
/// preview, or an undecodable format (WMF).
|
/// preview, or an undecodable format (WMF).
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn read_handle(path: &std::path::Path) -> Option<iced::widget::image::Handle> {
|
pub fn read_handle(path: &std::path::Path) -> Option<iced::widget::image::Handle> {
|
||||||
let img = dwg_thumbnailer::extract(path, MAX_DIM)?;
|
let img = dwg_thumbnailer::extract(path, MAX_DIM)?;
|
||||||
let (w, h) = (img.width(), img.height());
|
let (w, h) = (img.width(), img.height());
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
|
||||||
pub(crate) const EDGE_CHORD_FRAC: f64 = 0.002;
|
pub(crate) const EDGE_CHORD_FRAC: f64 = 0.002;
|
||||||
/// Truck's own triangulation chord tolerance for the cone faces still routed
|
/// Truck's own triangulation chord tolerance for the cone faces still routed
|
||||||
/// through its kernel, as a fraction of the surface radius.
|
/// through its kernel, as a fraction of the surface radius.
|
||||||
|
#[cfg(feature = "solid3d")]
|
||||||
pub(crate) const TRUCK_CHORD_FRAC: f64 = 0.1;
|
pub(crate) const TRUCK_CHORD_FRAC: f64 = 0.1;
|
||||||
/// Boundary-loop sampling for parameter-range classification (which arc of a
|
/// Boundary-loop sampling for parameter-range classification (which arc of a
|
||||||
/// sphere/torus a face covers): a fine fraction so the classification is
|
/// sphere/torus a face covers): a fine fraction so the classification is
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ struct SceneDependencyIndex {
|
||||||
text_styles: HashMap<String, DependencyTargets>,
|
text_styles: HashMap<String, DependencyTargets>,
|
||||||
dim_styles: HashMap<String, DependencyTargets>,
|
dim_styles: HashMap<String, DependencyTargets>,
|
||||||
object_styles: HashMap<Handle, DependencyTargets>,
|
object_styles: HashMap<Handle, DependencyTargets>,
|
||||||
blocks: HashMap<String, HashSet<Handle>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hatch_interaction_aabb(hatch: &model::hatch_model::HatchModel) -> Option<[f64; 4]> {
|
fn hatch_interaction_aabb(hatch: &model::hatch_model::HatchModel) -> Option<[f64; 4]> {
|
||||||
|
|
@ -270,6 +269,7 @@ impl std::fmt::Debug for PreparedOpenGeometry {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||||
pub(crate) struct WireGpuPatch {
|
pub(crate) struct WireGpuPatch {
|
||||||
pub(crate) changes: Arc<Vec<(Handle, ChangeKind)>>,
|
pub(crate) changes: Arc<Vec<(Handle, ChangeKind)>>,
|
||||||
pub(crate) runs: Arc<HashMap<Handle, Arc<Vec<WireModel>>>>,
|
pub(crate) runs: Arc<HashMap<Handle, Arc<Vec<WireModel>>>>,
|
||||||
|
|
@ -280,6 +280,7 @@ pub(crate) struct WireGpuPatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||||
pub(crate) struct WireIndexEdit {
|
pub(crate) struct WireIndexEdit {
|
||||||
pub(crate) handle: Handle,
|
pub(crate) handle: Handle,
|
||||||
pub(crate) start: usize,
|
pub(crate) start: usize,
|
||||||
|
|
@ -414,6 +415,7 @@ pub struct OpenTimings {
|
||||||
|
|
||||||
/// Build hatch / image / mesh caches from a document without needing `&mut Scene`.
|
/// Build hatch / image / mesh caches from a document without needing `&mut Scene`.
|
||||||
/// Intended to run on a background thread during file load.
|
/// Intended to run on a background thread during file load.
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches {
|
pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches {
|
||||||
build_derived_caches_impl(doc, None)
|
build_derived_caches_impl(doc, None)
|
||||||
}
|
}
|
||||||
|
|
@ -6631,10 +6633,7 @@ impl Scene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut index = SceneDependencyIndex {
|
let mut index = SceneDependencyIndex::default();
|
||||||
blocks: roots.clone(),
|
|
||||||
..SceneDependencyIndex::default()
|
|
||||||
};
|
|
||||||
for entity in self.document.entities() {
|
for entity in self.document.entities() {
|
||||||
let common = entity.common();
|
let common = entity.common();
|
||||||
let owner = if common.owner_handle.is_null() {
|
let owner = if common.owner_handle.is_null() {
|
||||||
|
|
@ -6792,18 +6791,6 @@ impl Scene {
|
||||||
self.invalidate_dependency_targets(combined);
|
self.invalidate_dependency_targets(combined);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn block_dependency_handles(&self, name: &str) -> Vec<Handle> {
|
|
||||||
if self.dependency_index_cache.borrow().is_none() {
|
|
||||||
*self.dependency_index_cache.borrow_mut() = Some(self.rebuild_dependency_index());
|
|
||||||
}
|
|
||||||
self.dependency_index_cache
|
|
||||||
.borrow()
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|index| index.blocks.get(&name.to_ascii_uppercase()))
|
|
||||||
.map(|handles| handles.iter().copied().collect())
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn invalidate_dependency_index(&self) {
|
pub(crate) fn invalidate_dependency_index(&self) {
|
||||||
self.dependency_index_cache.borrow_mut().take();
|
self.dependency_index_cache.borrow_mut().take();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -750,7 +750,7 @@ impl InteractionIndex {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let perf = crate::perf::enabled();
|
let perf = crate::perf::enabled();
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let build_started = std::time::Instant::now();
|
let build_started = iced::time::Instant::now();
|
||||||
let wire_handles: Vec<Option<u64>> = wires
|
let wire_handles: Vec<Option<u64>> = wires
|
||||||
.iter()
|
.iter()
|
||||||
.map(|wire| wire.name.parse::<u64>().ok())
|
.map(|wire| wire.name.parse::<u64>().ok())
|
||||||
|
|
@ -770,7 +770,7 @@ impl InteractionIndex {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let handles_elapsed = build_started.elapsed();
|
let handles_elapsed = build_started.elapsed();
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let collect_started = std::time::Instant::now();
|
let collect_started = iced::time::Instant::now();
|
||||||
let mut wire_entries = Vec::with_capacity(wires.len());
|
let mut wire_entries = Vec::with_capacity(wires.len());
|
||||||
let mut unbounded_wires = Vec::new();
|
let mut unbounded_wires = Vec::new();
|
||||||
let mut max_line_half_width_px = 0.0f32;
|
let mut max_line_half_width_px = 0.0f32;
|
||||||
|
|
@ -793,7 +793,7 @@ impl InteractionIndex {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let collect_elapsed = collect_started.elapsed();
|
let collect_elapsed = collect_started.elapsed();
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let flatten_started = std::time::Instant::now();
|
let flatten_started = iced::time::Instant::now();
|
||||||
let mut segment_parts = Vec::with_capacity(per_wire.len());
|
let mut segment_parts = Vec::with_capacity(per_wire.len());
|
||||||
let mut snap_point_parts = Vec::with_capacity(per_wire.len());
|
let mut snap_point_parts = Vec::with_capacity(per_wire.len());
|
||||||
let mut key_vertex_parts = Vec::with_capacity(per_wire.len());
|
let mut key_vertex_parts = Vec::with_capacity(per_wire.len());
|
||||||
|
|
@ -868,7 +868,7 @@ impl InteractionIndex {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let flatten_elapsed = flatten_started.elapsed();
|
let flatten_elapsed = flatten_started.elapsed();
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let spatial_started = std::time::Instant::now();
|
let spatial_started = iced::time::Instant::now();
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let (
|
let (
|
||||||
|
|
|
||||||
|
|
@ -3219,22 +3219,40 @@ impl MultiPipeline {
|
||||||
self.slot_clock = self.slot_clock.wrapping_add(1).max(1);
|
self.slot_clock = self.slot_clock.wrapping_add(1).max(1);
|
||||||
let now = self.slot_clock;
|
let now = self.slot_clock;
|
||||||
let reserved: rustc_hash::FxHashSet<u64> = instance_ids.iter().copied().collect();
|
let reserved: rustc_hash::FxHashSet<u64> = instance_ids.iter().copied().collect();
|
||||||
|
// `inner.slot_id` is updated later in `prepare`, after this whole
|
||||||
|
// method returns. Without a per-call claim set, two new viewports in
|
||||||
|
// the same primitive both see the freshly-grown slot as `MAX` and get
|
||||||
|
// assigned to it. Paper then blits the final occupant's resolve texture
|
||||||
|
// once as the full sheet and again as the floating viewport.
|
||||||
|
let mut claimed: rustc_hash::FxHashSet<usize> =
|
||||||
|
rustc_hash::FxHashSet::default();
|
||||||
let mut slots = Vec::with_capacity(instance_ids.len());
|
let mut slots = Vec::with_capacity(instance_ids.len());
|
||||||
|
|
||||||
for &instance_id in instance_ids {
|
for &instance_id in instance_ids {
|
||||||
let slot = if let Some(&slot) = self.slot_by_instance.get(&instance_id) {
|
let existing = self
|
||||||
|
.slot_by_instance
|
||||||
|
.get(&instance_id)
|
||||||
|
.copied()
|
||||||
|
.filter(|slot| !claimed.contains(slot));
|
||||||
|
let slot = if let Some(slot) = existing {
|
||||||
slot
|
slot
|
||||||
} else {
|
} else {
|
||||||
|
self.slot_by_instance.remove(&instance_id);
|
||||||
let vacant = self
|
let vacant = self
|
||||||
.inners
|
.inners
|
||||||
.iter()
|
.iter()
|
||||||
.position(|inner| inner.slot_id == u64::MAX);
|
.enumerate()
|
||||||
|
.find(|(slot, inner)| {
|
||||||
|
!claimed.contains(slot) && inner.slot_id == u64::MAX
|
||||||
|
})
|
||||||
|
.map(|(slot, _)| slot);
|
||||||
let recyclable = vacant.or_else(|| {
|
let recyclable = vacant.or_else(|| {
|
||||||
(self.inners.len() >= SOFT_LIMIT)
|
(self.inners.len() >= SOFT_LIMIT)
|
||||||
.then(|| {
|
.then(|| {
|
||||||
self.inners
|
self.inners
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
.filter(|(slot, _)| !claimed.contains(slot))
|
||||||
.filter(|(_, inner)| !reserved.contains(&inner.slot_id))
|
.filter(|(_, inner)| !reserved.contains(&inner.slot_id))
|
||||||
.filter(|(slot, _)| {
|
.filter(|(slot, _)| {
|
||||||
now.saturating_sub(self.slot_last_used[*slot]) > HOT_WINDOW
|
now.saturating_sub(self.slot_last_used[*slot]) > HOT_WINDOW
|
||||||
|
|
@ -3256,6 +3274,7 @@ impl MultiPipeline {
|
||||||
self.slot_by_instance.insert(instance_id, slot);
|
self.slot_by_instance.insert(instance_id, slot);
|
||||||
slot
|
slot
|
||||||
};
|
};
|
||||||
|
claimed.insert(slot);
|
||||||
self.slot_last_used[slot] = now;
|
self.slot_last_used[slot] = now;
|
||||||
slots.push(slot);
|
slots.push(slot);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,7 @@ impl WireArena {
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let ranges = handle_ranges(wires)?;
|
let ranges = handle_ranges(wires)?;
|
||||||
let perf = crate::perf::enabled();
|
let perf = crate::perf::enabled();
|
||||||
let total_started = std::time::Instant::now();
|
let total_started = iced::time::Instant::now();
|
||||||
|
|
||||||
// Reject an oversized batch before parallel emission allocates hundreds
|
// Reject an oversized batch before parallel emission allocates hundreds
|
||||||
// of megabytes. `points.len() - 1` is an upper bound because NaN-break
|
// of megabytes. `points.len() - 1` is an upper bound because NaN-break
|
||||||
|
|
@ -363,7 +363,7 @@ impl WireArena {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let pack_started = std::time::Instant::now();
|
let pack_started = iced::time::Instant::now();
|
||||||
use crate::par::prelude::*;
|
use crate::par::prelude::*;
|
||||||
let packed: Vec<PackedSlab> = plans
|
let packed: Vec<PackedSlab> = plans
|
||||||
.par_iter()
|
.par_iter()
|
||||||
|
|
@ -441,7 +441,7 @@ impl WireArena {
|
||||||
let const_cap = ((const_tail as u64 * HEADROOM_NUM / HEADROOM_DEN)
|
let const_cap = ((const_tail as u64 * HEADROOM_NUM / HEADROOM_DEN)
|
||||||
.max(MIN_CONST_CAP)
|
.max(MIN_CONST_CAP)
|
||||||
.min(MAX_CONSTS)) as u32;
|
.min(MAX_CONSTS)) as u32;
|
||||||
let upload_started = std::time::Instant::now();
|
let upload_started = iced::time::Instant::now();
|
||||||
let inst_buf = alloc_inst_initialized(device, inst_cap as u64, &instances);
|
let inst_buf = alloc_inst_initialized(device, inst_cap as u64, &instances);
|
||||||
let const_buf = alloc_const_initialized(device, const_cap as u64, &consts_cpu);
|
let const_buf = alloc_const_initialized(device, const_cap as u64, &consts_cpu);
|
||||||
let upload_ms = upload_started.elapsed().as_secs_f64() * 1000.0;
|
let upload_ms = upload_started.elapsed().as_secs_f64() * 1000.0;
|
||||||
|
|
|
||||||
|
|
@ -224,9 +224,6 @@ impl PackedWireInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
pub type WireInstance = PackedWireInstance;
|
|
||||||
|
|
||||||
/// Wire and hatch pipelines switch together: the fast path uses storage
|
/// Wire and hatch pipelines switch together: the fast path uses storage
|
||||||
/// buffers; the compatibility path carries wire constants in packed vertex
|
/// buffers; the compatibility path carries wire constants in packed vertex
|
||||||
/// attributes and hatch data in a texture.
|
/// attributes and hatch data in a texture.
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@ impl shader::Primitive for Primitive {
|
||||||
bounds: &Rectangle,
|
bounds: &Rectangle,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
) {
|
) {
|
||||||
let nav_prepare_started = std::time::Instant::now();
|
let nav_prepare_started = iced::time::Instant::now();
|
||||||
let phys = viewport.physical_size();
|
let phys = viewport.physical_size();
|
||||||
let full_size = Size::new(phys.width, phys.height);
|
let full_size = Size::new(phys.width, phys.height);
|
||||||
let scale = viewport.scale_factor() as f32;
|
let scale = viewport.scale_factor() as f32;
|
||||||
|
|
@ -436,11 +436,14 @@ impl shader::Primitive for Primitive {
|
||||||
// the whole wire buffer. Only for the scissor-free, mesh-free
|
// the whole wire buffer. Only for the scissor-free, mesh-free
|
||||||
// (single-batch) Model set; scissored paper viewports and mixed
|
// (single-batch) Model set; scissored paper viewports and mixed
|
||||||
// 2D/3D sets fall through to the shared batched path below.
|
// 2D/3D sets fall through to the shared batched path below.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let mut arena_served = false;
|
let mut arena_served = false;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let arena_served = false;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let _perf = crate::perf::enabled();
|
let _perf = crate::perf::enabled();
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let _t0 = std::time::Instant::now();
|
let _t0 = iced::time::Instant::now();
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let mut _patched = false;
|
let mut _patched = false;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
@ -884,7 +887,7 @@ impl shader::Primitive for Primitive {
|
||||||
target: &iced::wgpu::TextureView,
|
target: &iced::wgpu::TextureView,
|
||||||
clip: &Rectangle<u32>,
|
clip: &Rectangle<u32>,
|
||||||
) {
|
) {
|
||||||
let nav_render_started = std::time::Instant::now();
|
let nav_render_started = iced::time::Instant::now();
|
||||||
let cw = clip.width as f32;
|
let cw = clip.width as f32;
|
||||||
let ch = clip.height as f32;
|
let ch = clip.height as f32;
|
||||||
let clip_right = clip.x + clip.width;
|
let clip_right = clip.x + clip.width;
|
||||||
|
|
@ -1403,7 +1406,7 @@ impl Scene {
|
||||||
_hover_region: Option<usize>,
|
_hover_region: Option<usize>,
|
||||||
show_viewcube: bool,
|
show_viewcube: bool,
|
||||||
) -> Primitive {
|
) -> Primitive {
|
||||||
let nav_build_started = std::time::Instant::now();
|
let nav_build_started = iced::time::Instant::now();
|
||||||
let perf_nav = self.take_nav_perf();
|
let perf_nav = self.take_nav_perf();
|
||||||
// Hover comes from the scene cell driven by the app-level
|
// Hover comes from the scene cell driven by the app-level
|
||||||
// `CursorMoved` handler — the cube overlay sits above the shader
|
// `CursorMoved` handler — the cube overlay sits above the shader
|
||||||
|
|
@ -1463,7 +1466,7 @@ impl Scene {
|
||||||
};
|
};
|
||||||
let active = self.active_model_tile.get();
|
let active = self.active_model_tile.get();
|
||||||
let is_active = tile_idx == active;
|
let is_active = tile_idx == active;
|
||||||
let nav_build_started = std::time::Instant::now();
|
let nav_build_started = iced::time::Instant::now();
|
||||||
let perf_nav = if is_active {
|
let perf_nav = if is_active {
|
||||||
self.take_nav_perf()
|
self.take_nav_perf()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1749,14 +1752,17 @@ impl Scene {
|
||||||
// id so an unchanged wire set is not re-walked every frame.
|
// id so an unchanged wire set is not re-walked every frame.
|
||||||
// The reuse fallback inside the gather is keyed per wire SOURCE — the
|
// The reuse fallback inside the gather is keyed per wire SOURCE — the
|
||||||
// sheet, a Model tile, a content viewport and the implicit view carry
|
// sheet, a Model tile, a content viewport and the implicit view carry
|
||||||
// different glyph sets even at the same geometry epoch (#403). Tiles
|
// different glyph sets even at the same geometry epoch (#403). Paper
|
||||||
|
// sheets must also include their layout block: switching layouts does
|
||||||
|
// not change the geometry epoch, and a role-only key reused the prior
|
||||||
|
// sheet's glyph coordinates while its wires moved correctly. Tiles
|
||||||
// share the resident Model set, so they share one key; the implicit
|
// share the resident Model set, so they share one key; the implicit
|
||||||
// view mixes in the current layout block (BEDIT swaps sets without a
|
// view mixes in the current layout block (BEDIT swaps sets without a
|
||||||
// geometry delta).
|
// geometry delta).
|
||||||
let text_source_key: u64 = if inst.tile_idx.is_some() {
|
let text_source_key: u64 = if inst.tile_idx.is_some() {
|
||||||
0x1000_0000_0000_0000
|
0x1000_0000_0000_0000
|
||||||
} else if inst.paper_sheet {
|
} else if inst.paper_sheet {
|
||||||
0x2000_0000_0000_0000
|
0x2000_0000_0000_0000 | self.current_layout_block_handle().value()
|
||||||
} else if inst.handle == acadrust::Handle::NULL {
|
} else if inst.handle == acadrust::Handle::NULL {
|
||||||
0x4000_0000_0000_0000 | self.current_layout_block_handle().value()
|
0x4000_0000_0000_0000 | self.current_layout_block_handle().value()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,16 @@ import init, { parse_document } from "./worker_pkg/ocs_web_worker.js";
|
||||||
const ready = init();
|
const ready = init();
|
||||||
|
|
||||||
self.onmessage = async ({ data }) => {
|
self.onmessage = async ({ data }) => {
|
||||||
|
let stage = "initialize worker";
|
||||||
try {
|
try {
|
||||||
await ready;
|
await ready;
|
||||||
const encoded = parse_document(data.name, new Uint8Array(data.bytes));
|
const encoded = parse_document(
|
||||||
|
data.name,
|
||||||
|
new Uint8Array(data.bytes),
|
||||||
|
(next) => {
|
||||||
|
stage = next;
|
||||||
|
},
|
||||||
|
);
|
||||||
// wasm-bindgen returns a view into WebAssembly.Memory. Copy to a standalone
|
// wasm-bindgen returns a view into WebAssembly.Memory. Copy to a standalone
|
||||||
// ArrayBuffer before transferring it, otherwise the worker's wasm memory
|
// ArrayBuffer before transferring it, otherwise the worker's wasm memory
|
||||||
// itself would be detached.
|
// itself would be detached.
|
||||||
|
|
@ -14,7 +21,9 @@ self.onmessage = async ({ data }) => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
ok: false,
|
ok: false,
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: `${stage}: ${
|
||||||
|
error instanceof Error ? error.stack || error.message : String(error)
|
||||||
|
}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue