twsearch_ffi/
lib.rs

1mod events;
2
3use std::{
4    ffi::{c_char, CStr, CString},
5    ptr::null_mut,
6};
7
8use twsearch::scramble::{
9    random_scramble_for_event, scramble_finder::free_memory_for_all_scramble_finders, Event,
10};
11
12/// # Safety
13///
14/// This function can panic. If you are working in pure Rust, use [`twsearch::scramble::random_scramble_for_event`] instead.
15///
16/// Returns:
17/// - A null pointer for *any* error.
18/// - A valid scramble (in the form of a C string) otherwise.
19#[no_mangle]
20pub unsafe extern "C" fn ffi_random_scramble_for_event(
21    event_raw_cstr: *const c_char,
22) -> *const c_char {
23    // TODO: we can't avoid leaking the return value, but we could give a function to free all past returned values.
24    match ffi_random_scramble_for_event_internal(event_raw_cstr) {
25        Ok(scramble_raw_cstr) => scramble_raw_cstr,
26        Err(_) => null_mut(),
27    }
28}
29
30fn ffi_random_scramble_for_event_internal(
31    event_raw_cstr: *const c_char,
32) -> Result<*const c_char, ()> {
33    let event_cstr = unsafe { CStr::from_ptr(event_raw_cstr) };
34    let event_str = event_cstr.to_str().map_err(|_| ())?;
35    let event = Event::try_from(event_str).map_err(|_| ())?;
36    let result_str = random_scramble_for_event(event)
37        .map_err(|_| ())?
38        .to_string();
39    Ok(CString::new(result_str).unwrap().into_raw())
40}
41
42#[test]
43fn ffi_test() {
44    // event ID, min num moves (inclusive), max num moves (inclusive)
45    let test_data = [
46        ("222", 11, 13), // TODO: are there any states that can't be reached in exactly 11 moves for our scramble generators?
47        ("pyram", 11, 15), // TODO: are there any states that can't be reached in exactly 11 moves for our scramble generators (ignoring tips)?
48        ("333", 15, 30),
49        ("555", 60, 60),
50        ("666", 80, 80),
51        ("777", 100, 100),
52        ("minx", 83, 83),
53    ];
54
55    let dylib_path = test_cdylib::build_current_project();
56    let lib = unsafe { libloading::Library::new(dylib_path).unwrap() };
57    let func: libloading::Symbol<unsafe extern "C" fn(event_raw_cstr: *mut c_char) -> *mut c_char> =
58        unsafe { lib.get(b"ffi_random_scramble_for_event").unwrap() };
59    for (event_id, min_num_moves, max_num_moves) in test_data {
60        let event_raw_cstr = CString::new((event_id).to_owned()).unwrap().into_raw();
61        let scramble_raw_cstr = unsafe { func(event_raw_cstr) };
62        let scramble_cstr = unsafe { CStr::from_ptr(scramble_raw_cstr) };
63        let scramble_str = scramble_cstr.to_str().map_err(|_| ()).unwrap();
64        let alg = scramble_str.parse::<cubing::alg::Alg>().unwrap();
65        assert!(alg.nodes.len() >= min_num_moves);
66        assert!(alg.nodes.len() <= max_num_moves);
67    }
68}
69
70#[no_mangle]
71pub extern "C" fn ffi_free_memory_for_all_scramble_finders() -> u32 {
72    // We cast to `u32` for the public API so that it's more stable across environments (including WASM).
73    // If we've allocated more than `u32::MAX` scramble finders, I'd be *very* impressed.
74    free_memory_for_all_scramble_finders() as u32
75}