forked from 1b-agent/opencadstudio-upstream
Bundle the first-party Python scripting plugin
This commit is contained in:
1 parent
889042c1f1
commit
de51c1a17f
26 files changed
+11459
-27
No files matched your search
@@ -0,0 +1,81 @@
|
||||
//! Thread-local scratch slot that gives the `ocs` module's native functions
|
||||
//! access to the `HostApi` for the currently running `PY_EVAL` call.
|
||||
//!
|
||||
//! RustPython native functions registered via `#[pymodule]` are plain free
|
||||
//! functions — `add_native_module` takes a `&'static PyModuleDef`, so there is
|
||||
//! no per-call closure environment to capture `host: &mut dyn HostApi` in.
|
||||
//! This is the standard pattern for bridging an embedder's per-call context
|
||||
//! into embedded callbacks (the same shape as Lua userdata or a C callback's
|
||||
//! `void *ctx`): stash a pointer for the duration of one interpreter run, and
|
||||
//! clear it before the run's borrow of `host` ends.
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use ocs_plugin_api::host::HostApi;
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_HOST: Cell<Option<*mut dyn HostApi>> = const { Cell::new(None) };
|
||||
static UNDO_LABEL: Cell<&'static str> = const { Cell::new("") };
|
||||
static UNDO_STARTED: Cell<bool> = const { Cell::new(false) };
|
||||
}
|
||||
|
||||
/// Holds the thread-local host pointer for its lifetime; clears it on drop.
|
||||
/// Must not outlive the `&mut dyn HostApi` borrow it was constructed from.
|
||||
pub(crate) struct HostGuard;
|
||||
|
||||
impl HostGuard {
|
||||
/// `undo_label` is the label a later `ensure_undo_started()` call (from
|
||||
/// an `ocs` write function) will pass to `host.push_undo` — see its doc
|
||||
/// comment for why this is one group per script, not one per call.
|
||||
pub(crate) fn set(host: &mut dyn HostApi, undo_label: &'static str) -> Self {
|
||||
// SAFETY: erases `host`'s borrow lifetime so the fat pointer fits in a
|
||||
// `'static` thread_local slot (same layout either way: two words,
|
||||
// data + vtable). Sound only because `Drop` clears the slot before
|
||||
// this borrow's real lifetime ends — see `with_host`'s safety note.
|
||||
let ptr: *mut dyn HostApi =
|
||||
unsafe { std::mem::transmute::<&mut dyn HostApi, *mut dyn HostApi>(host) };
|
||||
CURRENT_HOST.with(|cell| cell.set(Some(ptr)));
|
||||
UNDO_LABEL.with(|cell| cell.set(undo_label));
|
||||
UNDO_STARTED.with(|cell| cell.set(false));
|
||||
HostGuard
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for HostGuard {
|
||||
fn drop(&mut self) {
|
||||
CURRENT_HOST.with(|cell| cell.set(None));
|
||||
}
|
||||
}
|
||||
|
||||
/// Call from an `ocs` write function (`add_line`, ...) right before it
|
||||
/// mutates the document. Starts exactly one `host.push_undo` group per
|
||||
/// `PY_EVAL`/`PY_RUN` call, however many entities the script adds — a script
|
||||
/// that adds ten lines should undo as one step, not ten. A no-op for a
|
||||
/// script that never writes, so a pure `PY_EVAL 1 + 1` doesn't clutter the
|
||||
/// undo stack.
|
||||
pub(crate) fn ensure_undo_started(host: &mut dyn HostApi) {
|
||||
UNDO_STARTED.with(|started| {
|
||||
if !started.get() {
|
||||
host.push_undo(UNDO_LABEL.with(|label| label.get()));
|
||||
started.set(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Run `f` with the `HostApi` active for the current `PY_EVAL` call, if any.
|
||||
/// Returns `None` outside of a `HostGuard`-covered call (a script should never
|
||||
/// observe this in practice, since the guard spans the whole interpreter run).
|
||||
///
|
||||
/// Not reentrant: `f` must not call `with_host` again itself, since that would
|
||||
/// materialize two live `&mut dyn HostApi` referring to the same host at once.
|
||||
/// Fine for Phase 1.2's `selection()`/`get()`, which don't call each other.
|
||||
pub(crate) fn with_host<R>(f: impl FnOnce(&mut dyn HostApi) -> R) -> Option<R> {
|
||||
CURRENT_HOST.with(|cell| {
|
||||
let ptr = cell.get()?;
|
||||
// SAFETY: `ptr` is only ever set by `HostGuard::set`, which is created
|
||||
// from a live `&mut dyn HostApi` and cleared (via `Drop`) before that
|
||||
// borrow's scope ends. The interpreter run this pointer is valid for
|
||||
// happens entirely on this thread, within that same scope.
|
||||
Some(f(unsafe { &mut *ptr }))
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user