Display tessellation plans (2/4): size rules and the facet angle (#803)

Per-face target side lengths, the caller's facet angle, refined grid lines, and fixes for underflowing coordinates and straight edges ending exactly on their vertex. Nothing drawn changes yet.

Refs #303

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Irakli KourtisandClaude Opus 5.5 authored and GitHub committed 2026-10-13 14:45:54 -04:00
1 parent 9bb18842e9
commit 8dd583930e
12 files changed
+870 -189

No files matched your search

+16 -46
View File
@@ -1,53 +1,22 @@
//! Ordered, bounded parallel map over independent items (desktop only; the browser build runs
//! serially). Each item runs under [`crate::guard`]; a worker that cannot be spawned runs its
//! chunk on the calling thread, and a worker that dies reports an internal error for its items.
//! Ordered parallel map over independent items, on the process-wide worker pool (desktop
//! only; the browser build runs serially). Each item runs under [`crate::guard`], so a panic in
//! one item is that item's error and never reaches the pool.
use crate::{KernelError, Result, guard};
use crate::{Result, guard};
/// Most worker threads.
const MAX_WORKERS: usize = 8;
pub(crate) fn workers() -> usize {
#[cfg(not(target_arch = "wasm32"))]
{
std::thread::available_parallelism().map_or(1, |n| n.get().min(MAX_WORKERS))
}
#[cfg(target_arch = "wasm32")]
{
1
}
}
/// `f` over `items`, results in item order. Runs serially below `min_items` items.
pub(crate) fn map<T: Sync, R: Send>(what: &'static str, items: &[T], min_items: usize, f: impl Fn(&T) -> Result<R> + Sync) -> Vec<Result<R>> {
/// `f` over `items`, results in item order: on the pool when `parallel`, else on the calling
/// thread. Waking the pool's workers costs tens of microseconds, so callers ask for it only
/// when the items carry more work than that (decided from the work, not the item count).
pub(crate) fn map<T: Sync, R: Send>(what: &'static str, items: &[T], parallel: bool, f: impl Fn(&T) -> Result<R> + Sync) -> Vec<Result<R>> {
let one = |item: &T| guard(what, || f(item));
// The browser build has no worker pool: everything runs on the calling thread.
#[cfg(target_arch = "wasm32")]
let _ = parallel;
#[cfg(not(target_arch = "wasm32"))]
{
let workers = workers();
if items.len() >= min_items.max(2) && workers > 1 {
return std::thread::scope(|scope| {
let size = items.len().div_ceil(workers);
let one = &one;
let pending: Vec<_> = items
.chunks(size)
.map(|chunk| {
let handle =
std::thread::Builder::new().name(what.into()).spawn_scoped(scope, move || chunk.iter().map(one).collect::<Vec<_>>()).ok();
(chunk, handle)
})
.collect();
let mut out = Vec::with_capacity(items.len());
for (chunk, handle) in pending {
match handle {
Some(h) => match h.join() {
Ok(results) => out.extend(results),
Err(_) => out.extend(chunk.iter().map(|_| Err(KernelError::Internal(format!("{what}: worker stopped"))))),
},
None => out.extend(chunk.iter().map(one)),
}
}
out
});
use rayon::prelude::*;
if parallel && items.len() > 1 && rayon::current_num_threads() > 1 {
return items.par_iter().map(one).collect();
}
}
items.iter().map(one).collect()
@@ -56,11 +25,12 @@ pub(crate) fn map<T: Sync, R: Send>(what: &'static str, items: &[T], min_items:
#[cfg(test)]
mod tests {
use super::*;
use crate::KernelError;
#[test]
fn keeps_order_and_errors() {
let items: Vec<u32> = (0..1000).collect();
let out = map("test", &items, 1, |x| if *x == 500 { Err(KernelError::Invalid("five hundred".into())) } else { Ok(x * 2) });
let out = map("test", &items, true, |x| if *x == 500 { Err(KernelError::Invalid("five hundred".into())) } else { Ok(x * 2) });
assert_eq!(out.len(), 1000);
for (i, r) in out.iter().enumerate() {
match r {