mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-05 11:35:55 +00:00
Kernel choice removed: solidcore is the only kernel
The C++ SMLib kernel goes (owner, 2026-11-02), so nothing chooses a kernel any more: - diagnostics.kernel and its schema types (KernelParams, KernelReply, KernelChoice) are gone, with their tests; - the Geometry kernel preference and its section of the Design page are gone, with assets/locales/pending/geometry-kernel.json. A saved preference file that still names a kernel loads without error and the key is ignored, like any removed setting (saved_preferences_with_a_removed_setting_still_load); - the model cache records "solidcore" as the kernel that computed it, so a cache an earlier build wrote on the C++ kernel is still a miss; - the CLI test of a cache saved on one kernel and opened on the other is gone (the io test covers the miss). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ
This commit is contained in:
@@ -386,44 +386,3 @@ fn a_design_saved_before_names_were_pinned_opens_pinned_and_unchanged() {
|
||||
}
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
|
||||
/// A saved model is its kernel's: the two differ in tolerances and in what they refuse, so a design
|
||||
/// opened on the other kernel is evaluated on it, in both directions; on the kernel that saved it the
|
||||
/// model is taken as it is. A process each, since the kernel is chosen for the process.
|
||||
/// It reaches the C++ kernel on purpose: SOLIDCRAFT_KERNEL=smlib is the option under test.
|
||||
#[cfg(feature = "smlib")]
|
||||
#[test]
|
||||
fn a_model_saved_on_one_kernel_is_evaluated_again_on_the_other() {
|
||||
let cli = || {
|
||||
let mut c = std::process::Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"));
|
||||
c.env_remove("SOLIDCRAFT_NO_MODEL_CACHE");
|
||||
c
|
||||
};
|
||||
// Under the build's own temporary folder: the files are the command line's, not this process's.
|
||||
let temp = |name: &str| Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!("model-cache-{}-{name}.solidcraft", std::process::id()));
|
||||
let script = temp("kernels-script").with_extension("json");
|
||||
let recipe = json!({"commands": [
|
||||
{"command": "solid.box", "params": {"length": 20, "width": 20, "height": 10, "body_name": "Plate"}},
|
||||
{"command": "solid.cylinder", "params": {"diameter": 8, "height": 10, "base": [10, 10, 10], "operation": "join", "targets": ["Plate"]}},
|
||||
]});
|
||||
std::fs::write(&script, recipe.to_string()).unwrap();
|
||||
// Whether opening found a model in the file it may take, whether it took it, and how many
|
||||
// features it evaluated.
|
||||
let open = |design: &Path, kernel: &str| {
|
||||
let out = cli().env("SOLIDCRAFT_KERNEL", kernel).arg("bench-open").arg(design).output().unwrap();
|
||||
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
|
||||
let lines: Vec<Value> = String::from_utf8_lossy(&out.stdout).lines().filter_map(|l| serde_json::from_str(l).ok()).collect();
|
||||
let step = |name: &str| lines.iter().find(|l| l["step"] == name).cloned().unwrap();
|
||||
let (parse, evaluated) = (step("parse"), step("evaluate"));
|
||||
(parse["saved_model"].as_bool().unwrap(), evaluated["adopted"].as_bool().unwrap(), evaluated["recomputed"].as_u64().unwrap())
|
||||
};
|
||||
for (saved, other) in [("solidcore", "smlib"), ("smlib", "solidcore")] {
|
||||
let design = temp(&format!("kernels-{saved}"));
|
||||
let out = cli().env("SOLIDCRAFT_KERNEL", saved).arg("run").arg(&script).arg("--save").arg(&design).output().unwrap();
|
||||
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
|
||||
assert_eq!(open(&design, saved), (true, true, 0), "saved on {saved}, opened on it");
|
||||
assert_eq!(open(&design, other), (false, false, 2), "saved on {saved}, opened on {other}");
|
||||
std::fs::remove_file(&design).unwrap();
|
||||
}
|
||||
std::fs::remove_file(&script).unwrap();
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"cmd.diagnostics_kernel": "Geometry Kernel",
|
||||
"prefs.geometry_kernel": "Geometry kernel",
|
||||
"prefs.geometry_kernel_environment": "The SOLIDCRAFT_KERNEL environment variable is set; it wins over this setting.",
|
||||
"prefs.geometry_kernel_note": "solidcore, the default, answers every operation. Choose SMLib for the C++ kernel. A solidcore failure is reported, never retried on SMLib.",
|
||||
"prefs.geometry_kernel_unavailable": "SMLib is not available in this build. Operations run on solidcore."
|
||||
},
|
||||
"ar": {
|
||||
"cmd.diagnostics_kernel": "نواة الهندسة",
|
||||
"prefs.geometry_kernel": "نواة الهندسة",
|
||||
"prefs.geometry_kernel_environment": "متغير البيئة SOLIDCRAFT_KERNEL مضبوط، وله الأولوية على هذا الإعداد.",
|
||||
"prefs.geometry_kernel_note": "يجيب solidcore، وهو الافتراضي، عن كل العمليات. اختر SMLib لاستخدام نواة C++. ويُبلَّغ عن إخفاق solidcore ولا تُعاد المحاولة أبدًا على SMLib.",
|
||||
"prefs.geometry_kernel_unavailable": "SMLib غير متاح في هذا الإصدار. تعمل العمليات على solidcore."
|
||||
},
|
||||
"de": {
|
||||
"cmd.diagnostics_kernel": "Geometriekernel",
|
||||
"prefs.geometry_kernel": "Geometriekernel",
|
||||
"prefs.geometry_kernel_environment": "Die Umgebungsvariable SOLIDCRAFT_KERNEL ist gesetzt; sie hat Vorrang vor dieser Einstellung.",
|
||||
"prefs.geometry_kernel_note": "solidcore, die Standardeinstellung, führt alle Vorgänge aus. Wählen Sie SMLib für den C++-Kernel. Ein Fehler in solidcore wird gemeldet, nie mit SMLib wiederholt.",
|
||||
"prefs.geometry_kernel_unavailable": "SMLib ist in diesem Build nicht verfügbar. Vorgänge laufen auf solidcore."
|
||||
},
|
||||
"fr": {
|
||||
"cmd.diagnostics_kernel": "Noyau géométrique",
|
||||
"prefs.geometry_kernel": "Noyau géométrique",
|
||||
"prefs.geometry_kernel_environment": "La variable d’environnement SOLIDCRAFT_KERNEL est définie ; elle l’emporte sur ce réglage.",
|
||||
"prefs.geometry_kernel_note": "solidcore, le réglage par défaut, traite toutes les opérations. Choisissez SMLib pour le noyau C++. Un échec de solidcore est signalé, jamais réessayé avec SMLib.",
|
||||
"prefs.geometry_kernel_unavailable": "SMLib n’est pas disponible dans cette version. Les opérations s’exécutent sur solidcore."
|
||||
},
|
||||
"he": {
|
||||
"cmd.diagnostics_kernel": "ליבת גאומטריה",
|
||||
"prefs.geometry_kernel": "ליבת גאומטריה",
|
||||
"prefs.geometry_kernel_environment": "משתנה הסביבה SOLIDCRAFT_KERNEL מוגדר, והוא גובר על הגדרה זו.",
|
||||
"prefs.geometry_kernel_note": "solidcore, ברירת המחדל, מבצע את כל הפעולות. בחרו ב-SMLib עבור ליבת ה-C++. כשל של solidcore מדווח, ולעולם אינו מנוסה שוב ב-SMLib.",
|
||||
"prefs.geometry_kernel_unavailable": "SMLib אינו זמין בגרסה זו. הפעולות מתבצעות ב-solidcore."
|
||||
},
|
||||
"ja": {
|
||||
"cmd.diagnostics_kernel": "ジオメトリ カーネル",
|
||||
"prefs.geometry_kernel": "ジオメトリ カーネル",
|
||||
"prefs.geometry_kernel_environment": "環境変数 SOLIDCRAFT_KERNEL が設定されているため、この設定より優先されます。",
|
||||
"prefs.geometry_kernel_note": "既定の solidcore がすべての処理を行います。C++ カーネルを使うには SMLib を選択してください。solidcore の失敗は報告され、SMLib で再試行されることはありません。",
|
||||
"prefs.geometry_kernel_unavailable": "このビルドでは SMLib を利用できません。処理は solidcore で実行されます。"
|
||||
},
|
||||
"vi": {
|
||||
"cmd.diagnostics_kernel": "Nhân hình học",
|
||||
"prefs.geometry_kernel": "Nhân hình học",
|
||||
"prefs.geometry_kernel_environment": "Biến môi trường SOLIDCRAFT_KERNEL đã được đặt; biến này được ưu tiên hơn thiết lập này.",
|
||||
"prefs.geometry_kernel_note": "solidcore, mặc định, xử lý mọi thao tác. Chọn SMLib để dùng nhân C++. Lỗi của solidcore được báo cáo, không bao giờ thử lại trên SMLib.",
|
||||
"prefs.geometry_kernel_unavailable": "Bản dựng này không có SMLib. Các thao tác chạy trên solidcore."
|
||||
},
|
||||
"zh": {
|
||||
"cmd.diagnostics_kernel": "几何内核",
|
||||
"prefs.geometry_kernel": "几何内核",
|
||||
"prefs.geometry_kernel_environment": "已设置环境变量 SOLIDCRAFT_KERNEL,它优先于此设置。",
|
||||
"prefs.geometry_kernel_note": "默认的 solidcore 处理所有操作。如需使用 C++ 内核,请选择 SMLib。solidcore 失败时会报告,绝不会改用 SMLib 重试。",
|
||||
"prefs.geometry_kernel_unavailable": "此版本不提供 SMLib。操作在 solidcore 上运行。"
|
||||
}
|
||||
}
|
||||
@@ -60,17 +60,6 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
inside it; total counts its outermost run only. Recording also starts with SOLIDCRAFT_PROFILE=1, the CLI's \
|
||||
--profile and the app's preference; off, it costs nothing measurable. Read-only",
|
||||
),
|
||||
CommandSpec::new("diagnostics.kernel", "Geometry Kernel", crate::checked!("diagnostics.kernel", kernel))
|
||||
.icon("bug_report")
|
||||
.noundo()
|
||||
.contract(&KERNEL_CONTRACT)
|
||||
.output(output_schema_for::<crate::schema::diagnostics::KernelReply>)
|
||||
.params(
|
||||
"choice?: solidcore | smlib (sets which kernel answers every operation: solidcore, the default = the Rust port; \
|
||||
smlib = the C++ SMLib kernel, refused as not available on a build without it, such as the web app; bodies already \
|
||||
computed keep their results). SOLIDCRAFT_KERNEL, when set, wins over it. Returns {choice, environment}: choice is \
|
||||
the kernel in force, environment whether SOLIDCRAFT_KERNEL chose it",
|
||||
),
|
||||
CommandSpec::new("diagnostics.validation", "Validation", crate::checked!("diagnostics.validation", validation))
|
||||
.icon("bug_report")
|
||||
.noundo()
|
||||
@@ -96,34 +85,6 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
),
|
||||
];
|
||||
|
||||
fn kernel(_: &mut Session, p: &Value) -> Result<Value> {
|
||||
use solidcraft_kernel::kernel_route as route;
|
||||
if let Some(v) = p.get("choice").filter(|v| !v.is_null()) {
|
||||
route::set_choice(kernel_asked(v, solidcraft_kernel::smlib_built())?);
|
||||
}
|
||||
use crate::schema::diagnostics::{KernelChoice, KernelReply};
|
||||
let choice = match route::choice() {
|
||||
route::Backend::Solidcore => KernelChoice::Solidcore,
|
||||
route::Backend::Smlib => KernelChoice::Smlib,
|
||||
};
|
||||
serde_json::to_value(KernelReply { choice, environment: route::overridden() }).map_err(|e| bad("diagnostics.kernel", e.to_string()))
|
||||
}
|
||||
|
||||
/// The kernel `choice` asks for, on a build that has (`built`) the C++ kernel or doesn't. A choice of
|
||||
/// the wrong type is refused like an unknown one, not read as no choice; `smlib` on a build without
|
||||
/// it is refused too, never answered by the other kernel.
|
||||
pub(crate) fn kernel_asked(choice: &Value, built: bool) -> Result<solidcraft_kernel::kernel_route::Backend> {
|
||||
use solidcraft_kernel::kernel_route::Backend;
|
||||
let asked = choice.as_str().and_then(Backend::from_id).ok_or_else(|| bad("diagnostics.kernel", "`choice` is solidcore or smlib"))?;
|
||||
if asked == Backend::Smlib && !built {
|
||||
return Err(EngineError::Disabled(
|
||||
"diagnostics.kernel".into(),
|
||||
"this build doesn't include the C++ SMLib kernel; build with --features smlib".into(),
|
||||
));
|
||||
}
|
||||
Ok(asked)
|
||||
}
|
||||
|
||||
fn dir(cmd: &str) -> Result<std::path::PathBuf> {
|
||||
crash::default_dir().ok_or_else(|| bad(cmd, "no crash folder on this system"))
|
||||
}
|
||||
@@ -182,7 +143,6 @@ fn crash_test(_: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
const PROFILE_CONTRACT: crate::schema::TypedContract<crate::schema::diagnostics::ProfileParams> = crate::schema::TypedContract::new();
|
||||
|
||||
const KERNEL_CONTRACT: crate::schema::TypedContract<crate::schema::diagnostics::KernelParams> = crate::schema::TypedContract::new();
|
||||
const VALIDATION_CONTRACT: crate::schema::TypedContract<crate::schema::diagnostics::ValidationParams> = crate::schema::TypedContract::new();
|
||||
|
||||
fn validation(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
@@ -330,44 +330,6 @@ impl From<solidcraft_kernel::Validation> for ValidationMode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read, or set, which kernel answers every operation.
|
||||
#[derive(Debug, Default, Deserialize, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct KernelParams {
|
||||
/// The kernel to use from now on. Default: leave it as it is. `smlib` is refused on a build
|
||||
/// without the C++ kernel; `SOLIDCRAFT_KERNEL`, when set, wins over it.
|
||||
#[serde(default, deserialize_with = "kernel_choice")]
|
||||
pub choice: Option<KernelChoice>,
|
||||
}
|
||||
|
||||
/// A geometry kernel.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum KernelChoice {
|
||||
/// solidcore, the Rust port of SMLib (the default).
|
||||
Solidcore,
|
||||
/// The C++ SMLib kernel.
|
||||
Smlib,
|
||||
}
|
||||
|
||||
fn kernel_choice<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Option<KernelChoice>, D::Error> {
|
||||
match Value::deserialize(d)? {
|
||||
Value::Null => Ok(None),
|
||||
Value::String(s) if s == "solidcore" => Ok(Some(KernelChoice::Solidcore)),
|
||||
Value::String(s) if s == "smlib" => Ok(Some(KernelChoice::Smlib)),
|
||||
_ => Err(serde::de::Error::custom(super::check::whole("`choice` is solidcore or smlib"))),
|
||||
}
|
||||
}
|
||||
|
||||
/// The reply of `diagnostics.kernel`.
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct KernelReply {
|
||||
/// The kernel in force now.
|
||||
pub choice: KernelChoice,
|
||||
/// Whether `SOLIDCRAFT_KERNEL` chose it.
|
||||
pub environment: bool,
|
||||
}
|
||||
|
||||
/// Read, or set, when SMLib's full check runs on the features computed from now on.
|
||||
#[derive(Debug, Default, Deserialize, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
|
||||
@@ -2,7 +2,7 @@ use serde_json::{Value, json};
|
||||
|
||||
use super::{
|
||||
CrashReport, CrashReportList, CrashReportParams, CrashReportsClearParams, CrashReportsCleared, CrashReportsParams, CrashTestMode,
|
||||
CrashTestParams, KernelParams, ProfileParams, ValidationParams,
|
||||
CrashTestParams, ProfileParams, ValidationParams,
|
||||
};
|
||||
use crate::Session;
|
||||
use crate::schema::tests::{assert_keys, assert_strict_keeps_todays_errors, conforms};
|
||||
@@ -16,7 +16,6 @@ fn schemas_and_the_key_check_agree() {
|
||||
assert_keys(input_schema_for::<CrashTestParams>(), &["mode"]);
|
||||
assert_keys(input_schema_for::<ProfileParams>(), &["enabled", "reset", "top", "trace"]);
|
||||
assert_keys(input_schema_for::<ValidationParams>(), &["mode"]);
|
||||
assert_keys(input_schema_for::<KernelParams>(), &["choice"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -30,8 +29,6 @@ fn strict_keeps_todays_errors() {
|
||||
("diagnostics.profile", json!({"trace": 5})),
|
||||
("diagnostics.validation", json!({"mode": "each step"})),
|
||||
("diagnostics.validation", json!({"mode": true})),
|
||||
("diagnostics.kernel", json!({"choice": "cpp"})),
|
||||
("diagnostics.kernel", json!({"choice": 1})),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -154,94 +151,6 @@ fn diagnostics_profile_reports_commands_features_and_the_kernel() {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Tests that set the process-wide kernel choice run one at a time, and put `solidcore` back
|
||||
/// however they end (a failed assertion included).
|
||||
struct KernelChoice(#[allow(dead_code)] std::sync::MutexGuard<'static, ()>);
|
||||
|
||||
static KERNEL_TESTS: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
|
||||
impl KernelChoice {
|
||||
fn take() -> Self {
|
||||
KernelChoice(KERNEL_TESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for KernelChoice {
|
||||
fn drop(&mut self) {
|
||||
solidcraft_kernel::kernel_route::set_choice(solidcraft_kernel::kernel_route::Backend::Solidcore);
|
||||
}
|
||||
}
|
||||
|
||||
/// diagnostics.kernel says the kernel in force and whether the environment chose it, and its
|
||||
/// `choice` sets it (unless SOLIDCRAFT_KERNEL overrides it).
|
||||
///
|
||||
/// The choice is process-wide and the other tests in this binary build bodies meanwhile, so only
|
||||
/// `solidcore` is set through the command; what `smlib` asks for is read from `kernel_asked`.
|
||||
#[test]
|
||||
fn diagnostics_kernel_reports_and_sets_the_choice() {
|
||||
use solidcraft_kernel::kernel_route as route;
|
||||
let _choice = KernelChoice::take();
|
||||
let mut s = Session::default();
|
||||
let r = s.execute("diagnostics.kernel", &json!({})).unwrap();
|
||||
let environment = route::overridden();
|
||||
assert_eq!(r, json!({ "choice": route::choice().id(), "environment": environment }));
|
||||
let r = s.execute("diagnostics.kernel", &json!({ "choice": "solidcore" })).unwrap();
|
||||
assert!(environment || r["choice"] == json!("solidcore"), "{r}");
|
||||
assert_eq!(crate::cmd::diagnostics::kernel_asked(&json!("solidcore"), false).unwrap(), route::Backend::Solidcore);
|
||||
assert_eq!(crate::cmd::diagnostics::kernel_asked(&json!("solidcore"), true).unwrap(), route::Backend::Solidcore);
|
||||
assert_eq!(crate::cmd::diagnostics::kernel_asked(&json!("smlib"), true).unwrap(), route::Backend::Smlib);
|
||||
}
|
||||
|
||||
/// `smlib` on a build without the C++ kernel is refused as not available, whichever kernel is in
|
||||
/// force, and leaves the choice alone: never an answer from the other kernel. Built with it, the
|
||||
/// command would move the other tests' bodies, so `kernel_asked` alone is asked.
|
||||
#[test]
|
||||
fn diagnostics_kernel_refuses_smlib_on_a_build_without_it() {
|
||||
let _choice = KernelChoice::take();
|
||||
let refused = crate::cmd::diagnostics::kernel_asked(&json!("smlib"), false).unwrap_err();
|
||||
assert!(matches!(&refused, crate::EngineError::Disabled(cmd, _) if cmd == "diagnostics.kernel"), "{refused}");
|
||||
if !solidcraft_kernel::smlib_built() {
|
||||
let mut s = Session::default();
|
||||
let before = s.execute("diagnostics.kernel", &json!({})).unwrap();
|
||||
assert!(matches!(s.execute("diagnostics.kernel", &json!({"choice": "smlib"})), Err(crate::EngineError::Disabled(..))));
|
||||
assert_eq!(s.execute("diagnostics.kernel", &json!({})).unwrap(), before);
|
||||
}
|
||||
}
|
||||
|
||||
/// An unknown or mistyped choice is an error that changes nothing; no hostile value panics.
|
||||
#[test]
|
||||
fn diagnostics_kernel_refuses_unknown_choices() {
|
||||
let _choice = KernelChoice::take();
|
||||
let mut s = Session::default();
|
||||
let before = s.execute("diagnostics.kernel", &json!({})).unwrap()["choice"].clone();
|
||||
let huge = "solidcore".repeat(100_000);
|
||||
for bad in [
|
||||
json!("truck"),
|
||||
json!("SMLIB"),
|
||||
json!(""),
|
||||
json!(huge),
|
||||
json!("routed"),
|
||||
json!("routed\0"),
|
||||
json!(5),
|
||||
json!(true),
|
||||
json!(["smlib"]),
|
||||
json!({"choice": "smlib"}),
|
||||
] {
|
||||
match s.execute("diagnostics.kernel", &json!({ "choice": bad })) {
|
||||
Err(crate::EngineError::BadParams { .. }) => {}
|
||||
other => panic!("`{}` was not refused: {:?}", bad.as_str().map_or(0, str::len), other.map_err(|e| e.to_string())),
|
||||
}
|
||||
}
|
||||
for v in crate::tests::hostile_values() {
|
||||
for p in [json!({ "choice": v.clone() }), v] {
|
||||
if let Err(crate::EngineError::Internal(_, msg)) = s.execute("diagnostics.kernel", &p) {
|
||||
panic!("diagnostics.kernel {p}: {msg}");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(s.execute("diagnostics.kernel", &json!({})).unwrap()["choice"], before);
|
||||
}
|
||||
|
||||
/// Features are checked once each by default; the command reads and sets the mode, and the mode
|
||||
/// is the user's: it stays when another design is started and when everything is computed again.
|
||||
#[test]
|
||||
|
||||
@@ -1548,13 +1548,11 @@ mod tests {
|
||||
manifest.as_object_mut().unwrap().remove("evaluation");
|
||||
entries.insert("cache/model.json".into(), serde_json::to_vec(&manifest).unwrap());
|
||||
assert!(read_design_geometry(&package(&entries)).is_none());
|
||||
// The kernel that computed it is recorded, and another kernel's model is evaluated again:
|
||||
// the same source gives different bodies on the two (a cache without one is a miss too).
|
||||
use solidcraft_kernel::kernel_route::{Backend, choice};
|
||||
assert_eq!(written(&original)["kernel"], json!(choice().id()));
|
||||
let other = if choice() == Backend::Solidcore { Backend::Smlib } else { Backend::Solidcore };
|
||||
let bytes = with(&[("kernel", other.id())]);
|
||||
assert!(read_design_geometry(&bytes).is_none(), "{}'s cache", other.id());
|
||||
// The kernel that computed it is recorded, and a model the C++ kernel computed (an earlier
|
||||
// build's) is evaluated again (a cache without one is a miss too).
|
||||
assert_eq!(written(&original)["kernel"], json!(native_cache::KERNEL));
|
||||
let bytes = with(&[("kernel", "smlib")]);
|
||||
assert!(read_design_geometry(&bytes).is_none(), "the C++ kernel's cache");
|
||||
assert_eq!(crate::read_design(&bytes).unwrap().to_json(), doc.to_json());
|
||||
let mut entries = original.clone();
|
||||
let mut manifest = written(&entries);
|
||||
|
||||
@@ -29,11 +29,10 @@ fn evaluation() -> String {
|
||||
format!("{}/{}-{}", env!("SOLIDCRAFT_EVALUATION_SOURCE"), std::env::consts::ARCH, std::env::consts::OS)
|
||||
}
|
||||
|
||||
/// The geometry kernel that answers in this process: another kernel's cache is not its result (the
|
||||
/// two differ in tolerances and in what they refuse), so a design opened under it evaluates again.
|
||||
fn kernel() -> &'static str {
|
||||
solidcraft_kernel::kernel_route::choice().id()
|
||||
}
|
||||
/// The geometry kernel that computed a cache. A cache another kernel wrote (the C++ SMLib, which
|
||||
/// earlier builds could run on) is not this build's result: the kernels differ in tolerances and in
|
||||
/// what they refuse, so a design opened from it evaluates again.
|
||||
pub(super) const KERNEL: &str = "solidcore";
|
||||
|
||||
/// A 4 x 4 matrix as its 16 numbers, in storage order.
|
||||
fn matrix(t: &[[f64; 4]; 4]) -> Vec<f64> {
|
||||
@@ -302,7 +301,7 @@ impl Encoded {
|
||||
document: digest(document),
|
||||
evaluation: evaluation(),
|
||||
format: FORMAT,
|
||||
kernel: kernel(),
|
||||
kernel: KERNEL,
|
||||
model: records,
|
||||
payload: digest(&data),
|
||||
producer: PRODUCER,
|
||||
@@ -352,7 +351,7 @@ fn current(manifest: &Value, document: &[u8]) -> bool {
|
||||
is("format", FORMAT)
|
||||
&& is("producer", PRODUCER)
|
||||
&& is("evaluation", &evaluation())
|
||||
&& is("kernel", kernel())
|
||||
&& is("kernel", KERNEL)
|
||||
&& is("document", &digest(document))
|
||||
&& source_trusted(env!("SOLIDCRAFT_EVALUATION_SOURCE"), cfg!(any(test, feature = "trust-unknown-source")))
|
||||
}
|
||||
|
||||
+7
-103
@@ -37,8 +37,6 @@ use crate::ui_kit;
|
||||
use crate::viewport::NavMode;
|
||||
|
||||
pub const UNITS: &[&str] = &["mm", "cm", "m", "in", "ft"];
|
||||
/// The Geometry kernel preference's choices, as `diagnostics.kernel` spells them.
|
||||
pub const KERNELS: [&str; 2] = ["solidcore", "smlib"];
|
||||
pub const OPENSCAD_NAV: &str = "openscad";
|
||||
pub const TOUCHPAD_NAV: &str = "touchpad";
|
||||
/// A mouse button that can drag the camera.
|
||||
@@ -1161,11 +1159,6 @@ preference_settings! {
|
||||
/// Up/Down and the wheel over a focused value field step its plain number (`value_step`,
|
||||
/// #2320). Off by default: the keys move the text cursor and the wheel scrolls, as before.
|
||||
step_values: bool = false, PAGE_DESIGN => check_id("ui.step_values");
|
||||
/// Which geometry kernel answers every operation (one of [`KERNELS`]): `solidcore`, the
|
||||
/// default, or `smlib`, the C++ kernel. A saved `routed`, the old default, reads as
|
||||
/// `solidcore` ([`migrate`]). Applied through
|
||||
/// `diagnostics.kernel`; `SOLIDCRAFT_KERNEL`, when set, wins.
|
||||
geometry_kernel: String = "solidcore".into(), PAGE_DESIGN;
|
||||
/// When SMLib's full check runs while features are computed (`diagnostics.validation`):
|
||||
/// `each_feature` (the default) checks the bodies each feature keeps once; `each_step`, as
|
||||
/// before, checks after every sweep, boolean and blend; `off` (experimental) never does.
|
||||
@@ -1633,14 +1626,6 @@ pub struct PrefsWindow {
|
||||
/// The Profile operations preference the profiler last followed: applied when it changes,
|
||||
/// so `diagnostics.profile` (an agent) and SOLIDCRAFT_PROFILE can switch it in between.
|
||||
applied_profile: Option<bool>,
|
||||
/// The Geometry kernel preference `diagnostics.kernel` last applied: applied when it changes
|
||||
/// (and at startup), so an agent's `diagnostics.kernel` in between stands.
|
||||
applied_kernel: Option<String>,
|
||||
/// The choice `SOLIDCRAFT_KERNEL` forces when it overrides the preference (the command's
|
||||
/// `environment` and `choice`).
|
||||
pub(crate) kernel_environment: Option<String>,
|
||||
/// The saved kernel is one this build doesn't have (`smlib` without the C++ kernel): the page says so.
|
||||
pub(crate) kernel_unavailable: bool,
|
||||
/// The appearance last asked of the native window ([`sync_window_theme`]).
|
||||
window_theme: Option<egui::SystemTheme>,
|
||||
/// The perspective angle the view was last drawn with ([`follow_view_fov`]); 0 before the first.
|
||||
@@ -2313,26 +2298,6 @@ pub fn apply(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
app.prefs_window.applied_profile = Some(profile);
|
||||
}
|
||||
// The geometry kernel, through its command (an unknown saved value is the default). `smlib` on
|
||||
// a build without it is refused: the preference page and the status line say so.
|
||||
let kernel = KERNELS.iter().copied().find(|k| *k == app.preferences.geometry_kernel).unwrap_or(KERNELS[0]);
|
||||
if app.prefs_window.applied_kernel.as_deref() != Some(kernel) {
|
||||
match app.session.execute("diagnostics.kernel", &json!({ "choice": kernel })) {
|
||||
Ok(v) => {
|
||||
let environment = v.get("environment").and_then(serde_json::Value::as_bool).unwrap_or(false);
|
||||
app.prefs_window.kernel_environment =
|
||||
environment.then(|| v.get("choice").and_then(serde_json::Value::as_str).unwrap_or_default().to_string());
|
||||
app.prefs_window.kernel_unavailable = false;
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("geometry kernel preference `{kernel}`: {e}");
|
||||
app.prefs_window.kernel_unavailable = true;
|
||||
let message = app.tr("prefs.geometry_kernel_unavailable").to_string();
|
||||
app.set_status(message, true);
|
||||
}
|
||||
}
|
||||
app.prefs_window.applied_kernel = Some(kernel.into());
|
||||
}
|
||||
apply_file_limits(&app.preferences);
|
||||
// The viewport's display quality (an unknown saved value draws at Normal).
|
||||
solidcraft_engine::doc::display_quality::set_current(
|
||||
@@ -4057,10 +4022,6 @@ pub fn migrate(mut p: Prefs) -> Prefs {
|
||||
p.orbit_around = OrbitAround::Cursor;
|
||||
p.orbit_cursor = false;
|
||||
}
|
||||
// `routed`, the old default, ran every operation on solidcore.
|
||||
if p.geometry_kernel == "routed" {
|
||||
p.geometry_kernel = KERNELS[0].into();
|
||||
}
|
||||
if !crate::space_mouse::drives_each_once(p.space_mouse_axis_source) {
|
||||
log::warn!("preferences: the 3D mouse axis assignment {:?} doesn't drive each axis once; using the default", p.space_mouse_axis_source);
|
||||
p.space_mouse_axis_source = crate::space_mouse::SAME_AXES;
|
||||
@@ -4201,10 +4162,6 @@ fn prepare_preferences(
|
||||
{
|
||||
return Err("`validation` must be each_step, each_feature or off".into());
|
||||
}
|
||||
// Before `migrate`, which reads the old saved `routed` as the default: asked for now, it is refused.
|
||||
if p.get("geometry_kernel").is_some_and(|v| !v.as_str().is_some_and(|k| KERNELS.contains(&k))) {
|
||||
return Err(format!("`geometry_kernel` must be one of {}", KERNELS.join(", ")));
|
||||
}
|
||||
if let Some(value) = p.get("navigation_bar") {
|
||||
crate::navigation_bar::validate(value)?;
|
||||
}
|
||||
@@ -4228,9 +4185,6 @@ fn prepare_preferences(
|
||||
if !UNITS.contains(&new.default_units.as_str()) {
|
||||
return Err(format!("units must be one of {}", UNITS.join(", ")));
|
||||
}
|
||||
if !KERNELS.contains(&new.geometry_kernel.as_str()) {
|
||||
return Err(format!("`geometry_kernel` must be one of {}", KERNELS.join(", ")));
|
||||
}
|
||||
if !WINDOW_SYSTEMS.contains(&new.window_system.as_str()) {
|
||||
return Err(format!("`window_system` must be one of {}", WINDOW_SYSTEMS.join(", ")));
|
||||
}
|
||||
@@ -4408,68 +4362,18 @@ mod tests {
|
||||
assert!(!solidcraft_engine::profile::enabled());
|
||||
}
|
||||
|
||||
/// The Geometry kernel preference reaches the kernel through diagnostics.kernel, at startup
|
||||
/// and when it changes, and only a choice the command knows is accepted. `smlib` keeps the C++
|
||||
/// kernel reachable, the behaviour from before solidcore was the default. (The kernel choice is
|
||||
/// process-wide and other tests start harnesses meanwhile, so `smlib` is only set on a build
|
||||
/// without it, where it is refused and nothing moves.)
|
||||
#[test]
|
||||
fn the_geometry_kernel_preference_applies_through_its_command() {
|
||||
use serde_json::json;
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.frames(1);
|
||||
assert_eq!(h.app.prefs_window.applied_kernel.as_deref(), Some("solidcore"), "applied at startup");
|
||||
let r = h.app.session.execute("diagnostics.kernel", &json!({})).unwrap();
|
||||
let environment = (r["environment"] == json!(true)).then(|| r["choice"].as_str().unwrap_or_default().to_string());
|
||||
assert_eq!(h.app.prefs_window.kernel_environment, environment);
|
||||
assert!(h.call("ui.prefs", json!({"geometry_kernel": "truck"}))["error"].as_str().is_some_and(|e| e.contains("geometry_kernel")));
|
||||
// `routed` is gone: a saved one reads as solidcore (below), asked for now it is refused.
|
||||
assert!(h.call("ui.prefs", json!({"geometry_kernel": "routed"}))["error"].as_str().is_some_and(|e| e.contains("geometry_kernel")));
|
||||
assert_eq!(h.app.preferences.geometry_kernel, "solidcore");
|
||||
assert_eq!(super::KERNELS, ["solidcore", "smlib"]);
|
||||
h.call("ui.prefs", json!({"geometry_kernel": "solidcore"}));
|
||||
h.frames(2);
|
||||
assert_eq!(h.app.prefs_window.applied_kernel.as_deref(), Some("solidcore"));
|
||||
assert!(!h.app.prefs_window.kernel_unavailable);
|
||||
// Saved with the other preferences.
|
||||
let saved = serde_json::to_value(&h.app.preferences).unwrap();
|
||||
assert_eq!(saved["geometry_kernel"], json!("solidcore"));
|
||||
// On a build without the C++ kernel, asking for it says so: not a switch to the other one.
|
||||
if !solidcraft_engine::kernel::smlib_built() {
|
||||
h.call("ui.prefs", json!({"geometry_kernel": "smlib"}));
|
||||
h.frames(2);
|
||||
assert!(h.app.prefs_window.kernel_unavailable);
|
||||
assert_eq!(h.app.preferences.geometry_kernel, "smlib");
|
||||
assert_ne!(h.app.session.execute("diagnostics.kernel", &json!({})).unwrap()["choice"], json!("smlib"));
|
||||
h.call("ui.prefs", json!({"geometry_kernel": "solidcore"}));
|
||||
h.frames(2);
|
||||
assert!(!h.app.prefs_window.kernel_unavailable);
|
||||
}
|
||||
}
|
||||
|
||||
/// A saved `routed`, the old default, loads as `solidcore`, without an error; any other value
|
||||
/// this version doesn't know stays as it was saved and runs on the default, and nothing panics.
|
||||
#[test]
|
||||
fn a_saved_routed_kernel_loads_as_solidcore() {
|
||||
use serde_json::json;
|
||||
let read = |saved: serde_json::Value| super::migrate(serde_json::from_value::<super::Prefs>(saved).unwrap()).geometry_kernel;
|
||||
assert_eq!(read(json!({"geometry_kernel": "routed"})), "solidcore");
|
||||
assert_eq!(read(json!({})), "solidcore");
|
||||
assert_eq!(read(json!({"geometry_kernel": "smlib"})), "smlib");
|
||||
assert_eq!(read(json!({"geometry_kernel": "truck"})), "truck");
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.preferences.geometry_kernel = "truck".into();
|
||||
h.frames(2);
|
||||
assert_eq!(h.app.prefs_window.applied_kernel.as_deref(), Some("solidcore"));
|
||||
}
|
||||
|
||||
/// Preferences saved with a setting this version no longer has still load, every other
|
||||
/// setting kept: the removed key is ignored like any unknown one.
|
||||
#[test]
|
||||
fn saved_preferences_with_a_removed_setting_still_load() {
|
||||
use serde_json::json;
|
||||
for removed in ["metric_tap_drill_major_less_pitch", "surface_extrude_symmetric_per_side"] {
|
||||
let saved = json!({"length_decimals": 4, removed: true});
|
||||
for (removed, value) in [
|
||||
("geometry_kernel", json!("smlib")),
|
||||
("geometry_kernel", json!("solidcore")),
|
||||
("metric_tap_drill_major_less_pitch", json!(true)),
|
||||
("surface_extrude_symmetric_per_side", json!(true)),
|
||||
] {
|
||||
let saved = json!({"length_decimals": 4, removed: value});
|
||||
let p = serde_json::from_value::<super::Prefs>(saved.clone()).unwrap();
|
||||
assert_eq!(p.length_decimals, 4, "{removed}");
|
||||
assert_eq!(super::lenient(&saved).length_decimals, 4, "{removed}");
|
||||
|
||||
@@ -136,28 +136,4 @@ fn origin_plane_names(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
setting::hide_origin(app, ui);
|
||||
ui_kit::text(ui, app.tr("ui.hide_origin_note"), ui_kit::Text::Hint("hide_origin"));
|
||||
});
|
||||
// Which geometry kernel answers; `prefs::apply` applies it through diagnostics.kernel.
|
||||
ui_kit::settings_section(ui, "geometry_kernel", app.tr("prefs.geometry_kernel"), |ui| {
|
||||
// Under SOLIDCRAFT_KERNEL the control shows the environment's choice, disabled.
|
||||
let environment = app.prefs_window.kernel_environment.clone();
|
||||
ui.add_enabled_ui(environment.is_none(), |ui| {
|
||||
let options = ["solidcore", "SMLib"];
|
||||
let shown = environment.as_deref().unwrap_or(&app.preferences.geometry_kernel);
|
||||
let mut selected = KERNELS.iter().position(|k| *k == shown).unwrap_or(0);
|
||||
ui_kit::segmented(ui, &mut selected, &options, ui_kit::Segments { intrinsic: true, ..Default::default() });
|
||||
if let Some(kernel) = KERNELS.get(selected)
|
||||
&& environment.is_none()
|
||||
&& app.preferences.geometry_kernel != *kernel
|
||||
{
|
||||
app.preferences.geometry_kernel = (*kernel).into();
|
||||
}
|
||||
});
|
||||
ui_kit::text(ui, app.tr("prefs.geometry_kernel_note"), ui_kit::Text::Hint("geometry_kernel"));
|
||||
if environment.is_some() {
|
||||
ui_kit::text(ui, app.tr("prefs.geometry_kernel_environment"), ui_kit::Text::Hint("geometry_kernel_environment"));
|
||||
}
|
||||
if app.prefs_window.kernel_unavailable {
|
||||
ui_kit::text(ui, app.tr("prefs.geometry_kernel_unavailable"), ui_kit::Text::Hint("geometry_kernel_unavailable"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user