forked from 1b-agent/opencadstudio-upstream
Run wire arena regressions in CI and shorten cache documentation
This commit is contained in:
1 parent
ff3262e39d
commit
3253965194
4 files changed
+11
-67
No files matched your search
@@ -19,6 +19,7 @@ jobs:
|
||||
run: |
|
||||
cargo test --lib app::control::tests
|
||||
cargo test --lib io::update_check::tests
|
||||
cargo test --lib scene::pipeline::wire_arena::tests
|
||||
- name: Validate MCP smoke script
|
||||
if: runner.os != 'Windows'
|
||||
run: python3 -m py_compile docs/automation/mcp_smoke.py
|
||||
|
||||
+7
-51
@@ -432,24 +432,10 @@ struct ResidentWireLayout {
|
||||
pub struct PreparedOpenGeometry {
|
||||
pub wires: Arc<Vec<WireModel>>,
|
||||
pub interaction_index: Option<Arc<crate::scene::pick::interaction_index::InteractionIndex>>,
|
||||
/// The per-entity tessellation memo the loader thread filled while building
|
||||
/// `wires`, with the guard hash it was valid under.
|
||||
///
|
||||
/// `prepare_open_geometry` works on a throwaway `Scene`, so this used to be
|
||||
/// dropped with it and the first geometry edit re-tessellated all 186 455
|
||||
/// entities from cold — 1.3 s of work the loader had already done. The
|
||||
/// guard travels with it because the memo is only usable under the state it
|
||||
/// was built for; a mismatch on the receiving side clears it, which is
|
||||
/// exactly the old behaviour.
|
||||
/// Loader tessellations and their guard; a state mismatch clears the memo.
|
||||
pub tess_memo: HashMap<Handle, Arc<Vec<WireModel>>>,
|
||||
pub tess_guard: u64,
|
||||
/// Block definitions the loader thread built while tessellating, keyed as
|
||||
/// `block_defn_cache` keys them but without an epoch — the receiving scene
|
||||
/// stamps its own.
|
||||
///
|
||||
/// Dropped with the throwaway scene before, which is why the first edit
|
||||
/// spent 1147 ms rebuilding definitions the loader had just built from the
|
||||
/// same, unmodified document.
|
||||
/// Loader block definitions; the receiving scene supplies its own epoch.
|
||||
pub block_defns: Vec<(u64, Arc<cache::block_cache::BlockCache>)>,
|
||||
}
|
||||
|
||||
@@ -1859,14 +1845,8 @@ pub struct Scene {
|
||||
/// Boundary source → associative hatch handles. Source edits reuse this
|
||||
/// index instead of scanning the document during every drag step.
|
||||
associative_hatch_source_cache: RefCell<Option<HashMap<Handle, Vec<Handle>>>>,
|
||||
/// Whether the drawing contains any associative centerline or center mark.
|
||||
///
|
||||
/// Refreshing those means scanning every entity and parsing its extended
|
||||
/// data — 1.17 M entities and ~30 ms each on the reproducer, run twice on
|
||||
/// every edit, to find none at all. `None` means not yet determined; the
|
||||
/// first scan records the answer, and later edits only re-examine the
|
||||
/// handles they actually changed. Staying `true` after the last one is
|
||||
/// deleted is merely slower, never wrong.
|
||||
/// Conservative association hint: unknown until scanned, then updated from
|
||||
/// changed entities. Retaining `true` after deletion only costs an extra scan.
|
||||
has_associative_centers: std::cell::Cell<Option<bool>>,
|
||||
/// Tessellated block definitions in block-local coords, keyed by render
|
||||
/// background and block epoch. Model and Paper adapt black/white colours
|
||||
@@ -2589,12 +2569,7 @@ impl Scene {
|
||||
changes
|
||||
}
|
||||
|
||||
/// Whether the associative centerline / center-mark scans can be skipped.
|
||||
///
|
||||
/// Answers from the cached hint when it is known, and otherwise from the
|
||||
/// changed entities alone: if any of them carries an association the hint
|
||||
/// flips to `true` and the scans run from then on. Only an undetermined
|
||||
/// hint costs a full pass, and that happens once.
|
||||
/// Check changed entities and scan the document only when the hint is unknown.
|
||||
fn associative_centers_possible(&self, changes: &[(Handle, ChangeKind)]) -> bool {
|
||||
if changes.iter().any(|(handle, _)| {
|
||||
self.document.get_entity(*handle).is_some_and(|entity| {
|
||||
@@ -7943,23 +7918,8 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the interaction handle index and the insert-hatch map now, while
|
||||
/// the drawing is still opening.
|
||||
///
|
||||
/// Both are built lazily on first use, and first use is the user's first
|
||||
/// hover over the canvas — where the cold build cost 992 ms mid-gesture
|
||||
/// (`hover-detail ... handles=986.1`). They cannot move to the loader
|
||||
/// thread: they read `hatches` / `meshes` / `block_meshes`, which are
|
||||
/// installed on the real `Scene` after the loader is done.
|
||||
///
|
||||
/// This does not make the work cheaper, it moves it to where the user is
|
||||
/// already waiting. A second on an open that is already seconds long reads
|
||||
/// very differently from a second-long freeze on a mouse move.
|
||||
/// Prepare hover indexes after derived geometry is installed during open.
|
||||
pub(crate) fn warm_interaction_caches(&self) {
|
||||
// Both indexes on the hover path, not just one. The handle index alone
|
||||
// left `hover-detail ... handles=384.6` on the first hover: the same
|
||||
// stage also builds the entity quadtree, which is keyed on
|
||||
// `geometry_epoch` and equally cold at that point.
|
||||
let _ = self.entity_index();
|
||||
let _ = self.interaction_handle_index();
|
||||
}
|
||||
@@ -9179,11 +9139,7 @@ impl Scene {
|
||||
if let Some((_, ref idx)) = *cache {
|
||||
if let Some(sort_map) = idx.get(&block_handle) {
|
||||
sorted = true;
|
||||
// `sort_by_cached_key`, not `sort_by_key`: the key parses
|
||||
// the wire's name out of a `String`, and `sort_by_key` calls
|
||||
// it O(n log n) times. On the resident set that is ~198 k
|
||||
// wires parsed eighteen times over — 295 ms on every rebuild,
|
||||
// including rebuilds where nothing was re-tessellated.
|
||||
// Parse each handle once while preserving stable draw order.
|
||||
wires.sort_by_cached_key(|w| {
|
||||
let key = Self::handle_from_wire_name(&w.name)
|
||||
.map(|h| h.value())
|
||||
|
||||
@@ -8,13 +8,8 @@ fn device_ceiling(device: &wgpu::Device) -> usize {
|
||||
(usize::try_from(device.limits().max_buffer_size).unwrap_or(usize::MAX) / 10) * 9
|
||||
}
|
||||
|
||||
/// The wire arena keeps one persistent instance buffer for the whole resident
|
||||
/// set and patches it in place. Capping that at the per-chunk budget pushes any
|
||||
/// drawing past ~524 k instances onto the batched fallback, which re-uploads
|
||||
/// everything on every edit — 200 ms a click on a drawing with 821 k. A single
|
||||
/// long-lived buffer can afford more than a transient chunk, and 128 MiB is
|
||||
/// still four times smaller than the 505 MiB allocation that crashed a 2 GB
|
||||
/// card in the first place.
|
||||
/// Persistent arenas need a larger cap than transient chunks to keep large
|
||||
/// drawings on the incremental upload path.
|
||||
const ARENA_CAP_BYTES: usize = 128 * 1024 * 1024;
|
||||
|
||||
/// `OCS_GPU_CHUNK_MIB`, when set. Overrides both caps so a test can force
|
||||
|
||||
@@ -2419,15 +2419,7 @@ impl Pipeline {
|
||||
Some(&mut self.block_geometry),
|
||||
);
|
||||
|
||||
// Index handle → wire slots once, here, so the per-hover selection
|
||||
// overlay can gather just the highlighted wires instead of scanning +
|
||||
// string-parsing the whole set every time the hovered entity changes.
|
||||
//
|
||||
// Parsing 198 k names is the bulk of this and each one is independent,
|
||||
// so it runs across cores; only the map insertion stays serial, and
|
||||
// that is cheap once the parse is done. Emission order is preserved --
|
||||
// `par_iter().enumerate()` collects in index order -- which the slot
|
||||
// lists rely on.
|
||||
// Parse handles in parallel, preserving wire order for hover slot lists.
|
||||
let t_index = crate::perf::enabled().then(iced::time::Instant::now);
|
||||
let parsed: Vec<(u64, u32)> = {
|
||||
use crate::par::prelude::*;
|
||||
|
||||
Reference in new issue
Block a user