updated README and a bunch of other stuff

This commit is contained in:
Sebastian Moser
2026-11-04 20:24:14 +01:00
parent 6eedb1826a
commit 9707286375
46 changed files with 3725 additions and 172 deletions

View File

@@ -1,3 +1,255 @@
//! C ABI for dynamically loaded parts (see `include/mize.h`).
//!
//! Names passed to `opt` / `method` / `thread` / `get_config` / `set_config`
//! are absolute dotted paths (e.g. `"vicinae.open"`).
//!
//! Errors use a thread-local slot (like errno): C code calls `set_err(...)`;
//! Rust checks via [`take_err`] after the call.
use std::cell::RefCell;
use std::ffi::{c_char, c_void, CStr};
use std::ptr;
//pub extern "C" fn
use crate::config::{ConfigOpt, MizeFun};
use crate::error::MizeError;
use crate::get_global_mize;
use crate::item::ItemData;
use crate::mize_err;
use crate::write_to_mize_stdout;
type CFun = unsafe extern "C" fn(*mut ItemData) -> *mut ItemData;
thread_local! {
/// Per-thread error set by C via `set_err`, consumed by Rust after the call.
static CUR_ERR: RefCell<Option<MizeError>> = const { RefCell::new(None) };
}
/// Clear the thread-local C error (call before invoking a C part entry / callback).
pub fn clear_err() {
CUR_ERR.with(|slot| {
*slot.borrow_mut() = None;
});
}
/// Take the thread-local C error, if any.
pub fn take_err() -> Option<MizeError> {
CUR_ERR.with(|slot| slot.borrow_mut().take())
}
/// Opaque pointer stored via `data_from_native` / read via `data_as_native`.
struct NativePtr(*mut c_void);
// SAFETY: the C++ part is responsible for only using the pointer on the right thread.
unsafe impl Send for NativePtr {}
unsafe impl Sync for NativePtr {}
fn cstr<'a>(p: *const c_char) -> Option<&'a str> {
if p.is_null() {
return None;
}
unsafe { CStr::from_ptr(p) }.to_str().ok()
}
fn wrap_c_fun(c_fun: CFun) -> MizeFun {
Box::new(move |args| {
clear_err();
let mut args_box = Box::new(args);
let ret_ptr = unsafe { c_fun(args_box.as_mut()) };
drop(args_box);
if let Some(err) = take_err() {
if !ret_ptr.is_null() {
unsafe { drop(Box::from_raw(ret_ptr)) };
}
return Err(err);
}
if ret_ptr.is_null() {
return Ok(ItemData::new());
}
Ok(*unsafe { Box::from_raw(ret_ptr) })
})
}
fn register_fun(name: &str, kind: &str, signature: &str, fun: MizeFun) {
let mut mize = match get_global_mize() {
Ok(m) => m,
Err(e) => {
let _ = write_to_mize_stdout(format_args!("[c_api] get_global_mize failed: {e:?}\n"));
return;
}
};
let id = match mize.id_new_dots(name) {
Ok(id) => id,
Err(e) => {
let _ = write_to_mize_stdout(format_args!("[c_api] bad id '{name}': {e:?}\n"));
return;
}
};
if mize.is_generate_mode().unwrap_or(false) {
let short = name.rsplit('.').next().unwrap_or(name);
let _ = mize.record_method(short, id.clone(), kind, signature);
return;
}
let opt = ConfigOpt {
name: id.dot(),
val: None,
fun: None,
deps: Vec::new(),
};
mize.config_opts.lock().unwrap().insert(id.clone(), opt);
mize.fun_values.lock().unwrap().insert(id, fun);
}
#[unsafe(no_mangle)]
pub extern "C" fn opt(name: *const c_char) {
let Some(name) = cstr(name) else { return };
let mut mize = match get_global_mize() {
Ok(m) => m,
Err(_) => return,
};
let Ok(id) = mize.id_new_dots(name) else { return };
if mize.is_generate_mode().unwrap_or(false) {
let short = name.rsplit('.').next().unwrap_or(name);
let _ = mize.record_opt(short, id.clone(), None);
return;
}
let opt = ConfigOpt {
name: id.dot(),
val: None,
fun: None,
deps: Vec::new(),
};
mize.config_opts.lock().unwrap().insert(id, opt);
}
#[unsafe(no_mangle)]
pub extern "C" fn method(name: *const c_char, signature: *const c_char, c_fun: CFun) {
let Some(name) = cstr(name) else { return };
let sig = cstr(signature).unwrap_or("");
register_fun(name, "method", sig, wrap_c_fun(c_fun));
}
#[unsafe(no_mangle)]
pub extern "C" fn effect(name: *const c_char, c_fun: CFun) {
let Some(name) = cstr(name) else { return };
register_fun(name, "effect", "", wrap_c_fun(c_fun));
}
#[unsafe(no_mangle)]
pub extern "C" fn apply(name: *const c_char, c_fun: CFun) {
let Some(name) = cstr(name) else { return };
register_fun(name, "apply", "", wrap_c_fun(c_fun));
}
#[unsafe(no_mangle)]
pub extern "C" fn thread(name: *const c_char, c_fun: CFun) {
let Some(name) = cstr(name) else { return };
register_fun(name, "thread", "", wrap_c_fun(c_fun));
}
#[unsafe(no_mangle)]
pub extern "C" fn set_config(name: *const c_char, value: *mut ItemData) {
if value.is_null() {
return;
}
let Some(name) = cstr(name) else {
unsafe { drop(Box::from_raw(value)) };
return;
};
let value = unsafe { *Box::from_raw(value) };
let mut mize = match get_global_mize() {
Ok(m) => m,
Err(_) => return,
};
let Ok(id) = mize.id_new_dots(name) else { return };
let mut opts = mize.config_opts.lock().unwrap();
match opts.get_mut(&id) {
Some(opt) => {
opt.val = Some(value);
}
None => {
opts.insert(
id.clone(),
ConfigOpt {
name: id.dot(),
val: Some(value),
fun: None,
deps: Vec::new(),
},
);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn get_config(name: *const c_char) -> *mut ItemData {
let Some(name) = cstr(name) else {
return ptr::null_mut();
};
let mut mize = match get_global_mize() {
Ok(m) => m,
Err(_) => return ptr::null_mut(),
};
match mize.get_config(name) {
Ok(data) => Box::into_raw(Box::new(data)),
Err(_) => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn data_new() -> *mut ItemData {
Box::into_raw(Box::new(ItemData::new()))
}
#[unsafe(no_mangle)]
pub extern "C" fn data_from_native(ptr: *mut c_void) -> *mut ItemData {
match ItemData::from_native(NativePtr(ptr)) {
Ok(data) => Box::into_raw(Box::new(data)),
Err(e) => {
let _ = write_to_mize_stdout(format_args!("[c_api] data_from_native: {e:?}\n"));
ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn data_as_native(data: *mut ItemData) -> *mut c_void {
if data.is_null() {
return ptr::null_mut();
}
let data = unsafe { &*data };
match data.native::<NativePtr>() {
Ok(guard) => guard.0,
Err(e) => {
let _ = write_to_mize_stdout(format_args!("[c_api] data_as_native: {e:?}\n"));
ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn data_free(data: *mut ItemData) {
if !data.is_null() {
unsafe { drop(Box::from_raw(data)) };
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mize_write_stdout(str_ptr: *const c_char) {
let Some(s) = cstr(str_ptr) else { return };
let _ = write_to_mize_stdout(format_args!("{s}"));
}
/// Set the thread-local error (like errno). `load_part` / C-callback wrappers turn this into `Err`.
#[unsafe(no_mangle)]
pub extern "C" fn set_err(msg: *const c_char) {
let text = cstr(msg).unwrap_or("set_err called with null/invalid message");
CUR_ERR.with(|slot| {
*slot.borrow_mut() = Some(mize_err!("{}", text));
});
}

View File

@@ -98,6 +98,17 @@ impl<T> MizeAny for T {
}
/// Load the `.so` for `part_name`, but register it under `at_id`
/// (e.g. `load_part_at("priv.spacetime", "spacetime")`).
/// No-op in generate mode — bindings come from listing the part in `gen = [...]`.
pub fn load_part_at(at_id: &str, part_name: &str) -> MizeResult<()> {
let mize = get_global_mize()?;
if mize.is_generate_mode().unwrap_or(false) {
return Ok(());
}
crate::platform::load_part(part_name, None, Some(at_id))
}
pub fn opt(name: &str) -> crate::config::ConfigOptId {
let mize = get_global_mize().unwrap();
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
@@ -162,6 +173,28 @@ pub fn effect<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(n
ConfigOptId { id }
}
pub fn apply<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(name: &str, fun: F) -> ConfigOptId {
let mize = get_global_mize().unwrap();
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
let id = cur_part.join(name).unwrap();
// in generate mode: record apply metadata, skip normal registration
if mize.is_generate_mode().unwrap_or(false) {
mize.record_method(name, id.clone(), "apply", "").ok();
return ConfigOptId { id };
}
let mut opts = mize.config_opts.lock().unwrap();
let opt = ConfigOpt {
name: id.dot(),
val: None,
fun: None,
deps: Vec::new(),
};
let mut fun_values = mize.fun_values.lock().unwrap();
fun_values.insert(id.clone(), Box::new(fun));
opts.insert(id.clone(), opt);
ConfigOptId { id }
}
pub fn thread<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(name: &str, fun: F) -> ConfigOptId {
let mize = get_global_mize().unwrap();
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
@@ -227,6 +260,18 @@ pub mod c_api {
})
}
}
pub extern "C" fn apply(name: *const c_char, c_fun: CFun) -> ConfigOptId {
unsafe {
let c_str = CStr::from_ptr(name);
let string = c_str.to_string_lossy();
super::apply(&string, move |args| {
let raw_ptr = Box::into_raw(Box::new(args));
let ret_ptr = c_fun(raw_ptr);
let data = *Box::from_raw(ret_ptr);
Ok(data)
})
}
}
pub extern "C" fn thread(name: *const c_char, c_fun: CFun) -> ConfigOptId {
unsafe {
let c_str = CStr::from_ptr(name);

61
core/src/include/mize.h Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
/**
* Mize C API — thin header over libmize.so exports (core/src/c_api.rs).
*
* Names for opt / method / thread / get_config / set_config are absolute
* dotted paths for now (e.g. "vicinae.open", "vicinae.nav").
*
* Errors: thread-local, like errno. Call set_err("...") from C; Rust
* (load_part / method wrappers) turns that into Err. Part entry is
* `void part_<name>(void)` — no sret / Result return.
*
* ItemData (`DATA`) is opaque. Ownership:
* - Callbacks receive a borrowed `args` (do not free).
* - Callbacks must return an owned DATA* (e.g. data_new()); may return NULL for empty.
* - get_config returns owned DATA*; free with data_free.
* - set_config takes ownership of the value pointer.
*/
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct ItemData DATA;
/** C method / thread / effect callback. args is borrowed; return owned DATA* or NULL. */
typedef DATA *(*CFun)(DATA *args);
void opt(const char *name);
void method(const char *name, const char *signature, CFun c_fun);
void effect(const char *name, CFun c_fun);
void thread(const char *name, CFun c_fun);
void set_config(const char *name, DATA *value);
DATA *get_config(const char *name);
DATA *data_new(void);
/** Store an opaque pointer in the native-value registry. */
DATA *data_from_native(void *ptr);
/** Read back a pointer previously stored with data_from_native. Returns NULL on error. */
void *data_as_native(DATA *data);
void data_free(DATA *data);
void mize_write_stdout(const char *str);
/** Set the thread-local error message (consumed by Rust after the call). */
void set_err(const char *msg);
#ifdef __cplusplus
}
#endif

View File

@@ -105,6 +105,7 @@ pub struct PartMetadata {
pub opts: Vec<OptMetadata>,
pub methods: Vec<MethodMetadata>,
pub effects: Vec<MethodMetadata>,
pub applies: Vec<MethodMetadata>,
pub threads: Vec<MethodMetadata>,
pub natives: Vec<NativeMetadata>,
}
@@ -120,7 +121,7 @@ pub struct OptMetadata {
pub struct MethodMetadata {
pub name: String,
pub id: MizeId,
pub kind: String, // "method", "effect", "thread"
pub kind: String, // "method", "effect", "apply", "thread"
/// Raw signature string, e.g. `"String -> ()"` or `"Command, Func -> ()"`.
pub signature: String,
/// Parsed argument type names (left of `->`).
@@ -339,20 +340,23 @@ impl Mize {
// end of platform specific init code
// load the parts, as specified in the parts config
match self
.get_config("parts")
{
Ok(parts_to_load) => {
for part_name in parts_to_load.cbor().as_array().ok_or("config parts was not an array")? {
let part_name = part_name.as_text().ok_or("part name was not a string")?;
if part_name.trim().is_empty() || self.has_part(part_name)? {
continue;
}
self.load_part(part_name)?;
// Prefer `parts` when it is an array; otherwise `parts.load` (so `parts.workspaces` /
// `parts.dist.cd` can coexist under a `parts` table in TOML).
let parts_to_load = match self.get_config("parts") {
Ok(data) if data.cbor().as_array().is_some() => Some(data),
_ => self.get_config("parts.load").ok(),
};
if let Some(parts_to_load) = parts_to_load {
for part_name in parts_to_load
.cbor()
.as_array()
.ok_or("config parts/parts.load was not an array")?
{
let part_name = part_name.as_text().ok_or("part name was not a string")?;
if part_name.trim().is_empty() || self.has_part(part_name)? {
continue;
}
}
Err(_err) => {
// no parts to load
self.load_part(part_name)?;
}
}
@@ -534,7 +538,7 @@ impl Mize {
pub fn load_part(&mut self, name: &str) -> MizeResult<()> {
tracing::debug!("loading part: {name}");
crate::platform::load_part(self, name, None)
crate::platform::load_part(name, None, None)
}
pub fn fetch_part(&mut self, name: &str) -> MizeResult<String> {
@@ -542,9 +546,8 @@ impl Mize {
crate::platform::fetch_part(self, name)
}
pub fn load_part_at(&mut self, name: &str, path: String) -> MizeResult<()> {
// platform specific init code
crate::platform::load_part(self, name, Some(path))
pub fn load_part_from_path(&mut self, name: &str, path: String) -> MizeResult<()> {
crate::platform::load_part(name, Some(path), None)
}
// ===== generate mode accessors =====
@@ -600,6 +603,7 @@ impl Mize {
};
match kind {
"effect" => metadata.effects.push(m),
"apply" => metadata.applies.push(m),
"thread" => metadata.threads.push(m),
_ => metadata.methods.push(m),
}
@@ -1037,9 +1041,7 @@ impl Mize {
let calling_part = self.clone().cur_eval_part()?;
self.clone().set_cur_eval_part(Some(part_id));
println!("before call");
let res = fun(args);
println!("after call");
self.clone().set_cur_eval_part(calling_part);
let mut fun_values = self.fun_values.lock()?;
fun_values.insert(id, fun);

View File

@@ -48,14 +48,14 @@ fn test_get_module_hash() -> MizeResult<()> {
toolchain_version = "rustc 1.78.0 (9b00956e5 2024-04-29)"
"#;
instance.set_blocking("0/config/selector", ItemData::from_toml(&selector_toml)?)?;
let (hash, selector) = get_part_hash(&mut instance, "String", ItemData::new())?;
// Pass selector fields as hash options (merges over defaults).
let options = ItemData::from_toml(&selector_toml)?;
let (hash, selector) = get_part_hash(&mut instance, "String", options)?;
println!("hash: {}", hash);
println!("selector: {}", selector);
assert_eq!(hash, "e96edf60216688314efc263c1aa8d6ce".to_owned());
assert_eq!(hash, "227b0216f9b7a324a621ff773c5241c9".to_owned());
Ok(())
}

View File

@@ -238,6 +238,9 @@ impl ItemData {
pub fn value_string(self) -> MizeResult<String> {
match self.cbor() {
CborValue::Text(string) => Ok(string.to_owned()),
CborValue::Bool(val) => Ok(format!("{val}")),
CborValue::Integer(val) => Ok(format!("{}", u32::try_from(val.clone())?)),
CborValue::Float(val) => Ok(format!("{val}")),
_ => Err(mize_err!("this ItemData has no string value")),
}
}

View File

@@ -292,9 +292,9 @@ pub mod platform {
Ok(())
}
pub fn load_part(mize: &mut Mize, part_name: &str, path: Option<String>) -> MizeResult<()> {
pub fn load_part(part_name: &str, path: Option<String>, at_id: Option<&str>) -> MizeResult<()> {
#[cfg(feature = "target-os")] {
os::load_part(mize, part_name, path)?;
os::load_part(part_name, path, at_id)?;
}
Ok(())

View File

@@ -171,12 +171,158 @@ pub fn seconds_since_modification(path: &Path) -> MizeResult<u64> {
Ok(duration.as_secs())
}
/// Best-effort rustc target triple for the currently running binary.
pub fn host_rust_target() -> &'static str {
#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
{
"x86_64-unknown-linux-gnu"
}
#[cfg(all(target_arch = "aarch64", target_os = "linux", target_env = "gnu"))]
{
"aarch64-unknown-linux-gnu"
}
#[cfg(all(target_arch = "x86_64", target_os = "macos"))]
{
"x86_64-apple-darwin"
}
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
{
"aarch64-apple-darwin"
}
#[cfg(all(target_arch = "x86_64", target_os = "windows"))]
{
"x86_64-pc-windows-gnu"
}
#[cfg(all(target_arch = "aarch64", target_os = "windows"))]
{
"aarch64-pc-windows-gnullvm"
}
#[cfg(not(any(
all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"),
all(target_arch = "aarch64", target_os = "linux", target_env = "gnu"),
all(target_arch = "x86_64", target_os = "macos"),
all(target_arch = "aarch64", target_os = "macos"),
all(target_arch = "x86_64", target_os = "windows"),
all(target_arch = "aarch64", target_os = "windows"),
)))]
{
"unknown"
}
}
/// Dist artifact filename: `{hash}-{part_id}-cd.tar.gz` or `{hash}-{part_id}-{version}.tar.gz`.
pub fn part_dist_artifact_name(hash: &str, part_id: &str, version: &str, cd: bool) -> String {
if cd {
format!("{hash}-{part_id}-cd.tar.gz")
} else {
format!("{hash}-{part_id}-{version}.tar.gz")
}
}
pub fn parts_dist_cd(mize: &mut Mize) -> bool {
mize.get_config("parts.dist.cd")
.ok()
.and_then(|d| d.value_bool().ok())
.unwrap_or(false)
}
/// Read `version` from a Cargo.toml's `[package]` table.
pub fn cargo_package_version(cargo_toml_path: &str) -> MizeResult<String> {
let contents = fs::read_to_string(cargo_toml_path)
.map_err(|e| mize_err!("could not read {cargo_toml_path}: {e}"))?;
let data = ItemData::from_toml(&contents)?;
data.get_path("package.version")?
.value_string()
.map_err(|e| e.msg(format!("no package.version in {cargo_toml_path}")))
}
/// Resolve a part's cargo version for dist URLs (non-cd).
pub fn resolve_part_version(mize: &mut Mize, part_name: &str) -> MizeResult<String> {
if let Ok(v) = mize.get_config(&format!("{part_name}.version")) {
if let Ok(s) = v.value_string() {
if !s.is_empty() {
return Ok(s);
}
}
}
if part_name == "mize" {
return Ok(env!("CARGO_PKG_VERSION").to_owned());
}
if let Ok(workspaces) = mize.get_config("parts.workspaces") {
if let Some(arr) = workspaces.cbor().as_array() {
for src in arr {
let Some(dir) = src.as_text() else { continue };
for candidate in [
format!("{dir}/{part_name}/Cargo.toml"),
format!("{dir}/mize.{part_name}/Cargo.toml"),
] {
if PathBuf::from(&candidate).exists() {
return cargo_package_version(&candidate);
}
}
}
}
}
if let Ok(mize_src) = mize.get_config("mize.src").and_then(|d| d.value_string()) {
for candidate in [
format!("{mize_src}/parts/{part_name}/Cargo.toml"),
format!("{mize_src}/parts/mize.{part_name}/Cargo.toml"),
format!("{mize_src}/core/Cargo.toml"),
] {
if part_name != "mize" && candidate.ends_with("core/Cargo.toml") {
continue;
}
if PathBuf::from(&candidate).exists() {
return cargo_package_version(&candidate);
}
}
}
Err(mize_err!(
"could not resolve cargo version for part '{part_name}' (set {part_name}.version or parts.workspaces, or enable parts.dist.cd)"
))
}
fn load_selector_base(mize: &mut Mize) -> ItemData {
// Prefer config opt; fall back to store path used by tests / older setups.
if let Ok(sel) = mize.get("self/config/selector").and_then(|i| i.as_data_full()) {
if !sel.cbor().is_null() {
return sel;
}
}
mize.get("0/config/selector")
.and_then(|i| i.as_data_full())
.unwrap_or_else(|_| ItemData::new())
}
pub fn get_part_hash(
mize: &mut Mize,
name: &str,
options: ItemData,
) -> MizeResult<(String, String)> {
let mut selector_data = mize.get("self/config/selector")?.as_data_full()?;
let mut selector_data = load_selector_base(mize);
if selector_data
.get_path("mize_version")
.ok()
.and_then(|v| v.value_string().ok())
.filter(|s| !s.is_empty())
.is_none()
{
selector_data.set_path("mize_version", env!("CARGO_PKG_VERSION"))?;
}
if selector_data
.get_path("system")
.ok()
.and_then(|v| v.value_string().ok())
.filter(|s| !s.is_empty())
.is_none()
{
selector_data.set_path("system", host_rust_target())?;
}
// handle the case, when we want a part for not the system we are running on...
debug!("get_part_hash: name: {}", name);
@@ -227,21 +373,31 @@ pub fn get_part_hash(
pub fn fetch_part(mize: &mut Mize, part_name: &str) -> MizeResult<String> {
let (part_hash, part_selector) = get_part_hash(mize, part_name, ItemData::new())?;
let part_url = "https://mize.works/mize/dist/{part_hash}-{part_name}.tar.gz";
let cd = parts_dist_cd(mize);
let version = if cd {
String::new()
} else {
resolve_part_version(mize, part_name)?
};
let artifact = part_dist_artifact_name(&part_hash, part_name, &version, cd);
let part_url = format!("https://dist.mize.works/{artifact}");
let store_path = mize.get_config("store")?.value_string()?;
let parts_path = format!("{store_path}/parts");
let target_dir = format!("{parts_path}/{part_hash}-{part_name}");
let tmp_file_path_gz = format!("{parts_path}/{part_hash}-{part_name}.tar.gz");
let tmp_file_path_gz = format!("{parts_path}/{artifact}");
std::fs::create_dir_all(&parts_path)?;
let tmp_gz_file = if !PathBuf::from(tmp_file_path_gz.clone()).exists() {
let mut tmp_gz_file = File::create(&tmp_file_path_gz)?;
debug!("downloading part '{}' with hash: {}", part_name, part_hash);
debug!(
"downloading part '{}' from {} (selector: {})",
part_name, part_url, part_selector
);
let response = http_req::request::get(part_url, &mut tmp_gz_file)?;
let response = http_req::request::get(&part_url, &mut tmp_gz_file)?;
let code = response.status_code();
if code != 200.into() {
@@ -252,8 +408,8 @@ pub fn fetch_part(mize: &mut Mize, part_name: &str) -> MizeResult<String> {
part_selector
)
.msg(format!(
"the http request to fetch the part returned a status code of: {}",
code
"the http request to fetch the part from '{}' returned a status code of: {}",
part_url, code
)));
}
@@ -309,8 +465,11 @@ fn get_part_path(mize: &mut Mize, part_name: &str, _path: Option<String>) -> Miz
//Err(mize_err!("part {part_name} not found"))
}
pub fn load_part(mize: &mut Mize, part_name: &str, path: Option<String>) -> MizeResult<()> {
let part_path = get_part_path(mize, part_name, path)?;
/// Load a part `.so` for `part_name`, registering it under `at_id` (or `part_name` if None).
/// Uses `get_global_mize()` so nested loads from inside another part's init work.
pub fn load_part(part_name: &str, path: Option<String>, at_id: Option<&str>) -> MizeResult<()> {
let mut mize = crate::get_global_mize()?;
let part_path = get_part_path(&mut mize, part_name, path)?;
let lib = unsafe { libloading::Library::new(part_path)? };
@@ -326,32 +485,35 @@ pub fn load_part(mize: &mut Mize, part_name: &str, path: Option<String>) -> Mize
let part_symbol_name = part_name.replace(".", "_");
let func: libloading::Symbol<
unsafe extern "C" fn() -> MizeResult<()>,
> = unsafe { lib.get(format!("part_{}", part_symbol_name).as_bytes())? };
let func: libloading::Symbol<unsafe extern "C" fn()> =
unsafe { lib.get(format!("part_{}", part_symbol_name).as_bytes())? };
let part_id = mize.clone().id_new_dots(part_name)?;
let eval_name = at_id.unwrap_or(part_name);
let part_id = mize.clone().id_new_dots(eval_name)?;
{
let mut opts = mize.config_opts.lock()?;
opts.entry(part_id.clone()).or_insert_with(|| ConfigOpt {
name: part_name.to_owned(),
name: eval_name.to_owned(),
val: Some(ItemData::new()),
fun: None,
deps: Vec::new(),
});
}
let prev_eval = mize.clone().cur_eval_part()?;
mize.clone().set_cur_eval_part(Some(part_id))?;
unsafe { func()? };
mize.clone().set_cur_eval_part(None)?;
let mut cur_eval_part = mize.cur_eval_part.lock()?;
*cur_eval_part = None;
crate::c_api::clear_err();
unsafe { func() };
let c_err = crate::c_api::take_err();
mize.clone().set_cur_eval_part(prev_eval)?;
// dropping the lib, would (i suspect) free all memory, of the library's code, which would
// make the part's vtable point into empty memory
std::mem::forget(lib);
if let Some(err) = c_err {
return Err(err);
}
Ok(())
}