From 223a77bd6dc82e3f654b1353632da191c2478eda Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Sun, 6 Oct 2024 02:22:43 +0900 Subject: [PATCH 1/8] =?UTF-8?q?add:=20C=20API=E3=81=AE"delete"=E3=81=AB?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=83=95=E3=83=86=E3=82=A3=E3=83=8D=E3=83=83?= =?UTF-8?q?=E3=83=88=E3=82=92=E5=BC=B5=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C APIにおいて次の状況に対してセーフティネットを張り、パニックするように する。 1. オブジェクトが他スレッドでアクセスされている最中に"delete"を試みる 2. "delete"後に他の通常のメソッド関数の利用を試みる 3. "delete"後に"delete"を試みる このPRは #836 の解決**ではなく**、ドキュメントにも手を加えていない。とい うのも`VoicevoxVoiceModelFile`には次のゲッターメソッドがあり、これらをカ バーするのは現状のAPIの形だと不可能だからである。 * `voicevox_voice_model_file_id` * `voicevox_voice_model_file_get_metas_json` --- Cargo.lock | 100 +++++--------- Cargo.toml | 2 + crates/voicevox_core_c_api/Cargo.toml | 6 +- crates/voicevox_core_c_api/src/c_impls.rs | 122 +++++++++++++++--- crates/voicevox_core_c_api/src/lib.rs | 116 ++++++++--------- .../tests/e2e/snapshots.toml | 24 ++++ .../tests/e2e/testcases.rs | 4 + .../e2e/testcases/double_delete_openjtalk.rs | 61 +++++++++ .../testcases/double_delete_synthesizer.rs | 85 ++++++++++++ .../e2e/testcases/double_delete_user_dict.rs | 46 +++++++ .../double_delete_voice_model_file.rs | 58 +++++++++ 11 files changed, 480 insertions(+), 144 deletions(-) create mode 100644 crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_openjtalk.rs create mode 100644 crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_synthesizer.rs create mode 100644 crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_user_dict.rs create mode 100644 crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_voice_model_file.rs diff --git a/Cargo.lock b/Cargo.lock index 2c2be4f8d..3906cd6b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,7 +366,7 @@ dependencies = [ "bitflags 2.5.0", "cexpr", "clang-sys", - "itertools 0.11.0", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -440,6 +440,12 @@ dependencies = [ "piper", ] +[[package]] +name = "boxcar" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba19c552ee63cb6646b75e1166d1bdb8a6d34a6d19e319dec88c8adadff2db3" + [[package]] name = "bstr" version = "1.2.0" @@ -1369,6 +1375,15 @@ dependencies = [ "miniz_oxide 0.8.0", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1872,15 +1887,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.12.1" @@ -1994,7 +2000,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.48.0", + "windows-targets 0.52.6", ] [[package]] @@ -2226,6 +2232,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2747,7 +2759,10 @@ checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "difflib", + "float-cmp", + "normalize-line-endings", "predicates-core", + "regex", ] [[package]] @@ -4356,15 +4371,18 @@ dependencies = [ "anstyle-query", "anyhow", "assert_cmd", + "boxcar", "camino", "chrono", "clap 4.5.19", "colorchoice", "const_format", "cstr", - "derive-getters", + "derive_more", "duct", + "duplicate", "easy-ext", + "indexmap 2.6.0", "inventory", "itertools 0.10.5", "libc", @@ -4372,6 +4390,7 @@ dependencies = [ "libtest-mimic", "ndarray", "ndarray-stats", + "predicates", "process_path", "ref-cast", "regex", @@ -4702,21 +4721,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -4739,12 +4743,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4763,12 +4761,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4787,12 +4779,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4817,12 +4803,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4841,12 +4821,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4859,12 +4833,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4883,12 +4851,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 627fab02f..aaf8185ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ async_zip = "=0.0.16" bindgen = "0.69.4" binstall-tar = "0.4.42" blocking = "1.6.1" +boxcar = "0.2.6" bytes = "1.7.2" camino = "1.1.9" cargo_metadata = "0.18.1" @@ -60,6 +61,7 @@ once_cell = "1.20.1" ouroboros = "0.18.4" parse-display = "0.8.2" pollster = "0.3.0" +predicates = "3.1.2" pretty_assertions = "1.4.1" proc-macro2 = "1.0.86" pyo3 = "0.20.3" diff --git a/crates/voicevox_core_c_api/Cargo.toml b/crates/voicevox_core_c_api/Cargo.toml index e03d6cc4f..c51f01442 100644 --- a/crates/voicevox_core_c_api/Cargo.toml +++ b/crates/voicevox_core_c_api/Cargo.toml @@ -20,12 +20,14 @@ link-onnxruntime = ["voicevox_core/link-onnxruntime"] [dependencies] anstream = { workspace = true, default-features = false, features = ["auto"] } anstyle-query.workspace = true +boxcar.workspace = true camino.workspace = true chrono = { workspace = true, default-features = false, features = ["clock"] } colorchoice.workspace = true const_format.workspace = true cstr.workspace = true -derive-getters.workspace = true +derive_more.workspace = true +duplicate.workspace = true easy-ext.workspace = true itertools.workspace = true libc.workspace = true @@ -45,10 +47,12 @@ clap = { workspace = true, features = ["derive"] } duct.workspace = true easy-ext.workspace = true inventory.workspace = true +indexmap = { workspace = true, features = ["serde"] } libloading.workspace = true libtest-mimic.workspace = true ndarray.workspace = true ndarray-stats.workspace = true +predicates.workspace = true regex.workspace = true serde = { workspace = true, features = ["derive"] } serde_with.workspace = true diff --git a/crates/voicevox_core_c_api/src/c_impls.rs b/crates/voicevox_core_c_api/src/c_impls.rs index 0e9ff9a78..e0cd9d387 100644 --- a/crates/voicevox_core_c_api/src/c_impls.rs +++ b/crates/voicevox_core_c_api/src/c_impls.rs @@ -1,11 +1,20 @@ -use std::{ffi::CString, path::Path}; +use std::{ + collections::HashMap, + ffi::CString, + fmt::Debug, + ops::DerefMut, + path::Path, + sync::{Arc, LazyLock}, +}; use camino::Utf8Path; +use duplicate::duplicate_item; +use easy_ext::ext; use ref_cast::ref_cast_custom; use voicevox_core::{InitializeOptions, Result, VoiceModelId}; use crate::{ - helpers::CApiResult, OpenJtalkRc, VoicevoxOnnxruntime, VoicevoxSynthesizer, + helpers::CApiResult, OpenJtalkRc, VoicevoxOnnxruntime, VoicevoxSynthesizer, VoicevoxUserDict, VoicevoxVoiceModelFile, }; @@ -61,10 +70,9 @@ macro_rules! to_cstr { use to_cstr; impl OpenJtalkRc { - pub(crate) fn new(open_jtalk_dic_dir: impl AsRef) -> Result { - Ok(Self { - open_jtalk: voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?, - }) + pub(crate) fn new(open_jtalk_dic_dir: impl AsRef) -> Result<&'static Self> { + let body = voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?; + Ok(::new(body)) } } @@ -73,42 +81,124 @@ impl VoicevoxSynthesizer { onnxruntime: &'static VoicevoxOnnxruntime, open_jtalk: &OpenJtalkRc, options: &InitializeOptions, - ) -> Result { - let synthesizer = voicevox_core::blocking::Synthesizer::new( + ) -> Result<&'static Self> { + let body = voicevox_core::blocking::Synthesizer::new( &onnxruntime.0, - open_jtalk.open_jtalk.clone(), + (*open_jtalk.body()).clone(), options, )?; - Ok(Self { synthesizer }) + Ok(::new(body)) } pub(crate) fn onnxruntime(&self) -> &'static VoicevoxOnnxruntime { - VoicevoxOnnxruntime::new(self.synthesizer.onnxruntime()) + VoicevoxOnnxruntime::new(self.body().onnxruntime()) } pub(crate) fn load_voice_model( &self, model: &voicevox_core::blocking::VoiceModelFile, ) -> CApiResult<()> { - self.synthesizer.load_voice_model(model)?; + self.body().load_voice_model(model)?; Ok(()) } pub(crate) fn unload_voice_model(&self, model_id: VoiceModelId) -> Result<()> { - self.synthesizer.unload_voice_model(model_id)?; + self.body().unload_voice_model(model_id)?; Ok(()) } pub(crate) fn metas(&self) -> CString { - let metas = &self.synthesizer.metas(); + let metas = &self.body().metas(); CString::new(serde_json::to_string(metas).unwrap()).unwrap() } } impl VoicevoxVoiceModelFile { - pub(crate) fn open(path: impl AsRef) -> Result { + pub(crate) fn open(path: impl AsRef) -> Result<&'static Self> { let model = voicevox_core::blocking::VoiceModelFile::open(path)?; let metas = CString::new(serde_json::to_string(model.metas()).unwrap()).unwrap(); - Ok(Self { model, metas }) + Ok(Self::new(VoiceModelFileWithMetas { model, metas })) + } +} + +pub(crate) struct VoiceModelFileWithMetas { + pub(crate) model: voicevox_core::blocking::VoiceModelFile, + pub(crate) metas: CString, +} + +pub(crate) trait CApiObject: From + Into + Copy + Debug { + type Body: 'static; + + fn heads() -> &'static boxcar::Vec; + fn bodies() -> &'static std::sync::Mutex>>; + + fn new(body: Self::Body) -> &'static Self { + let i = Self::heads().push_with(|i| to_id(i).into()); + Self::lock_bodies().insert(to_id(i), body.into()); + return &Self::heads()[i]; + + fn to_id(i: usize) -> u32 { + i.try_into().expect("too large") + } + } + + /// # Panics + /// + /// 次の場合にパニックする。 + /// + /// * `self`に対して以前にこの関数を呼んでいた場合 + /// * `self`がまだ他で利用中である場合 + fn drop_body(self) { + let body = Self::lock_bodies() + .remove(&self.into()) + .unwrap_or_else(|| self.panic_for_deleted()); + drop(Arc::into_inner(body).unwrap_or_else(|| self.panic_for_in_use())); + } + + /// # Panics + /// + /// `drop_body`を呼んでいるとパニックする。 + fn body(self) -> Arc { + Self::lock_bodies() + .get(&self.into()) + .unwrap_or_else(|| self.panic_for_deleted()) + .clone() + } +} + +#[ext] +impl T { + fn lock_bodies() -> impl DerefMut>> { + Self::bodies().lock().unwrap_or_else(|e| panic!("{e}")) + } + + fn panic_for_deleted(self) -> ! { + panic!("`{self:?}`は既に破棄されています"); + } + + fn panic_for_in_use(self) -> ! { + panic!("`{self:?}`が破棄されようとしましたが、これはまだ利用中です"); + } +} + +#[duplicate_item( + H B; + [ OpenJtalkRc ] [ voicevox_core::blocking::OpenJtalk ]; + [ VoicevoxUserDict ] [ voicevox_core::blocking::UserDict ]; + [ VoicevoxSynthesizer ] [ voicevox_core::blocking::Synthesizer ]; + [ VoicevoxVoiceModelFile ] [ VoiceModelFileWithMetas ]; +)] +impl CApiObject for H { + type Body = B; + + fn heads() -> &'static boxcar::Vec { + static HEADS: boxcar::Vec = boxcar::Vec::new(); + &HEADS + } + + fn bodies() -> &'static std::sync::Mutex>> { + static BODIES: LazyLock>>> = + LazyLock::new(Default::default); + &BODIES } } diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index a5c440146..97c185552 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -12,6 +12,7 @@ mod drop_check; mod helpers; mod result_code; mod slice_owner; +use self::c_impls::CApiObject; use self::drop_check::C_STRING_DROP_CHECKER; use self::helpers::{ accent_phrases_to_json, audio_query_model_to_json, ensure_utf8, into_result_code_with_error, @@ -22,7 +23,7 @@ use self::slice_owner::U8_SLICE_OWNER; use anstream::{AutoStream, RawStream}; use chrono::SecondsFormat; use colorchoice::ColorChoice; -use derive_getters::Getters; +use derive_more::{From, Into}; use ref_cast::RefCastCustom; use std::env; use std::ffi::{CStr, CString}; @@ -30,7 +31,7 @@ use std::fmt; use std::io; use std::os::raw::c_char; use std::ptr::NonNull; -use std::sync::{Arc, Once}; +use std::sync::Once; use tracing_subscriber::fmt::format::Writer; use tracing_subscriber::EnvFilter; use uuid::Uuid; @@ -262,8 +263,9 @@ pub unsafe extern "C" fn voicevox_onnxruntime_init_once( /// voicevox_open_jtalk_rc_delete(open_jtalk); /// ``` /// } +#[derive(Clone, Copy, Debug, From, Into)] pub struct OpenJtalkRc { - open_jtalk: voicevox_core::blocking::OpenJtalk, + id: u32, } /// ::OpenJtalkRc を構築(_construct_)する。 @@ -289,7 +291,7 @@ pub struct OpenJtalkRc { #[no_mangle] pub unsafe extern "C" fn voicevox_open_jtalk_rc_new( open_jtalk_dic_dir: *const c_char, - out_open_jtalk: NonNull>, + out_open_jtalk: NonNull>, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { @@ -318,7 +320,7 @@ pub extern "C" fn voicevox_open_jtalk_rc_use_user_dict( ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { - open_jtalk.open_jtalk.use_user_dict(&user_dict.dict)?; + open_jtalk.body().use_user_dict(&user_dict.body())?; Ok(()) })()) } @@ -338,9 +340,9 @@ pub extern "C" fn voicevox_open_jtalk_rc_use_user_dict( /// - `open_jtalk`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 /// } #[no_mangle] -pub extern "C" fn voicevox_open_jtalk_rc_delete(open_jtalk: Box) { +pub unsafe extern "C" fn voicevox_open_jtalk_rc_delete(open_jtalk: NonNull) { init_logger_once(); - drop(open_jtalk); + unsafe { open_jtalk.as_ref() }.drop_body(); // SAFETY: ユーザーに要求しているもので十分 } /// ハードウェアアクセラレーションモードを設定する設定値。 @@ -397,14 +399,13 @@ pub extern "C" fn voicevox_get_version() -> *const c_char { /// /// VVMファイルと対応する。 /// 構築(_construction_)は ::voicevox_voice_model_file_open で行い、破棄(_destruction_)は ::voicevox_voice_model_file_close で行う。 -#[derive(Getters)] +#[derive(Clone, Copy, Debug, From, Into)] pub struct VoicevoxVoiceModelFile { - model: voicevox_core::blocking::VoiceModelFile, - metas: CString, + id: u32, } /// 音声モデルID。 -pub type VoicevoxVoiceModelId<'a> = &'a [u8; 16]; +pub type VoicevoxVoiceModelId = *const [u8; 16]; /// スタイルID。 /// @@ -425,7 +426,7 @@ pub type VoicevoxStyleId = u32; #[no_mangle] pub unsafe extern "C" fn voicevox_voice_model_file_open( path: *const c_char, - out_model: NonNull>, + out_model: NonNull>, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { @@ -448,9 +449,9 @@ pub unsafe extern "C" fn voicevox_voice_model_file_open( #[no_mangle] pub extern "C" fn voicevox_voice_model_file_id( model: &VoicevoxVoiceModelFile, -) -> VoicevoxVoiceModelId<'_> { +) -> VoicevoxVoiceModelId { init_logger_once(); - model.model.id_ref().as_bytes() + model.body().model.id_ref().as_bytes() } /// ::VoicevoxVoiceModelFile からメタ情報を取得する。 @@ -468,7 +469,7 @@ pub extern "C" fn voicevox_voice_model_file_get_metas_json( model: &VoicevoxVoiceModelFile, ) -> *const c_char { init_logger_once(); - model.metas().as_ptr() + model.body().metas.as_ptr() } /// ::VoicevoxVoiceModelFile を、所有しているファイルディスクリプタを閉じた上で破棄(_destruct_)する。 @@ -480,17 +481,17 @@ pub extern "C" fn voicevox_voice_model_file_get_metas_json( /// - `model`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 /// } #[no_mangle] -pub extern "C" fn voicevox_voice_model_file_close(model: Box) { +pub unsafe extern "C" fn voicevox_voice_model_file_close(model: NonNull) { init_logger_once(); - drop(model); + unsafe { model.as_ref() }.drop_body(); // SAFETY: ユーザーに要求しているもので十分 } /// 音声シンセサイザ。 /// /// 構築(_construction_)は ::voicevox_synthesizer_new で行い、破棄(_destruction_)は ::voicevox_synthesizer_delete で行う。 -#[derive(Getters)] +#[derive(Clone, Copy, Debug, From, Into)] pub struct VoicevoxSynthesizer { - synthesizer: voicevox_core::blocking::Synthesizer, + id: u32, } /// ::VoicevoxSynthesizer を構築(_construct_)する。 @@ -512,7 +513,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_new( onnxruntime: &'static VoicevoxOnnxruntime, open_jtalk: &OpenJtalkRc, options: VoicevoxInitializeOptions, - out_synthesizer: NonNull>, + out_synthesizer: NonNull>, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { @@ -533,9 +534,9 @@ pub unsafe extern "C" fn voicevox_synthesizer_new( /// - `synthesizer`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 /// } #[no_mangle] -pub extern "C" fn voicevox_synthesizer_delete(synthesizer: Box) { +pub unsafe extern "C" fn voicevox_synthesizer_delete(synthesizer: NonNull) { init_logger_once(); - drop(synthesizer); + unsafe { synthesizer.as_ref() }.drop_body(); // SAFETY: ユーザーに要求しているもので十分 } /// 音声モデルを読み込む。 @@ -555,7 +556,7 @@ pub extern "C" fn voicevox_synthesizer_load_voice_model( model: &VoicevoxVoiceModelFile, ) -> VoicevoxResultCode { init_logger_once(); - into_result_code_with_error(synthesizer.load_voice_model(model.model())) + into_result_code_with_error(synthesizer.load_voice_model(&model.body().model)) } /// 音声モデルの読み込みを解除する。 @@ -570,12 +571,12 @@ pub extern "C" fn voicevox_synthesizer_load_voice_model( /// - `model_id`は読み込みについて有効でなければならない。 /// } #[no_mangle] -pub extern "C" fn voicevox_synthesizer_unload_voice_model( +pub unsafe extern "C" fn voicevox_synthesizer_unload_voice_model( synthesizer: &VoicevoxSynthesizer, - model_id: VoicevoxVoiceModelId<'_>, + model_id: VoicevoxVoiceModelId, ) -> VoicevoxResultCode { init_logger_once(); - let model_id = model_id.to_model_id(); + let model_id = unsafe { (*model_id).to_model_id() }; // SAFETY: ユーザーに要求しているもので十分 into_result_code_with_error(synthesizer.unload_voice_model(model_id).map_err(Into::into)) } @@ -607,7 +608,7 @@ pub extern "C" fn voicevox_synthesizer_get_onnxruntime( #[no_mangle] pub extern "C" fn voicevox_synthesizer_is_gpu_mode(synthesizer: &VoicevoxSynthesizer) -> bool { init_logger_once(); - synthesizer.synthesizer().is_gpu_mode() + synthesizer.body().is_gpu_mode() } /// 指定したIDの音声モデルが読み込まれているか判定する。 @@ -622,13 +623,13 @@ pub extern "C" fn voicevox_synthesizer_is_gpu_mode(synthesizer: &VoicevoxSynthes /// - `model_id`は読み込みについて有効でなければならない。 /// } #[no_mangle] -pub extern "C" fn voicevox_synthesizer_is_loaded_voice_model( +pub unsafe extern "C" fn voicevox_synthesizer_is_loaded_voice_model( synthesizer: &VoicevoxSynthesizer, - model_id: VoicevoxVoiceModelId<'_>, + model_id: VoicevoxVoiceModelId, ) -> bool { init_logger_once(); - let model_id = model_id.to_model_id(); - synthesizer.synthesizer().is_loaded_voice_model(model_id) + let model_id = unsafe { (*model_id).to_model_id() }; // SAFETY: ユーザーに要求しているもので十分 + synthesizer.body().is_loaded_voice_model(model_id) } /// 今読み込んでいる音声モデルのメタ情報を、JSONで取得する。 @@ -729,7 +730,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query_from_kana( let kana = ensure_utf8(kana)?; let audio_query = synthesizer - .synthesizer() + .body() .audio_query_from_kana(kana, StyleId::new(style_id))?; let audio_query = CString::new(audio_query_model_to_json(&audio_query)) .expect("should not contain '\\0'"); @@ -777,7 +778,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query( let text = ensure_utf8(text)?; let audio_query = synthesizer - .synthesizer() + .body() .audio_query(text, StyleId::new(style_id))?; let audio_query = CString::new(audio_query_model_to_json(&audio_query)) .expect("should not contain '\\0'"); @@ -824,7 +825,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases_from_kana( into_result_code_with_error((|| { let kana = ensure_utf8(CStr::from_ptr(kana))?; let accent_phrases = synthesizer - .synthesizer() + .body() .create_accent_phrases_from_kana(kana, StyleId::new(style_id))?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); @@ -870,7 +871,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases( into_result_code_with_error((|| { let text = ensure_utf8(CStr::from_ptr(text))?; let accent_phrases = synthesizer - .synthesizer() + .body() .create_accent_phrases(text, StyleId::new(style_id))?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); @@ -909,7 +910,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_data( serde_json::from_str(ensure_utf8(CStr::from_ptr(accent_phrases_json))?) .map_err(CApiError::InvalidAccentPhrase)?; let accent_phrases = synthesizer - .synthesizer() + .body() .replace_mora_data(&accent_phrases, StyleId::new(style_id))?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); @@ -948,7 +949,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_phoneme_length( serde_json::from_str(ensure_utf8(CStr::from_ptr(accent_phrases_json))?) .map_err(CApiError::InvalidAccentPhrase)?; let accent_phrases = synthesizer - .synthesizer() + .body() .replace_phoneme_length(&accent_phrases, StyleId::new(style_id))?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); @@ -987,7 +988,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_pitch( serde_json::from_str(ensure_utf8(CStr::from_ptr(accent_phrases_json))?) .map_err(CApiError::InvalidAccentPhrase)?; let accent_phrases = synthesizer - .synthesizer() + .body() .replace_mora_pitch(&accent_phrases, StyleId::new(style_id))?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); @@ -1047,7 +1048,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_synthesis( .map_err(|_| CApiError::InvalidUtf8Input)?; let audio_query: AudioQuery = serde_json::from_str(audio_query_json).map_err(CApiError::InvalidAudioQuery)?; - let wav = synthesizer.synthesizer().synthesis( + let wav = synthesizer.body().synthesis( &audio_query, StyleId::new(style_id), &SynthesisOptions::from(options), @@ -1103,7 +1104,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts_from_kana( init_logger_once(); into_result_code_with_error((|| { let kana = ensure_utf8(CStr::from_ptr(kana))?; - let output = synthesizer.synthesizer().tts_from_kana( + let output = synthesizer.body().tts_from_kana( kana, StyleId::new(style_id), &TtsOptions::from(options), @@ -1144,11 +1145,10 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts( init_logger_once(); into_result_code_with_error((|| { let text = ensure_utf8(CStr::from_ptr(text))?; - let output = synthesizer.synthesizer().tts( - text, - StyleId::new(style_id), - &TtsOptions::from(options), - )?; + let output = + synthesizer + .body() + .tts(text, StyleId::new(style_id), &TtsOptions::from(options))?; U8_SLICE_OWNER.own_and_lend(output, output_wav, output_wav_length); Ok(()) })()) @@ -1225,9 +1225,9 @@ pub extern "C" fn voicevox_error_result_to_message( } /// ユーザー辞書。 -#[derive(Default)] +#[derive(Clone, Copy, Debug, From, Into)] pub struct VoicevoxUserDict { - dict: Arc, + id: u32, } /// ユーザー辞書の単語。 @@ -1290,9 +1290,9 @@ pub extern "C" fn voicevox_user_dict_word_make( /// /// @returns ::VoicevoxUserDict #[no_mangle] -pub extern "C" fn voicevox_user_dict_new() -> Box { +pub extern "C" fn voicevox_user_dict_new() -> NonNull { init_logger_once(); - Default::default() + VoicevoxUserDict::new(Default::default()).into() } /// ユーザー辞書にファイルを読み込ませる。 @@ -1313,7 +1313,7 @@ pub unsafe extern "C" fn voicevox_user_dict_load( init_logger_once(); into_result_code_with_error((|| { let dict_path = ensure_utf8(unsafe { CStr::from_ptr(dict_path) })?; - user_dict.dict.load(dict_path)?; + user_dict.body().load(dict_path)?; Ok(()) })()) @@ -1343,7 +1343,7 @@ pub unsafe extern "C" fn voicevox_user_dict_add_word( init_logger_once(); into_result_code_with_error((|| { let word = word.read_unaligned().try_into_word()?; - let uuid = user_dict.dict.add_word(word)?; + let uuid = user_dict.body().add_word(word)?; output_word_uuid.write_unaligned(uuid.into_bytes()); Ok(()) @@ -1372,7 +1372,7 @@ pub unsafe extern "C" fn voicevox_user_dict_update_word( into_result_code_with_error((|| { let word_uuid = Uuid::from_slice(word_uuid).map_err(CApiError::InvalidUuid)?; let word = word.read_unaligned().try_into_word()?; - user_dict.dict.update_word(word_uuid, word)?; + user_dict.body().update_word(word_uuid, word)?; Ok(()) })()) @@ -1396,7 +1396,7 @@ pub extern "C" fn voicevox_user_dict_remove_word( init_logger_once(); into_result_code_with_error((|| { let word_uuid = Uuid::from_slice(word_uuid).map_err(CApiError::InvalidUuid)?; - user_dict.dict.remove_word(word_uuid)?; + user_dict.body().remove_word(word_uuid)?; Ok(()) })()) } @@ -1420,7 +1420,7 @@ pub unsafe extern "C" fn voicevox_user_dict_to_json( output_json: NonNull<*mut c_char>, ) -> VoicevoxResultCode { init_logger_once(); - let json = user_dict.dict.to_json(); + let json = user_dict.body().to_json(); let json = CString::new(json).expect("\\0を含まない文字列であることが保証されている"); output_json.write_unaligned(C_STRING_DROP_CHECKER.whitelist(json).into_raw()); VoicevoxResultCode::VOICEVOX_RESULT_OK @@ -1442,7 +1442,7 @@ pub extern "C" fn voicevox_user_dict_import( ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { - user_dict.dict.import(&other_dict.dict)?; + user_dict.body().import(&other_dict.body())?; Ok(()) })()) } @@ -1464,7 +1464,7 @@ pub unsafe extern "C" fn voicevox_user_dict_save( init_logger_once(); into_result_code_with_error((|| { let path = ensure_utf8(CStr::from_ptr(path))?; - user_dict.dict.save(path)?; + user_dict.body().save(path)?; Ok(()) })()) } @@ -1477,7 +1477,7 @@ pub unsafe extern "C" fn voicevox_user_dict_save( /// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 /// } #[no_mangle] -pub unsafe extern "C" fn voicevox_user_dict_delete(user_dict: Box) { +pub unsafe extern "C" fn voicevox_user_dict_delete(user_dict: NonNull) { init_logger_once(); - drop(user_dict); + unsafe { user_dict.as_ref() }.drop_body(); // SAFETY: ユーザーに要求しているもので十分 } diff --git a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml index eb5911c7e..cdb2dbc29 100644 --- a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml +++ b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml @@ -64,6 +64,30 @@ stderr.unix = ''' last_error_message = "Statusが初期化されていません" stderr = "" +[double_delete_openjtalk] +stderr_contains_all = [ + "\n`OpenJtalkRc { id: 0 }`は既に破棄されています\n", + "\nthread caused non-unwinding panic. aborting.\n", +] + +[double_delete_synthesizer] +stderr_contains_all = [ + "\n`VoicevoxSynthesizer { id: 0 }`は既に破棄されています\n", + "\nthread caused non-unwinding panic. aborting.\n", +] + +[double_delete_user_dict] +stderr_contains_all = [ + "\n`VoicevoxUserDict { id: 0 }`は既に破棄されています\n", + "\nthread caused non-unwinding panic. aborting.\n", +] + +[double_delete_voice_model_file] +stderr_contains_all = [ + "\n`VoicevoxVoiceModelFile { id: 0 }`は既に破棄されています\n", + "\nthread caused non-unwinding panic. aborting.\n", +] + [global_info] result_messages.0 = "エラーが発生しませんでした" result_messages.1 = "OpenJTalkの辞書が読み込まれていません" diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases.rs b/crates/voicevox_core_c_api/tests/e2e/testcases.rs index 31eb9cdfe..677d2855f 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases.rs @@ -1,5 +1,9 @@ mod compatible_engine; mod compatible_engine_load_model_before_initialize; +mod double_delete_openjtalk; +mod double_delete_synthesizer; +mod double_delete_user_dict; +mod double_delete_voice_model_file; mod global_info; mod simple_tts; mod synthesizer_new_output_json; diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_openjtalk.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_openjtalk.rs new file mode 100644 index 000000000..20efff861 --- /dev/null +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_openjtalk.rs @@ -0,0 +1,61 @@ +//! `voicevox_open_jtalk_rc_delete`を二度呼ぶとクラッシュすることを確認する。 + +use std::{ffi::CString, mem::MaybeUninit, sync::LazyLock}; + +use assert_cmd::assert::AssertResult; +use indexmap::IndexSet; +use libloading::Library; +use serde::{Deserialize, Serialize}; +use test_util::{ + c_api::{self, CApi, VoicevoxResultCode}, + OPEN_JTALK_DIC_DIR, +}; + +use crate::{ + assert_cdylib::{self, case, Utf8Output}, + snapshots, +}; + +case!(TestCase); + +#[derive(Serialize, Deserialize)] +struct TestCase; + +#[typetag::serde(name = "double_delete_openjtalk")] +impl assert_cdylib::TestCase for TestCase { + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; + + let openjtalk = { + let mut openjtalk = MaybeUninit::uninit(); + let open_jtalk_dic_dir = CString::new(OPEN_JTALK_DIC_DIR).unwrap(); + assert_ok( + lib.voicevox_open_jtalk_rc_new(open_jtalk_dic_dir.as_ptr(), openjtalk.as_mut_ptr()), + ); + openjtalk.assume_init() + }; + + lib.voicevox_open_jtalk_rc_delete(openjtalk); + lib.voicevox_open_jtalk_rc_delete(openjtalk); + unreachable!(); + + fn assert_ok(result_code: VoicevoxResultCode) { + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); + } + } + + fn assert_output(&self, output: Utf8Output) -> AssertResult { + let mut assert = output.assert().try_failure()?.try_stdout("")?; + for s in &SNAPSHOTS.stderr_contains_all { + assert = assert.try_stderr(predicates::str::contains(s))?; + } + Ok(assert) + } +} + +static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_openjtalk); + +#[derive(Deserialize)] +struct Snapshots { + stderr_contains_all: IndexSet, +} diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_synthesizer.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_synthesizer.rs new file mode 100644 index 000000000..22fdb92c9 --- /dev/null +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_synthesizer.rs @@ -0,0 +1,85 @@ +//! `voicevox_synthesizer_delete`を二度呼ぶとクラッシュすることを確認する。 + +use std::{ffi::CString, mem::MaybeUninit, sync::LazyLock}; + +use assert_cmd::assert::AssertResult; +use indexmap::IndexSet; +use libloading::Library; +use serde::{Deserialize, Serialize}; +use test_util::{ + c_api::{self, CApi, VoicevoxInitializeOptions, VoicevoxResultCode}, + OPEN_JTALK_DIC_DIR, +}; + +use crate::{ + assert_cdylib::{self, case, Utf8Output}, + snapshots, +}; + +case!(TestCase); + +#[derive(Serialize, Deserialize)] +struct TestCase; + +#[typetag::serde(name = "double_delete_synthesizer")] +impl assert_cdylib::TestCase for TestCase { + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; + + let onnxruntime = { + let mut onnxruntime = MaybeUninit::uninit(); + assert_ok(lib.voicevox_onnxruntime_load_once( + lib.voicevox_make_default_load_onnxruntime_options(), + onnxruntime.as_mut_ptr(), + )); + onnxruntime.assume_init() + }; + + let openjtalk = { + let mut openjtalk = MaybeUninit::uninit(); + let open_jtalk_dic_dir = CString::new(OPEN_JTALK_DIC_DIR).unwrap(); + assert_ok( + lib.voicevox_open_jtalk_rc_new(open_jtalk_dic_dir.as_ptr(), openjtalk.as_mut_ptr()), + ); + openjtalk.assume_init() + }; + + let synthesizer = { + let mut synthesizer = MaybeUninit::uninit(); + assert_ok(lib.voicevox_synthesizer_new( + onnxruntime, + openjtalk, + VoicevoxInitializeOptions { + acceleration_mode: + c_api::VoicevoxAccelerationMode_VOICEVOX_ACCELERATION_MODE_CPU, + ..lib.voicevox_make_default_initialize_options() + }, + synthesizer.as_mut_ptr(), + )); + synthesizer.assume_init() + }; + + lib.voicevox_synthesizer_delete(synthesizer); + lib.voicevox_synthesizer_delete(synthesizer); + unreachable!(); + + fn assert_ok(result_code: VoicevoxResultCode) { + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); + } + } + + fn assert_output(&self, output: Utf8Output) -> AssertResult { + let mut assert = output.assert().try_failure()?.try_stdout("")?; + for s in &SNAPSHOTS.stderr_contains_all { + assert = assert.try_stderr(predicates::str::contains(s))?; + } + Ok(assert) + } +} + +static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_synthesizer); + +#[derive(Deserialize)] +struct Snapshots { + stderr_contains_all: IndexSet, +} diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_user_dict.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_user_dict.rs new file mode 100644 index 000000000..13436e8fa --- /dev/null +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_user_dict.rs @@ -0,0 +1,46 @@ +//! `voicevox_user_dict_delete`を二度呼ぶとクラッシュすることを確認する。 + +use std::sync::LazyLock; + +use assert_cmd::assert::AssertResult; +use indexmap::IndexSet; +use libloading::Library; +use serde::{Deserialize, Serialize}; +use test_util::c_api::CApi; + +use crate::{ + assert_cdylib::{self, case, Utf8Output}, + snapshots, +}; + +case!(TestCase); + +#[derive(Serialize, Deserialize)] +struct TestCase; + +#[typetag::serde(name = "double_delete_user_dict")] +impl assert_cdylib::TestCase for TestCase { + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; + + let dict = lib.voicevox_user_dict_new(); + lib.voicevox_user_dict_delete(dict); + lib.voicevox_user_dict_delete(dict); + unreachable!(); + } + + fn assert_output(&self, output: Utf8Output) -> AssertResult { + let mut assert = output.assert().try_failure()?.try_stdout("")?; + for s in &SNAPSHOTS.stderr_contains_all { + assert = assert.try_stderr(predicates::str::contains(s))?; + } + Ok(assert) + } +} + +static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_user_dict); + +#[derive(Deserialize)] +struct Snapshots { + stderr_contains_all: IndexSet, +} diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_voice_model_file.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_voice_model_file.rs new file mode 100644 index 000000000..573f6bb1b --- /dev/null +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_voice_model_file.rs @@ -0,0 +1,58 @@ +//! `voicevox_voice_model_file_close`を二度呼ぶとクラッシュすることを確認する。 + +use std::{mem::MaybeUninit, sync::LazyLock}; + +use assert_cmd::assert::AssertResult; +use indexmap::IndexSet; +use libloading::Library; +use serde::{Deserialize, Serialize}; +use test_util::c_api::{self, CApi, VoicevoxResultCode}; + +use crate::{ + assert_cdylib::{self, case, Utf8Output}, + snapshots, +}; + +case!(TestCase); + +#[derive(Serialize, Deserialize)] +struct TestCase; + +#[typetag::serde(name = "double_delete_voice_model_file")] +impl assert_cdylib::TestCase for TestCase { + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; + + let model = { + let mut model = MaybeUninit::uninit(); + assert_ok(lib.voicevox_voice_model_file_open( + c_api::SAMPLE_VOICE_MODEL_FILE_PATH.as_ptr(), + model.as_mut_ptr(), + )); + model.assume_init() + }; + + lib.voicevox_voice_model_file_close(model); + lib.voicevox_voice_model_file_close(model); + unreachable!(); + + fn assert_ok(result_code: VoicevoxResultCode) { + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); + } + } + + fn assert_output(&self, output: Utf8Output) -> AssertResult { + let mut assert = output.assert().try_failure()?.try_stdout("")?; + for s in &SNAPSHOTS.stderr_contains_all { + assert = assert.try_stderr(predicates::str::contains(s))?; + } + Ok(assert) + } +} + +static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_voice_model_file); + +#[derive(Deserialize)] +struct Snapshots { + stderr_contains_all: IndexSet, +} From 0b38629cfbf546cf848a1ffaad5f3bb90a2f7066 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Sun, 6 Oct 2024 17:52:58 +0900 Subject: [PATCH 2/8] =?UTF-8?q?`CApiObject`=E3=81=ABdoc=E3=82=92=E6=9B=B8?= =?UTF-8?q?=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://discord.com/channels/879570910208733277/893889888208977960/1292402538745954346 --- crates/voicevox_core_c_api/src/c_impls.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/voicevox_core_c_api/src/c_impls.rs b/crates/voicevox_core_c_api/src/c_impls.rs index e0cd9d387..23e44c66c 100644 --- a/crates/voicevox_core_c_api/src/c_impls.rs +++ b/crates/voicevox_core_c_api/src/c_impls.rs @@ -126,6 +126,28 @@ pub(crate) struct VoiceModelFileWithMetas { pub(crate) metas: CString, } +/// プロセスの終わりまでデストラクトされない、ユーザーにオブジェクトとして貸し出す`u32`相当の値。 +/// +/// インスタンスは次のような形。 +/// +/// ``` +/// #[derive(Clone, Copy, Debug, From, Into)] +/// #[repr(Rust)] +/// pub struct VoicevoxSynthesizer { +/// id: u32, +/// } +/// ``` +/// +/// `Body`そのものではなくこのトレイトのインスタンスをユーザーに渡すようにし、次のユーザー操作に対するセーフティネットを実現する。 +/// +/// 1. オブジェクトが他スレッドでアクセスされている最中に"delete"を試みる +/// 2. "delete"後に他の通常のメソッド関数の利用を試みる +/// 3. "delete"後に"delete"を試みる +/// +/// ただし次のゲッター関数に関しては機能しない。 +/// +/// - `voicevox_voice_model_file_id` +/// - `voicevox_voice_model_file_get_metas_json` pub(crate) trait CApiObject: From + Into + Copy + Debug { type Body: 'static; From 464d6f7ef5fe93bb3d1eb1e7f9d10a7f8e4b8df7 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Mon, 7 Oct 2024 03:15:11 +0900 Subject: [PATCH 3/8] =?UTF-8?q?`CApiObject::`{`Body`=20=E2=86=92=20`RustAp?= =?UTF-8?q?iObject`}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/voicevox_core_c_api/src/c_impls.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/voicevox_core_c_api/src/c_impls.rs b/crates/voicevox_core_c_api/src/c_impls.rs index 4fe62e91d..3b60e9378 100644 --- a/crates/voicevox_core_c_api/src/c_impls.rs +++ b/crates/voicevox_core_c_api/src/c_impls.rs @@ -140,7 +140,7 @@ fn metas_to_json(metas: &[SpeakerMeta]) -> CString { /// } /// ``` /// -/// `Body`そのものではなくこのトレイトのインスタンスをユーザーに渡すようにし、次のユーザー操作に対するセーフティネットを実現する。 +/// `RustApi`そのものではなくこのトレイトのインスタンスをユーザーに渡すようにし、次のユーザー操作に対するセーフティネットを実現する。 /// /// 1. オブジェクトが他スレッドでアクセスされている最中に"delete"を試みる /// 2. "delete"後に他の通常のメソッド関数の利用を試みる @@ -151,12 +151,12 @@ fn metas_to_json(metas: &[SpeakerMeta]) -> CString { /// - `voicevox_voice_model_file_id` /// - `voicevox_voice_model_file_get_metas_json` pub(crate) trait CApiObject: From + Into + Copy + Debug { - type Body: 'static; + type RustApiObject: 'static; fn heads() -> &'static boxcar::Vec; - fn bodies() -> &'static std::sync::Mutex>>; + fn bodies() -> &'static std::sync::Mutex>>; - fn new(body: Self::Body) -> &'static Self { + fn new(body: Self::RustApiObject) -> &'static Self { let i = Self::heads().push_with(|i| to_id(i).into()); Self::lock_bodies().insert(to_id(i), body.into()); return &Self::heads()[i]; @@ -182,7 +182,7 @@ pub(crate) trait CApiObject: From + Into + Copy + Debug { /// # Panics /// /// `drop_body`を呼んでいるとパニックする。 - fn body(self) -> Arc { + fn body(self) -> Arc { Self::lock_bodies() .get(&self.into()) .unwrap_or_else(|| self.panic_for_deleted()) @@ -192,7 +192,7 @@ pub(crate) trait CApiObject: From + Into + Copy + Debug { #[ext] impl T { - fn lock_bodies() -> impl DerefMut>> { + fn lock_bodies() -> impl DerefMut>> { Self::bodies().lock().unwrap_or_else(|e| panic!("{e}")) } @@ -213,14 +213,14 @@ impl T { [ VoicevoxVoiceModelFile ] [ voicevox_core::blocking::VoiceModelFile ]; )] impl CApiObject for H { - type Body = B; + type RustApiObject = B; fn heads() -> &'static boxcar::Vec { static HEADS: boxcar::Vec = boxcar::Vec::new(); &HEADS } - fn bodies() -> &'static std::sync::Mutex>> { + fn bodies() -> &'static std::sync::Mutex>> { static BODIES: LazyLock>>> = LazyLock::new(Default::default); &BODIES From 3e6ddf10b947693fd80244c94fb6cf11e1bb275a Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Tue, 8 Oct 2024 02:33:03 +0900 Subject: [PATCH 4/8] =?UTF-8?q?`CApiObject`=E3=82=921-bit=E9=95=B7?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- crates/voicevox_core_c_api/Cargo.toml | 2 +- crates/voicevox_core_c_api/src/c_impls.rs | 63 ++++++++++++------- crates/voicevox_core_c_api/src/lib.rs | 23 ++++--- .../tests/e2e/snapshots.toml | 16 ++--- .../e2e/testcases/double_delete_openjtalk.rs | 7 ++- .../testcases/double_delete_synthesizer.rs | 7 ++- .../e2e/testcases/double_delete_user_dict.rs | 7 ++- .../double_delete_voice_model_file.rs | 7 ++- 9 files changed, 79 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3906cd6b4..c32b36d5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4378,10 +4378,10 @@ dependencies = [ "colorchoice", "const_format", "cstr", - "derive_more", "duct", "duplicate", "easy-ext", + "educe", "indexmap 2.6.0", "inventory", "itertools 0.10.5", diff --git a/crates/voicevox_core_c_api/Cargo.toml b/crates/voicevox_core_c_api/Cargo.toml index c51f01442..2852aaa69 100644 --- a/crates/voicevox_core_c_api/Cargo.toml +++ b/crates/voicevox_core_c_api/Cargo.toml @@ -26,9 +26,9 @@ chrono = { workspace = true, default-features = false, features = ["clock"] } colorchoice.workspace = true const_format.workspace = true cstr.workspace = true -derive_more.workspace = true duplicate.workspace = true easy-ext.workspace = true +educe.workspace = true itertools.workspace = true libc.workspace = true process_path.workspace = true diff --git a/crates/voicevox_core_c_api/src/c_impls.rs b/crates/voicevox_core_c_api/src/c_impls.rs index 3b60e9378..43497660b 100644 --- a/crates/voicevox_core_c_api/src/c_impls.rs +++ b/crates/voicevox_core_c_api/src/c_impls.rs @@ -1,7 +1,9 @@ use std::{ + any, collections::HashMap, ffi::CString, - fmt::Debug, + fmt::{Debug, Display}, + mem, ops::DerefMut, path::Path, sync::{Arc, LazyLock}, @@ -128,15 +130,13 @@ fn metas_to_json(metas: &[SpeakerMeta]) -> CString { CString::new(metas).expect("should not contain NUL") } -/// プロセスの終わりまでデストラクトされない、ユーザーにオブジェクトとして貸し出す`u32`相当の値。 +/// プロセスの終わりまでデストラクトされない、ユーザーにオブジェクトとして貸し出す1-bit長の構造体。 /// /// インスタンスは次のような形。 /// /// ``` -/// #[derive(Clone, Copy, Debug, From, Into)] -/// #[repr(Rust)] /// pub struct VoicevoxSynthesizer { -/// id: u32, +/// _padding: MaybeUninit<[u8; 1]>, /// } /// ``` /// @@ -150,20 +150,25 @@ fn metas_to_json(metas: &[SpeakerMeta]) -> CString { /// /// - `voicevox_voice_model_file_id` /// - `voicevox_voice_model_file_get_metas_json` -pub(crate) trait CApiObject: From + Into + Copy + Debug { +pub(crate) trait CApiObject: Default + Debug { type RustApiObject: 'static; fn heads() -> &'static boxcar::Vec; - fn bodies() -> &'static std::sync::Mutex>>; + + fn bodies() -> &'static std::sync::Mutex< + HashMap< + usize, // `heads`の要素へのポインタのアドレス + Arc, + >, + >; fn new(body: Self::RustApiObject) -> &'static Self { - let i = Self::heads().push_with(|i| to_id(i).into()); - Self::lock_bodies().insert(to_id(i), body.into()); - return &Self::heads()[i]; + assert!(mem::size_of::() > 0); - fn to_id(i: usize) -> u32 { - i.try_into().expect("too large") - } + let i = Self::heads().push(Default::default()); + let this = &Self::heads()[i]; + Self::lock_bodies().insert(this as *const _ as _, body.into()); + this } /// # Panics @@ -172,9 +177,9 @@ pub(crate) trait CApiObject: From + Into + Copy + Debug { /// /// * `self`に対して以前にこの関数を呼んでいた場合 /// * `self`がまだ他で利用中である場合 - fn drop_body(self) { + fn drop_body(&self) { let body = Self::lock_bodies() - .remove(&self.into()) + .remove(&(self as *const _ as _)) .unwrap_or_else(|| self.panic_for_deleted()); drop(Arc::into_inner(body).unwrap_or_else(|| self.panic_for_in_use())); } @@ -182,9 +187,9 @@ pub(crate) trait CApiObject: From + Into + Copy + Debug { /// # Panics /// /// `drop_body`を呼んでいるとパニックする。 - fn body(self) -> Arc { + fn body(&self) -> Arc { Self::lock_bodies() - .get(&self.into()) + .get(&(self as *const _ as _)) .unwrap_or_else(|| self.panic_for_deleted()) .clone() } @@ -192,16 +197,26 @@ pub(crate) trait CApiObject: From + Into + Copy + Debug { #[ext] impl T { - fn lock_bodies() -> impl DerefMut>> { + fn lock_bodies() -> impl DerefMut>> { Self::bodies().lock().unwrap_or_else(|e| panic!("{e}")) } - fn panic_for_deleted(self) -> ! { - panic!("`{self:?}`は既に破棄されています"); + fn panic_for_deleted(&self) -> ! { + let display = self.display(); + panic!("{display}は既に破棄されています"); + } + + fn panic_for_in_use(&self) -> ! { + let display = self.display(); + panic!("{display}が破棄されようとしましたが、これはまだ利用中です"); } - fn panic_for_in_use(self) -> ! { - panic!("`{self:?}`が破棄されようとしましたが、これはまだ利用中です"); + fn display(&self) -> impl Display + '_ { + let type_name = any::type_name::() + .split("::") + .last() + .expect("should not empty"); + format!("`{type_name}` ({self:018p})") } } @@ -220,8 +235,8 @@ impl CApiObject for H { &HEADS } - fn bodies() -> &'static std::sync::Mutex>> { - static BODIES: LazyLock>>> = + fn bodies() -> &'static std::sync::Mutex>> { + static BODIES: LazyLock>>> = LazyLock::new(Default::default); &BODIES } diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index a1f20f973..dbea536be 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -23,12 +23,13 @@ use self::slice_owner::U8_SLICE_OWNER; use anstream::{AutoStream, RawStream}; use chrono::SecondsFormat; use colorchoice::ColorChoice; -use derive_more::{From, Into}; +use educe::Educe; use ref_cast::RefCastCustom; use std::env; use std::ffi::{CStr, CString}; use std::fmt; use std::io; +use std::mem::MaybeUninit; use std::os::raw::c_char; use std::ptr::NonNull; use std::sync::Once; @@ -262,9 +263,10 @@ pub unsafe extern "C" fn voicevox_onnxruntime_init_once( /// voicevox_open_jtalk_rc_delete(open_jtalk); /// ``` /// } -#[derive(Clone, Copy, Debug, From, Into)] +#[derive(Debug, Educe)] +#[educe(Default(expression = "Self { _padding: MaybeUninit::uninit() }"))] pub struct OpenJtalkRc { - id: u32, + _padding: MaybeUninit<[u8; 1]>, } /// ::OpenJtalkRc を構築(_construct_)する。 @@ -398,9 +400,10 @@ pub extern "C" fn voicevox_get_version() -> *const c_char { /// /// VVMファイルと対応する。 /// 構築(_construction_)は ::voicevox_voice_model_file_open で行い、破棄(_destruction_)は ::voicevox_voice_model_file_close で行う。 -#[derive(Clone, Copy, Debug, From, Into)] +#[derive(Debug, Educe)] +#[educe(Default(expression = "Self { _padding: MaybeUninit::uninit() }"))] pub struct VoicevoxVoiceModelFile { - id: u32, + _padding: MaybeUninit<[u8; 1]>, } /// 音声モデルID。 @@ -491,9 +494,10 @@ pub unsafe extern "C" fn voicevox_voice_model_file_close(model: NonNull構築(_construction_)は ::voicevox_synthesizer_new で行い、破棄(_destruction_)は ::voicevox_synthesizer_delete で行う。 -#[derive(Clone, Copy, Debug, From, Into)] +#[derive(Debug, Educe)] +#[educe(Default(expression = "Self { _padding: MaybeUninit::uninit() }"))] pub struct VoicevoxSynthesizer { - id: u32, + _padding: MaybeUninit<[u8; 1]>, } /// ::VoicevoxSynthesizer を構築(_construct_)する。 @@ -1228,9 +1232,10 @@ pub extern "C" fn voicevox_error_result_to_message( } /// ユーザー辞書。 -#[derive(Clone, Copy, Debug, From, Into)] +#[derive(Debug, Educe)] +#[educe(Default(expression = "Self { _padding: MaybeUninit::uninit() }"))] pub struct VoicevoxUserDict { - id: u32, + _padding: MaybeUninit<[u8; 1]>, } /// ユーザー辞書の単語。 diff --git a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml index cdb2dbc29..602824543 100644 --- a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml +++ b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml @@ -65,26 +65,26 @@ last_error_message = "Statusが初期化されていません" stderr = "" [double_delete_openjtalk] -stderr_contains_all = [ - "\n`OpenJtalkRc { id: 0 }`は既に破棄されています\n", +stderr_matches_all = [ + '\n`OpenJtalkRc` \(0x[0-9a-f]{16}\)は既に破棄されています\n', "\nthread caused non-unwinding panic. aborting.\n", ] [double_delete_synthesizer] -stderr_contains_all = [ - "\n`VoicevoxSynthesizer { id: 0 }`は既に破棄されています\n", +stderr_matches_all = [ + '\n`VoicevoxSynthesizer` \(0x[0-9a-f]{16}\)は既に破棄されています\n', "\nthread caused non-unwinding panic. aborting.\n", ] [double_delete_user_dict] -stderr_contains_all = [ - "\n`VoicevoxUserDict { id: 0 }`は既に破棄されています\n", +stderr_matches_all = [ + '\n`VoicevoxUserDict` \(0x[0-9a-f]{16}\)は既に破棄されています\n', "\nthread caused non-unwinding panic. aborting.\n", ] [double_delete_voice_model_file] -stderr_contains_all = [ - "\n`VoicevoxVoiceModelFile { id: 0 }`は既に破棄されています\n", +stderr_matches_all = [ + '\n`VoicevoxVoiceModelFile` \(0x[0-9a-f]{16}\)は既に破棄されています\n', "\nthread caused non-unwinding panic. aborting.\n", ] diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_openjtalk.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_openjtalk.rs index 20efff861..511ae3b41 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_openjtalk.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_openjtalk.rs @@ -46,8 +46,9 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { let mut assert = output.assert().try_failure()?.try_stdout("")?; - for s in &SNAPSHOTS.stderr_contains_all { - assert = assert.try_stderr(predicates::str::contains(s))?; + for s in &SNAPSHOTS.stderr_matches_all { + let p = predicates::str::is_match(s).unwrap_or_else(|e| panic!("{e}")); + assert = assert.try_stderr(p)?; } Ok(assert) } @@ -57,5 +58,5 @@ static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_openjt #[derive(Deserialize)] struct Snapshots { - stderr_contains_all: IndexSet, + stderr_matches_all: IndexSet, } diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_synthesizer.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_synthesizer.rs index 22fdb92c9..170ee718f 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_synthesizer.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_synthesizer.rs @@ -70,8 +70,9 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { let mut assert = output.assert().try_failure()?.try_stdout("")?; - for s in &SNAPSHOTS.stderr_contains_all { - assert = assert.try_stderr(predicates::str::contains(s))?; + for s in &SNAPSHOTS.stderr_matches_all { + let p = predicates::str::is_match(s).unwrap_or_else(|e| panic!("{e}")); + assert = assert.try_stderr(p)?; } Ok(assert) } @@ -81,5 +82,5 @@ static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_synthe #[derive(Deserialize)] struct Snapshots { - stderr_contains_all: IndexSet, + stderr_matches_all: IndexSet, } diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_user_dict.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_user_dict.rs index 13436e8fa..6256b63e0 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_user_dict.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_user_dict.rs @@ -31,8 +31,9 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { let mut assert = output.assert().try_failure()?.try_stdout("")?; - for s in &SNAPSHOTS.stderr_contains_all { - assert = assert.try_stderr(predicates::str::contains(s))?; + for s in &SNAPSHOTS.stderr_matches_all { + let p = predicates::str::is_match(s).unwrap_or_else(|e| panic!("{e}")); + assert = assert.try_stderr(p)?; } Ok(assert) } @@ -42,5 +43,5 @@ static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_user_d #[derive(Deserialize)] struct Snapshots { - stderr_contains_all: IndexSet, + stderr_matches_all: IndexSet, } diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_voice_model_file.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_voice_model_file.rs index 573f6bb1b..1ea6bdc19 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_voice_model_file.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_voice_model_file.rs @@ -43,8 +43,9 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { let mut assert = output.assert().try_failure()?.try_stdout("")?; - for s in &SNAPSHOTS.stderr_contains_all { - assert = assert.try_stderr(predicates::str::contains(s))?; + for s in &SNAPSHOTS.stderr_matches_all { + let p = predicates::str::is_match(s).unwrap_or_else(|e| panic!("{e}")); + assert = assert.try_stderr(p)?; } Ok(assert) } @@ -54,5 +55,5 @@ static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_voice_ #[derive(Deserialize)] struct Snapshots { - stderr_contains_all: IndexSet, + stderr_matches_all: IndexSet, } From ad753defdd48edb7ca396983f31ad739b467c7ce Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Tue, 8 Oct 2024 03:15:39 +0900 Subject: [PATCH 5/8] =?UTF-8?q?`drop=5Fbody`=E6=99=82=E3=81=AB`body`?= =?UTF-8?q?=E3=81=AE=E7=B5=82=E4=BA=86=E3=82=92=E5=BE=85=E3=81=A4=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + Cargo.toml | 1 + crates/voicevox_core_c_api/Cargo.toml | 1 + crates/voicevox_core_c_api/src/c_impls.rs | 56 ++++++++++++++++------- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c32b36d5d..e501f78f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4390,6 +4390,7 @@ dependencies = [ "libtest-mimic", "ndarray", "ndarray-stats", + "parking_lot", "predicates", "process_path", "ref-cast", diff --git a/Cargo.toml b/Cargo.toml index aaf8185ed..519562602 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ ndarray-stats = "0.5.1" octocrab = { version = "0.19.0", default-features = false } once_cell = "1.20.1" ouroboros = "0.18.4" +parking_lot = "0.12.1" parse-display = "0.8.2" pollster = "0.3.0" predicates = "3.1.2" diff --git a/crates/voicevox_core_c_api/Cargo.toml b/crates/voicevox_core_c_api/Cargo.toml index 2852aaa69..3eca7ddb1 100644 --- a/crates/voicevox_core_c_api/Cargo.toml +++ b/crates/voicevox_core_c_api/Cargo.toml @@ -31,6 +31,7 @@ easy-ext.workspace = true educe.workspace = true itertools.workspace = true libc.workspace = true +parking_lot = { workspace = true, features = ["arc_lock"] } process_path.workspace = true ref-cast.workspace = true serde_json = { workspace = true, features = ["preserve_order"] } diff --git a/crates/voicevox_core_c_api/src/c_impls.rs b/crates/voicevox_core_c_api/src/c_impls.rs index 43497660b..b29a3f6ad 100644 --- a/crates/voicevox_core_c_api/src/c_impls.rs +++ b/crates/voicevox_core_c_api/src/c_impls.rs @@ -4,7 +4,7 @@ use std::{ ffi::CString, fmt::{Debug, Display}, mem, - ops::DerefMut, + ops::{Deref, DerefMut}, path::Path, sync::{Arc, LazyLock}, }; @@ -13,6 +13,7 @@ use camino::Utf8Path; use duplicate::duplicate_item; use easy_ext::ext; use ref_cast::ref_cast_custom; +use tracing::warn; use voicevox_core::{InitializeOptions, Result, SpeakerMeta, VoiceModelId}; use crate::{ @@ -155,10 +156,15 @@ pub(crate) trait CApiObject: Default + Debug { fn heads() -> &'static boxcar::Vec; + #[expect(clippy::type_complexity, reason = "コメント書いてる")] fn bodies() -> &'static std::sync::Mutex< HashMap< usize, // `heads`の要素へのポインタのアドレス - Arc, + Arc< + parking_lot::RwLock< + Option, // `RwLock`をdropする直前まで`Some` + >, + >, >, >; @@ -167,7 +173,8 @@ pub(crate) trait CApiObject: Default + Debug { let i = Self::heads().push(Default::default()); let this = &Self::heads()[i]; - Self::lock_bodies().insert(this as *const _ as _, body.into()); + let body = parking_lot::RwLock::new(body.into()).into(); + Self::lock_bodies().insert(this as *const _ as _, body); this } @@ -181,23 +188,38 @@ pub(crate) trait CApiObject: Default + Debug { let body = Self::lock_bodies() .remove(&(self as *const _ as _)) .unwrap_or_else(|| self.panic_for_deleted()); - drop(Arc::into_inner(body).unwrap_or_else(|| self.panic_for_in_use())); + + drop( + body.try_write_arc() + .unwrap_or_else(|| { + warn!("{} is still in use. Waiting before closing", self.display()); + body.write_arc() + }) + .take() + .unwrap_or_else(|| self.panic_for_deleted()), + ); } /// # Panics /// /// `drop_body`を呼んでいるとパニックする。 - fn body(&self) -> Arc { - Self::lock_bodies() + fn body(&self) -> impl Deref { + let body = Self::lock_bodies() .get(&(self as *const _ as _)) .unwrap_or_else(|| self.panic_for_deleted()) - .clone() + .read_arc(); + voicevox_core::__internal::interop::raii::try_map_guard(body, |body| { + body.as_ref().ok_or(()) + }) + .unwrap_or_else(|()| self.panic_for_deleted()) } } #[ext] impl T { - fn lock_bodies() -> impl DerefMut>> { + fn lock_bodies( + ) -> impl DerefMut>>>> + { Self::bodies().lock().unwrap_or_else(|e| panic!("{e}")) } @@ -206,11 +228,6 @@ impl T { panic!("{display}は既に破棄されています"); } - fn panic_for_in_use(&self) -> ! { - let display = self.display(); - panic!("{display}が破棄されようとしましたが、これはまだ利用中です"); - } - fn display(&self) -> impl Display + '_ { let type_name = any::type_name::() .split("::") @@ -235,9 +252,16 @@ impl CApiObject for H { &HEADS } - fn bodies() -> &'static std::sync::Mutex>> { - static BODIES: LazyLock>>> = - LazyLock::new(Default::default); + fn bodies() -> &'static std::sync::Mutex< + HashMap>>>, + > { + #[expect( + clippy::type_complexity, + reason = "`CApiObject::bodies`の方でコメント書いてる" + )] + static BODIES: LazyLock< + std::sync::Mutex>>>>, + > = LazyLock::new(Default::default); &BODIES } } From 61cc6dfcb63379721c1878b080787670b38ec695 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Tue, 8 Oct 2024 03:55:49 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=81=A8=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../include/voicevox_core.h | 85 +++++++++++-------- crates/voicevox_core_c_api/src/c_impls.rs | 21 ++--- crates/voicevox_core_c_api/src/lib.rs | 85 +++++++++++-------- 3 files changed, 105 insertions(+), 86 deletions(-) diff --git a/crates/voicevox_core_c_api/include/voicevox_core.h b/crates/voicevox_core_c_api/include/voicevox_core.h index e8656d8ec..e952903c0 100644 --- a/crates/voicevox_core_c_api/include/voicevox_core.h +++ b/crates/voicevox_core_c_api/include/voicevox_core.h @@ -543,8 +543,8 @@ VoicevoxResultCode voicevox_open_jtalk_rc_new(const char *open_jtalk_dic_dir, * @param [in] user_dict ユーザー辞書 * * \safety{ - * - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならず、また ::voicevox_open_jtalk_rc_delete で解放されていてはいけない。 - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 + * - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならない。 + * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * } */ #ifdef _WIN32 @@ -556,6 +556,10 @@ VoicevoxResultCode voicevox_open_jtalk_rc_use_user_dict(const struct OpenJtalkRc /** * ::OpenJtalkRc を破棄(_destruct_)する。 * + * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 + * + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 + * * @param [in] open_jtalk 破棄対象 * * \example{ @@ -565,8 +569,7 @@ VoicevoxResultCode voicevox_open_jtalk_rc_use_user_dict(const struct OpenJtalkRc * } * * \safety{ - * - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 - * - `open_jtalk`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 + * - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならない。 * } */ #ifdef _WIN32 @@ -618,7 +621,7 @@ VoicevoxResultCode voicevox_voice_model_file_open(const char *path, * @param [out] output_voice_model_id 音声モデルID * * \safety{ - * - `model`は ::voicevox_voice_model_file_open で得たものでなければならず、また ::voicevox_voice_model_file_close で解放されていてはいけない。 + * - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 * - `output_voice_model_id`は書き込みについて有効でなければならない。 * } */ @@ -638,7 +641,7 @@ void voicevox_voice_model_file_id(const struct VoicevoxVoiceModelFile *model, * @returns メタ情報のJSON文字列 * * \safety{ - * - `model`は ::voicevox_voice_model_file_open で得たものでなければならず、また ::voicevox_voice_model_file_close で解放されていてはいけない。 + * - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 * } */ #ifdef _WIN32 @@ -649,11 +652,14 @@ char *voicevox_voice_model_file_create_metas_json(const struct VoicevoxVoiceMode /** * ::VoicevoxVoiceModelFile を、所有しているファイルディスクリプタを閉じた上で破棄(_destruct_)する。 * + * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 + * + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 + * * @param [in] model 破棄対象 * * \safety{ - * - `model`は ::voicevox_voice_model_file_open で得たものでなければならず、また既にこの関数で解放されていてはいけない。 - * - `model`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 + * - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 * } */ #ifdef _WIN32 @@ -673,7 +679,7 @@ void voicevox_voice_model_file_close(struct VoicevoxVoiceModelFile *model); * * \safety{ * - `onnxruntime`は ::voicevox_onnxruntime_load_once または ::voicevox_onnxruntime_init_once で得たものでなければならない。 - * - `open_jtalk`は ::voicevox_voice_model_file_open で得たものでなければならず、また ::voicevox_open_jtalk_rc_new で解放されていてはいけない。 + * - `open_jtalk`は ::voicevox_voice_model_file_open で得たものでなければならない。 * - `out_synthesizer`は書き込みについて有効でなければならない。 * } */ @@ -688,11 +694,14 @@ VoicevoxResultCode voicevox_synthesizer_new(const struct VoicevoxOnnxruntime *on /** * ::VoicevoxSynthesizer を破棄(_destruct_)する。 * + * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 + * + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 + * * @param [in] synthesizer 破棄対象 * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 - * - `synthesizer`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * } */ #ifdef _WIN32 @@ -709,8 +718,8 @@ void voicevox_synthesizer_delete(struct VoicevoxSynthesizer *synthesizer); * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 - * - `model`は ::voicevox_voice_model_file_open で得たものでなければならず、また ::voicevox_voice_model_file_close で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 + * - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 * } */ #ifdef _WIN32 @@ -728,7 +737,7 @@ VoicevoxResultCode voicevox_synthesizer_load_voice_model(const struct VoicevoxSy * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `model_id`は読み込みについて有効でなければならない。 * } */ @@ -746,7 +755,7 @@ VoicevoxResultCode voicevox_synthesizer_unload_voice_model(const struct Voicevox * @returns ::VoicevoxOnnxruntime のインスタンス * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * } */ #ifdef _WIN32 @@ -762,7 +771,7 @@ const struct VoicevoxOnnxruntime *voicevox_synthesizer_get_onnxruntime(const str * @returns GPUモードかどうか * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * } */ #ifdef _WIN32 @@ -779,7 +788,7 @@ bool voicevox_synthesizer_is_gpu_mode(const struct VoicevoxSynthesizer *synthesi * @returns モデルが読み込まれているかどうか * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `model_id`は読み込みについて有効でなければならない。 * } */ @@ -799,7 +808,7 @@ bool voicevox_synthesizer_is_loaded_voice_model(const struct VoicevoxSynthesizer * @return メタ情報のJSON文字列 * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * } */ #ifdef _WIN32 @@ -859,7 +868,7 @@ VoicevoxResultCode voicevox_onnxruntime_create_supported_devices_json(const stru * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -894,7 +903,7 @@ VoicevoxResultCode voicevox_synthesizer_create_audio_query_from_kana(const struc * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -930,7 +939,7 @@ VoicevoxResultCode voicevox_synthesizer_create_audio_query(const struct Voicevox * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -965,7 +974,7 @@ VoicevoxResultCode voicevox_synthesizer_create_accent_phrases_from_kana(const st * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -991,7 +1000,7 @@ VoicevoxResultCode voicevox_synthesizer_create_accent_phrases(const struct Voice * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -1017,7 +1026,7 @@ VoicevoxResultCode voicevox_synthesizer_replace_mora_data(const struct VoicevoxS * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -1043,7 +1052,7 @@ VoicevoxResultCode voicevox_synthesizer_replace_phoneme_length(const struct Voic * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -1080,7 +1089,7 @@ struct VoicevoxSynthesisOptions voicevox_make_default_synthesis_options(void); * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `audio_query_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_wav_length`は書き込みについて有効でなければならない。 * - `output_wav`は書き込みについて有効でなければならない。 @@ -1120,7 +1129,7 @@ struct VoicevoxTtsOptions voicevox_make_default_tts_options(void); * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_wav_length`は書き込みについて有効でなければならない。 * - `output_wav`は書き込みについて有効でなければならない。 @@ -1151,7 +1160,7 @@ VoicevoxResultCode voicevox_synthesizer_tts_from_kana(const struct VoicevoxSynth * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_wav_length`は書き込みについて有効でなければならない。 * - `output_wav`は書き込みについて有効でなければならない。 @@ -1269,7 +1278,7 @@ struct VoicevoxUserDict *voicevox_user_dict_new(void); * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 + * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `dict_path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * } */ @@ -1291,7 +1300,7 @@ VoicevoxResultCode voicevox_user_dict_load(const struct VoicevoxUserDict *user_d * @param user_dict は有効な :VoicevoxUserDict のポインタであること * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 + * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_word_uuid`は書き込みについて有効でなければならない。 * } @@ -1312,7 +1321,7 @@ VoicevoxResultCode voicevox_user_dict_add_word(const struct VoicevoxUserDict *us * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 + * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `word_uuid`は読み込みについて有効でなければならない。 * - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * } @@ -1332,7 +1341,7 @@ VoicevoxResultCode voicevox_user_dict_update_word(const struct VoicevoxUserDict * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 + * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `word_uuid`は読み込みについて有効でなければならない。 * } */ @@ -1352,7 +1361,7 @@ VoicevoxResultCode voicevox_user_dict_remove_word(const struct VoicevoxUserDict * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 + * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `output_json`は書き込みについて有効でなければならない。 * } */ @@ -1370,7 +1379,7 @@ VoicevoxResultCode voicevox_user_dict_to_json(const struct VoicevoxUserDict *use * @returns 結果コード * * \safety{ - * - `user_dict`と`other_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 + * - `user_dict`と`other_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * } */ #ifdef _WIN32 @@ -1386,7 +1395,7 @@ VoicevoxResultCode voicevox_user_dict_import(const struct VoicevoxUserDict *user * @param [in] path 保存先のファイルパス * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 + * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * } */ @@ -1399,10 +1408,14 @@ VoicevoxResultCode voicevox_user_dict_save(const struct VoicevoxUserDict *user_d /** * ユーザー辞書を破棄(_destruct_)する。 * + * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 + * + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 + * * @param [in] user_dict 破棄対象 * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 + * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * } */ #ifdef _WIN32 diff --git a/crates/voicevox_core_c_api/src/c_impls.rs b/crates/voicevox_core_c_api/src/c_impls.rs index b29a3f6ad..b56bdcd16 100644 --- a/crates/voicevox_core_c_api/src/c_impls.rs +++ b/crates/voicevox_core_c_api/src/c_impls.rs @@ -141,16 +141,12 @@ fn metas_to_json(metas: &[SpeakerMeta]) -> CString { /// } /// ``` /// -/// `RustApi`そのものではなくこのトレイトのインスタンスをユーザーに渡すようにし、次のユーザー操作に対するセーフティネットを実現する。 +/// `RustApiObject`そのものではなくこのトレイトのインスタンスをユーザーに渡すようにすることで、次のことを実現する。 /// -/// 1. オブジェクトが他スレッドでアクセスされている最中に"delete"を試みる -/// 2. "delete"後に他の通常のメソッド関数の利用を試みる -/// 3. "delete"後に"delete"を試みる -/// -/// ただし次のゲッター関数に関しては機能しない。 -/// -/// - `voicevox_voice_model_file_id` -/// - `voicevox_voice_model_file_get_metas_json` +/// 1. "delete"時に対象オブジェクトに対するアクセスがあった場合、アクセスが終わるまで待つ +/// 2. 次のユーザー操作に対するセーフティネットを張り、パニックするようにする +/// 1. "delete"後に他の通常のメソッド関数の利用を試みる +/// 2. "delete"後に"delete"を試みる pub(crate) trait CApiObject: Default + Debug { type RustApiObject: 'static; @@ -180,10 +176,7 @@ pub(crate) trait CApiObject: Default + Debug { /// # Panics /// - /// 次の場合にパニックする。 - /// - /// * `self`に対して以前にこの関数を呼んでいた場合 - /// * `self`がまだ他で利用中である場合 + /// `self`に対してこの関数を二度呼ぶとパニックする。 fn drop_body(&self) { let body = Self::lock_bodies() .remove(&(self as *const _ as _)) @@ -202,7 +195,7 @@ pub(crate) trait CApiObject: Default + Debug { /// # Panics /// - /// `drop_body`を呼んでいるとパニックする。 + /// `self`に対して`drop_body`を呼んでいるとパニックする。 fn body(&self) -> impl Deref { let body = Self::lock_bodies() .get(&(self as *const _ as _)) diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index dbea536be..6879b89c5 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -311,8 +311,8 @@ pub unsafe extern "C" fn voicevox_open_jtalk_rc_new( /// @param [in] user_dict ユーザー辞書 /// /// \safety{ -/// - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならず、また ::voicevox_open_jtalk_rc_delete で解放されていてはいけない。 -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 +/// - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならない。 +/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// } #[no_mangle] pub extern "C" fn voicevox_open_jtalk_rc_use_user_dict( @@ -328,6 +328,10 @@ pub extern "C" fn voicevox_open_jtalk_rc_use_user_dict( /// ::OpenJtalkRc を破棄(_destruct_)する。 /// +/// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 +/// +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 +/// /// @param [in] open_jtalk 破棄対象 /// /// \example{ @@ -337,8 +341,7 @@ pub extern "C" fn voicevox_open_jtalk_rc_use_user_dict( /// } /// /// \safety{ -/// - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 -/// - `open_jtalk`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 +/// - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_open_jtalk_rc_delete(open_jtalk: NonNull) { @@ -445,7 +448,7 @@ pub unsafe extern "C" fn voicevox_voice_model_file_open( /// @param [out] output_voice_model_id 音声モデルID /// /// \safety{ -/// - `model`は ::voicevox_voice_model_file_open で得たものでなければならず、また ::voicevox_voice_model_file_close で解放されていてはいけない。 +/// - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 /// - `output_voice_model_id`は書き込みについて有効でなければならない。 /// } #[no_mangle] @@ -467,7 +470,7 @@ pub unsafe extern "C" fn voicevox_voice_model_file_id( /// @returns メタ情報のJSON文字列 /// /// \safety{ -/// - `model`は ::voicevox_voice_model_file_open で得たものでなければならず、また ::voicevox_voice_model_file_close で解放されていてはいけない。 +/// - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 /// } #[no_mangle] pub extern "C" fn voicevox_voice_model_file_create_metas_json( @@ -479,11 +482,14 @@ pub extern "C" fn voicevox_voice_model_file_create_metas_json( /// ::VoicevoxVoiceModelFile を、所有しているファイルディスクリプタを閉じた上で破棄(_destruct_)する。 /// +/// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 +/// +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 +/// /// @param [in] model 破棄対象 /// /// \safety{ -/// - `model`は ::voicevox_voice_model_file_open で得たものでなければならず、また既にこの関数で解放されていてはいけない。 -/// - `model`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 +/// - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_voice_model_file_close(model: NonNull) { @@ -511,7 +517,7 @@ pub struct VoicevoxSynthesizer { /// /// \safety{ /// - `onnxruntime`は ::voicevox_onnxruntime_load_once または ::voicevox_onnxruntime_init_once で得たものでなければならない。 -/// - `open_jtalk`は ::voicevox_voice_model_file_open で得たものでなければならず、また ::voicevox_open_jtalk_rc_new で解放されていてはいけない。 +/// - `open_jtalk`は ::voicevox_voice_model_file_open で得たものでなければならない。 /// - `out_synthesizer`は書き込みについて有効でなければならない。 /// } #[no_mangle] @@ -533,11 +539,14 @@ pub unsafe extern "C" fn voicevox_synthesizer_new( /// ::VoicevoxSynthesizer を破棄(_destruct_)する。 /// +/// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 +/// +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 +/// /// @param [in] synthesizer 破棄対象 /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 -/// - `synthesizer`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_delete(synthesizer: NonNull) { @@ -553,8 +562,8 @@ pub unsafe extern "C" fn voicevox_synthesizer_delete(synthesizer: NonNull読み込みについて有効でなければならない。 /// } #[no_mangle] @@ -593,7 +602,7 @@ pub extern "C" fn voicevox_synthesizer_unload_voice_model( /// @returns ::VoicevoxOnnxruntime のインスタンス /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// } #[no_mangle] pub extern "C" fn voicevox_synthesizer_get_onnxruntime( @@ -609,7 +618,7 @@ pub extern "C" fn voicevox_synthesizer_get_onnxruntime( /// @returns GPUモードかどうか /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// } #[no_mangle] pub extern "C" fn voicevox_synthesizer_is_gpu_mode(synthesizer: &VoicevoxSynthesizer) -> bool { @@ -625,7 +634,7 @@ pub extern "C" fn voicevox_synthesizer_is_gpu_mode(synthesizer: &VoicevoxSynthes /// @returns モデルが読み込まれているかどうか /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `model_id`は読み込みについて有効でなければならない。 /// } #[no_mangle] @@ -647,7 +656,7 @@ pub extern "C" fn voicevox_synthesizer_is_loaded_voice_model( /// @return メタ情報のJSON文字列 /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// } #[no_mangle] pub extern "C" fn voicevox_synthesizer_create_metas_json( @@ -719,7 +728,7 @@ pub unsafe extern "C" fn voicevox_onnxruntime_create_supported_devices_json( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } @@ -767,7 +776,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query_from_kana( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } @@ -816,7 +825,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } @@ -862,7 +871,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases_from_kana( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } @@ -899,7 +908,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } @@ -938,7 +947,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_data( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } @@ -977,7 +986,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_phoneme_length( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } @@ -1033,7 +1042,7 @@ pub extern "C" fn voicevox_make_default_synthesis_options() -> VoicevoxSynthesis /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `audio_query_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_wav_length`は書き込みについて有効でなければならない。 /// - `output_wav`は書き込みについて有効でなければならない。 @@ -1093,7 +1102,7 @@ pub extern "C" fn voicevox_make_default_tts_options() -> VoicevoxTtsOptions { /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_wav_length`は書き込みについて有効でなければならない。 /// - `output_wav`は書き込みについて有効でなければならない。 @@ -1134,7 +1143,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts_from_kana( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_wav_length`は書き込みについて有効でなければならない。 /// - `output_wav`は書き込みについて有効でなければならない。 @@ -1310,7 +1319,7 @@ pub extern "C" fn voicevox_user_dict_new() -> NonNull { /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 +/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `dict_path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// } #[no_mangle] @@ -1338,7 +1347,7 @@ pub unsafe extern "C" fn voicevox_user_dict_load( /// @param user_dict は有効な :VoicevoxUserDict のポインタであること /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 +/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_word_uuid`は書き込みについて有効でなければならない。 /// } @@ -1366,7 +1375,7 @@ pub unsafe extern "C" fn voicevox_user_dict_add_word( /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 +/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `word_uuid`は読み込みについて有効でなければならない。 /// - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// } @@ -1393,7 +1402,7 @@ pub unsafe extern "C" fn voicevox_user_dict_update_word( /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 +/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `word_uuid`は読み込みについて有効でなければならない。 /// } #[no_mangle] @@ -1419,7 +1428,7 @@ pub extern "C" fn voicevox_user_dict_remove_word( /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 +/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `output_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] @@ -1441,7 +1450,7 @@ pub unsafe extern "C" fn voicevox_user_dict_to_json( /// @returns 結果コード /// /// \safety{ -/// - `user_dict`と`other_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 +/// - `user_dict`と`other_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// } #[no_mangle] pub extern "C" fn voicevox_user_dict_import( @@ -1461,7 +1470,7 @@ pub extern "C" fn voicevox_user_dict_import( /// @param [in] path 保存先のファイルパス /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 +/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// } #[no_mangle] @@ -1479,10 +1488,14 @@ pub unsafe extern "C" fn voicevox_user_dict_save( /// ユーザー辞書を破棄(_destruct_)する。 /// +/// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 +/// +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 +/// /// @param [in] user_dict 破棄対象 /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 +/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_user_dict_delete(user_dict: NonNull) { From e6abd7761eb2f89cf74a3b4a68588b1368c53a90 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Tue, 8 Oct 2024 05:34:16 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=E3=82=AA=E3=83=96=E3=82=B8=E3=82=A7?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=81=A8=E3=81=97=E3=81=A6=E6=B8=A1=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=82=8B=E3=83=9D=E3=82=A4=E3=83=B3=E3=82=BF=E3=82=92?= =?UTF-8?q?=E4=BF=A1=E9=A0=BC=E3=81=97=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 77 ++++++++- Cargo.toml | 1 - crates/voicevox_core_c_api/Cargo.toml | 1 - .../include/voicevox_core.h | 66 -------- crates/voicevox_core_c_api/src/c_impls.rs | 145 +++------------- crates/voicevox_core_c_api/src/lib.rs | 156 ++++++------------ crates/voicevox_core_c_api/src/object.rs | 145 ++++++++++++++++ 7 files changed, 284 insertions(+), 307 deletions(-) create mode 100644 crates/voicevox_core_c_api/src/object.rs diff --git a/Cargo.lock b/Cargo.lock index e501f78f8..1dd3d5d53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,7 +366,7 @@ dependencies = [ "bitflags 2.5.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.11.0", "lazy_static", "lazycell", "log", @@ -440,12 +440,6 @@ dependencies = [ "piper", ] -[[package]] -name = "boxcar" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba19c552ee63cb6646b75e1166d1bdb8a6d34a6d19e319dec88c8adadff2db3" - [[package]] name = "bstr" version = "1.2.0" @@ -1887,6 +1881,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -2000,7 +2003,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.0", ] [[package]] @@ -4371,7 +4374,6 @@ dependencies = [ "anstyle-query", "anyhow", "assert_cmd", - "boxcar", "camino", "chrono", "clap 4.5.19", @@ -4722,6 +4724,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -4744,6 +4761,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4762,6 +4785,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4780,6 +4809,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4804,6 +4839,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4822,6 +4863,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4834,6 +4881,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4852,6 +4905,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 519562602..69ecbb58a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ async_zip = "=0.0.16" bindgen = "0.69.4" binstall-tar = "0.4.42" blocking = "1.6.1" -boxcar = "0.2.6" bytes = "1.7.2" camino = "1.1.9" cargo_metadata = "0.18.1" diff --git a/crates/voicevox_core_c_api/Cargo.toml b/crates/voicevox_core_c_api/Cargo.toml index 3eca7ddb1..7c4484907 100644 --- a/crates/voicevox_core_c_api/Cargo.toml +++ b/crates/voicevox_core_c_api/Cargo.toml @@ -20,7 +20,6 @@ link-onnxruntime = ["voicevox_core/link-onnxruntime"] [dependencies] anstream = { workspace = true, default-features = false, features = ["auto"] } anstyle-query.workspace = true -boxcar.workspace = true camino.workspace = true chrono = { workspace = true, default-features = false, features = ["clock"] } colorchoice.workspace = true diff --git a/crates/voicevox_core_c_api/include/voicevox_core.h b/crates/voicevox_core_c_api/include/voicevox_core.h index e952903c0..43a1a3b62 100644 --- a/crates/voicevox_core_c_api/include/voicevox_core.h +++ b/crates/voicevox_core_c_api/include/voicevox_core.h @@ -541,11 +541,6 @@ VoicevoxResultCode voicevox_open_jtalk_rc_new(const char *open_jtalk_dic_dir, * * @param [in] open_jtalk Open JTalkのオブジェクト * @param [in] user_dict ユーザー辞書 - * - * \safety{ - * - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならない。 - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -567,10 +562,6 @@ VoicevoxResultCode voicevox_open_jtalk_rc_use_user_dict(const struct OpenJtalkRc * voicevox_open_jtalk_rc_delete(open_jtalk); * ``` * } - * - * \safety{ - * - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -621,7 +612,6 @@ VoicevoxResultCode voicevox_voice_model_file_open(const char *path, * @param [out] output_voice_model_id 音声モデルID * * \safety{ - * - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 * - `output_voice_model_id`は書き込みについて有効でなければならない。 * } */ @@ -639,10 +629,6 @@ void voicevox_voice_model_file_id(const struct VoicevoxVoiceModelFile *model, * @param [in] model 音声モデル * * @returns メタ情報のJSON文字列 - * - * \safety{ - * - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -657,10 +643,6 @@ char *voicevox_voice_model_file_create_metas_json(const struct VoicevoxVoiceMode * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 * * @param [in] model 破棄対象 - * - * \safety{ - * - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -679,7 +661,6 @@ void voicevox_voice_model_file_close(struct VoicevoxVoiceModelFile *model); * * \safety{ * - `onnxruntime`は ::voicevox_onnxruntime_load_once または ::voicevox_onnxruntime_init_once で得たものでなければならない。 - * - `open_jtalk`は ::voicevox_voice_model_file_open で得たものでなければならない。 * - `out_synthesizer`は書き込みについて有効でなければならない。 * } */ @@ -699,10 +680,6 @@ VoicevoxResultCode voicevox_synthesizer_new(const struct VoicevoxOnnxruntime *on * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 * * @param [in] synthesizer 破棄対象 - * - * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -716,11 +693,6 @@ void voicevox_synthesizer_delete(struct VoicevoxSynthesizer *synthesizer); * @param [in] model 音声モデル * * @returns 結果コード - * - * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 - * - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -737,7 +709,6 @@ VoicevoxResultCode voicevox_synthesizer_load_voice_model(const struct VoicevoxSy * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `model_id`は読み込みについて有効でなければならない。 * } */ @@ -753,10 +724,6 @@ VoicevoxResultCode voicevox_synthesizer_unload_voice_model(const struct Voicevox * @param [in] synthesizer 音声シンセサイザ * * @returns ::VoicevoxOnnxruntime のインスタンス - * - * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -769,10 +736,6 @@ const struct VoicevoxOnnxruntime *voicevox_synthesizer_get_onnxruntime(const str * @param [in] synthesizer 音声シンセサイザ * * @returns GPUモードかどうか - * - * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -788,7 +751,6 @@ bool voicevox_synthesizer_is_gpu_mode(const struct VoicevoxSynthesizer *synthesi * @returns モデルが読み込まれているかどうか * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `model_id`は読み込みについて有効でなければならない。 * } */ @@ -806,10 +768,6 @@ bool voicevox_synthesizer_is_loaded_voice_model(const struct VoicevoxSynthesizer * @param [in] synthesizer 音声シンセサイザ * * @return メタ情報のJSON文字列 - * - * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -868,7 +826,6 @@ VoicevoxResultCode voicevox_onnxruntime_create_supported_devices_json(const stru * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -903,7 +860,6 @@ VoicevoxResultCode voicevox_synthesizer_create_audio_query_from_kana(const struc * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -939,7 +895,6 @@ VoicevoxResultCode voicevox_synthesizer_create_audio_query(const struct Voicevox * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -974,7 +929,6 @@ VoicevoxResultCode voicevox_synthesizer_create_accent_phrases_from_kana(const st * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -1000,7 +954,6 @@ VoicevoxResultCode voicevox_synthesizer_create_accent_phrases(const struct Voice * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -1026,7 +979,6 @@ VoicevoxResultCode voicevox_synthesizer_replace_mora_data(const struct VoicevoxS * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -1052,7 +1004,6 @@ VoicevoxResultCode voicevox_synthesizer_replace_phoneme_length(const struct Voic * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -1089,7 +1040,6 @@ struct VoicevoxSynthesisOptions voicevox_make_default_synthesis_options(void); * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `audio_query_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_wav_length`は書き込みについて有効でなければならない。 * - `output_wav`は書き込みについて有効でなければならない。 @@ -1129,7 +1079,6 @@ struct VoicevoxTtsOptions voicevox_make_default_tts_options(void); * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_wav_length`は書き込みについて有効でなければならない。 * - `output_wav`は書き込みについて有効でなければならない。 @@ -1160,7 +1109,6 @@ VoicevoxResultCode voicevox_synthesizer_tts_from_kana(const struct VoicevoxSynth * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 * - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_wav_length`は書き込みについて有効でなければならない。 * - `output_wav`は書き込みについて有効でなければならない。 @@ -1278,7 +1226,6 @@ struct VoicevoxUserDict *voicevox_user_dict_new(void); * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `dict_path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * } */ @@ -1300,7 +1247,6 @@ VoicevoxResultCode voicevox_user_dict_load(const struct VoicevoxUserDict *user_d * @param user_dict は有効な :VoicevoxUserDict のポインタであること * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_word_uuid`は書き込みについて有効でなければならない。 * } @@ -1321,7 +1267,6 @@ VoicevoxResultCode voicevox_user_dict_add_word(const struct VoicevoxUserDict *us * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `word_uuid`は読み込みについて有効でなければならない。 * - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * } @@ -1341,7 +1286,6 @@ VoicevoxResultCode voicevox_user_dict_update_word(const struct VoicevoxUserDict * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `word_uuid`は読み込みについて有効でなければならない。 * } */ @@ -1361,7 +1305,6 @@ VoicevoxResultCode voicevox_user_dict_remove_word(const struct VoicevoxUserDict * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `output_json`は書き込みについて有効でなければならない。 * } */ @@ -1377,10 +1320,6 @@ VoicevoxResultCode voicevox_user_dict_to_json(const struct VoicevoxUserDict *use * @param [in] user_dict ユーザー辞書 * @param [in] other_dict インポートするユーザー辞書 * @returns 結果コード - * - * \safety{ - * - `user_dict`と`other_dict`は ::voicevox_user_dict_new で得たものでなければならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -1395,7 +1334,6 @@ VoicevoxResultCode voicevox_user_dict_import(const struct VoicevoxUserDict *user * @param [in] path 保存先のファイルパス * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 * - `path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * } */ @@ -1413,10 +1351,6 @@ VoicevoxResultCode voicevox_user_dict_save(const struct VoicevoxUserDict *user_d * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 * * @param [in] user_dict 破棄対象 - * - * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) diff --git a/crates/voicevox_core_c_api/src/c_impls.rs b/crates/voicevox_core_c_api/src/c_impls.rs index b56bdcd16..c657d0908 100644 --- a/crates/voicevox_core_c_api/src/c_impls.rs +++ b/crates/voicevox_core_c_api/src/c_impls.rs @@ -1,11 +1,8 @@ use std::{ - any, collections::HashMap, ffi::CString, - fmt::{Debug, Display}, - mem, - ops::{Deref, DerefMut}, path::Path, + ptr::NonNull, sync::{Arc, LazyLock}, }; @@ -13,11 +10,12 @@ use camino::Utf8Path; use duplicate::duplicate_item; use easy_ext::ext; use ref_cast::ref_cast_custom; -use tracing::warn; use voicevox_core::{InitializeOptions, Result, SpeakerMeta, VoiceModelId}; use crate::{ - helpers::CApiResult, OpenJtalkRc, VoicevoxOnnxruntime, VoicevoxSynthesizer, VoicevoxUserDict, + helpers::CApiResult, + object::{CApiObject, CApiObjectPtrExt as _}, + OpenJtalkRc, VoicevoxOnnxruntime, VoicevoxSynthesizer, VoicevoxUserDict, VoicevoxVoiceModelFile, }; @@ -73,7 +71,7 @@ macro_rules! to_cstr { use to_cstr; impl OpenJtalkRc { - pub(crate) fn new(open_jtalk_dic_dir: impl AsRef) -> Result<&'static Self> { + pub(crate) fn new(open_jtalk_dic_dir: impl AsRef) -> Result> { let body = voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?; Ok(::new(body)) } @@ -82,46 +80,52 @@ impl OpenJtalkRc { impl VoicevoxSynthesizer { pub(crate) fn new( onnxruntime: &'static VoicevoxOnnxruntime, - open_jtalk: &OpenJtalkRc, + open_jtalk: *const OpenJtalkRc, options: &InitializeOptions, - ) -> Result<&'static Self> { + ) -> Result> { let body = voicevox_core::blocking::Synthesizer::new( &onnxruntime.0, - (*open_jtalk.body()).clone(), + open_jtalk.body().clone(), options, )?; Ok(::new(body)) } +} - pub(crate) fn onnxruntime(&self) -> &'static VoicevoxOnnxruntime { +#[ext(VoicevoxSynthesizerPtrExt)] +impl *const VoicevoxSynthesizer { + pub(crate) fn onnxruntime(self) -> &'static VoicevoxOnnxruntime { VoicevoxOnnxruntime::new(self.body().onnxruntime()) } pub(crate) fn load_voice_model( - &self, + self, model: &voicevox_core::blocking::VoiceModelFile, ) -> CApiResult<()> { self.body().load_voice_model(model)?; Ok(()) } - pub(crate) fn unload_voice_model(&self, model_id: VoiceModelId) -> Result<()> { + pub(crate) fn unload_voice_model(self, model_id: VoiceModelId) -> Result<()> { self.body().unload_voice_model(model_id)?; Ok(()) } - pub(crate) fn metas(&self) -> CString { + pub(crate) fn metas(self) -> CString { metas_to_json(&self.body().metas()) } } impl VoicevoxVoiceModelFile { - pub(crate) fn open(path: impl AsRef) -> Result<&'static Self> { + pub(crate) fn open(path: impl AsRef) -> Result> { let model = voicevox_core::blocking::VoiceModelFile::open(path)?; Ok(Self::new(model)) } +} - pub(crate) fn metas(&self) -> CString { +#[ext(VoicevoxVoiceModelFilePtrExt)] +impl *const VoicevoxVoiceModelFile { + pub(crate) fn metas(self) -> CString { metas_to_json(self.body().metas()) } } @@ -131,105 +135,6 @@ fn metas_to_json(metas: &[SpeakerMeta]) -> CString { CString::new(metas).expect("should not contain NUL") } -/// プロセスの終わりまでデストラクトされない、ユーザーにオブジェクトとして貸し出す1-bit長の構造体。 -/// -/// インスタンスは次のような形。 -/// -/// ``` -/// pub struct VoicevoxSynthesizer { -/// _padding: MaybeUninit<[u8; 1]>, -/// } -/// ``` -/// -/// `RustApiObject`そのものではなくこのトレイトのインスタンスをユーザーに渡すようにすることで、次のことを実現する。 -/// -/// 1. "delete"時に対象オブジェクトに対するアクセスがあった場合、アクセスが終わるまで待つ -/// 2. 次のユーザー操作に対するセーフティネットを張り、パニックするようにする -/// 1. "delete"後に他の通常のメソッド関数の利用を試みる -/// 2. "delete"後に"delete"を試みる -pub(crate) trait CApiObject: Default + Debug { - type RustApiObject: 'static; - - fn heads() -> &'static boxcar::Vec; - - #[expect(clippy::type_complexity, reason = "コメント書いてる")] - fn bodies() -> &'static std::sync::Mutex< - HashMap< - usize, // `heads`の要素へのポインタのアドレス - Arc< - parking_lot::RwLock< - Option, // `RwLock`をdropする直前まで`Some` - >, - >, - >, - >; - - fn new(body: Self::RustApiObject) -> &'static Self { - assert!(mem::size_of::() > 0); - - let i = Self::heads().push(Default::default()); - let this = &Self::heads()[i]; - let body = parking_lot::RwLock::new(body.into()).into(); - Self::lock_bodies().insert(this as *const _ as _, body); - this - } - - /// # Panics - /// - /// `self`に対してこの関数を二度呼ぶとパニックする。 - fn drop_body(&self) { - let body = Self::lock_bodies() - .remove(&(self as *const _ as _)) - .unwrap_or_else(|| self.panic_for_deleted()); - - drop( - body.try_write_arc() - .unwrap_or_else(|| { - warn!("{} is still in use. Waiting before closing", self.display()); - body.write_arc() - }) - .take() - .unwrap_or_else(|| self.panic_for_deleted()), - ); - } - - /// # Panics - /// - /// `self`に対して`drop_body`を呼んでいるとパニックする。 - fn body(&self) -> impl Deref { - let body = Self::lock_bodies() - .get(&(self as *const _ as _)) - .unwrap_or_else(|| self.panic_for_deleted()) - .read_arc(); - voicevox_core::__internal::interop::raii::try_map_guard(body, |body| { - body.as_ref().ok_or(()) - }) - .unwrap_or_else(|()| self.panic_for_deleted()) - } -} - -#[ext] -impl T { - fn lock_bodies( - ) -> impl DerefMut>>>> - { - Self::bodies().lock().unwrap_or_else(|e| panic!("{e}")) - } - - fn panic_for_deleted(&self) -> ! { - let display = self.display(); - panic!("{display}は既に破棄されています"); - } - - fn display(&self) -> impl Display + '_ { - let type_name = any::type_name::() - .split("::") - .last() - .expect("should not empty"); - format!("`{type_name}` ({self:018p})") - } -} - #[duplicate_item( H B; [ OpenJtalkRc ] [ voicevox_core::blocking::OpenJtalk ]; @@ -240,21 +145,19 @@ impl T { impl CApiObject for H { type RustApiObject = B; - fn heads() -> &'static boxcar::Vec { - static HEADS: boxcar::Vec = boxcar::Vec::new(); + fn heads() -> &'static std::sync::Mutex> { + static HEADS: std::sync::Mutex> = std::sync::Mutex::new(vec![]); &HEADS } fn bodies() -> &'static std::sync::Mutex< HashMap>>>, > { - #[expect( - clippy::type_complexity, - reason = "`CApiObject::bodies`の方でコメント書いてる" - )] + #[expect(clippy::type_complexity, reason = "`CApiObject::bodies`と同様")] static BODIES: LazyLock< std::sync::Mutex>>>>, > = LazyLock::new(Default::default); + &BODIES } } diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index 6879b89c5..f830e7a72 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -10,17 +10,19 @@ mod c_impls; mod compatible_engine; mod drop_check; mod helpers; +mod object; mod result_code; mod slice_owner; -use self::c_impls::CApiObject; use self::drop_check::C_STRING_DROP_CHECKER; use self::helpers::{ accent_phrases_to_json, audio_query_model_to_json, ensure_utf8, into_result_code_with_error, CApiError, UuidBytesExt as _, }; +use self::object::{CApiObject as _, CApiObjectPtrExt as _}; use self::result_code::VoicevoxResultCode; use self::slice_owner::U8_SLICE_OWNER; use anstream::{AutoStream, RawStream}; +use c_impls::{VoicevoxSynthesizerPtrExt as _, VoicevoxVoiceModelFilePtrExt as _}; use chrono::SecondsFormat; use colorchoice::ColorChoice; use educe::Educe; @@ -297,7 +299,7 @@ pub unsafe extern "C" fn voicevox_open_jtalk_rc_new( init_logger_once(); into_result_code_with_error((|| { let open_jtalk_dic_dir = ensure_utf8(CStr::from_ptr(open_jtalk_dic_dir))?; - let open_jtalk = OpenJtalkRc::new(open_jtalk_dic_dir)?.into(); + let open_jtalk = OpenJtalkRc::new(open_jtalk_dic_dir)?; out_open_jtalk.write_unaligned(open_jtalk); Ok(()) })()) @@ -309,15 +311,10 @@ pub unsafe extern "C" fn voicevox_open_jtalk_rc_new( /// /// @param [in] open_jtalk Open JTalkのオブジェクト /// @param [in] user_dict ユーザー辞書 -/// -/// \safety{ -/// - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならない。 -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 -/// } #[no_mangle] pub extern "C" fn voicevox_open_jtalk_rc_use_user_dict( - open_jtalk: &OpenJtalkRc, - user_dict: &VoicevoxUserDict, + open_jtalk: *const OpenJtalkRc, + user_dict: *const VoicevoxUserDict, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { @@ -339,14 +336,10 @@ pub extern "C" fn voicevox_open_jtalk_rc_use_user_dict( /// voicevox_open_jtalk_rc_delete(open_jtalk); /// ``` /// } -/// -/// \safety{ -/// - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならない。 -/// } #[no_mangle] -pub unsafe extern "C" fn voicevox_open_jtalk_rc_delete(open_jtalk: NonNull) { +pub extern "C" fn voicevox_open_jtalk_rc_delete(open_jtalk: *mut OpenJtalkRc) { init_logger_once(); - unsafe { open_jtalk.as_ref() }.drop_body(); // SAFETY: ユーザーに要求しているもので十分 + open_jtalk.drop_body(); } /// ハードウェアアクセラレーションモードを設定する設定値。 @@ -436,7 +429,7 @@ pub unsafe extern "C" fn voicevox_voice_model_file_open( init_logger_once(); into_result_code_with_error((|| { let path = ensure_utf8(CStr::from_ptr(path))?; - let model = VoicevoxVoiceModelFile::open(path)?.into(); + let model = VoicevoxVoiceModelFile::open(path)?; out_model.write_unaligned(model); Ok(()) })()) @@ -448,12 +441,11 @@ pub unsafe extern "C" fn voicevox_voice_model_file_open( /// @param [out] output_voice_model_id 音声モデルID /// /// \safety{ -/// - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 /// - `output_voice_model_id`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_voice_model_file_id( - model: &VoicevoxVoiceModelFile, + model: *const VoicevoxVoiceModelFile, output_voice_model_id: NonNull<[u8; 16]>, ) { init_logger_once(); @@ -468,13 +460,9 @@ pub unsafe extern "C" fn voicevox_voice_model_file_id( /// @param [in] model 音声モデル /// /// @returns メタ情報のJSON文字列 -/// -/// \safety{ -/// - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 -/// } #[no_mangle] pub extern "C" fn voicevox_voice_model_file_create_metas_json( - model: &VoicevoxVoiceModelFile, + model: *const VoicevoxVoiceModelFile, ) -> *mut c_char { init_logger_once(); C_STRING_DROP_CHECKER.whitelist(model.metas()).into_raw() @@ -487,14 +475,10 @@ pub extern "C" fn voicevox_voice_model_file_create_metas_json( /// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 /// /// @param [in] model 破棄対象 -/// -/// \safety{ -/// - `model`は ::voicevox_voice_model_file_open で得たものでなければならない。 -/// } #[no_mangle] -pub unsafe extern "C" fn voicevox_voice_model_file_close(model: NonNull) { +pub extern "C" fn voicevox_voice_model_file_close(model: *mut VoicevoxVoiceModelFile) { init_logger_once(); - unsafe { model.as_ref() }.drop_body(); // SAFETY: ユーザーに要求しているもので十分 + model.drop_body(); } /// 音声シンセサイザ。 @@ -517,13 +501,12 @@ pub struct VoicevoxSynthesizer { /// /// \safety{ /// - `onnxruntime`は ::voicevox_onnxruntime_load_once または ::voicevox_onnxruntime_init_once で得たものでなければならない。 -/// - `open_jtalk`は ::voicevox_voice_model_file_open で得たものでなければならない。 /// - `out_synthesizer`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_new( onnxruntime: &'static VoicevoxOnnxruntime, - open_jtalk: &OpenJtalkRc, + open_jtalk: *const OpenJtalkRc, options: VoicevoxInitializeOptions, out_synthesizer: NonNull>, ) -> VoicevoxResultCode { @@ -531,7 +514,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_new( into_result_code_with_error((|| { let options = options.into(); - let synthesizer = VoicevoxSynthesizer::new(onnxruntime, open_jtalk, &options)?.into(); + let synthesizer = VoicevoxSynthesizer::new(onnxruntime, open_jtalk, &options)?; out_synthesizer.write_unaligned(synthesizer); Ok(()) })()) @@ -544,14 +527,10 @@ pub unsafe extern "C" fn voicevox_synthesizer_new( /// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 /// /// @param [in] synthesizer 破棄対象 -/// -/// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 -/// } #[no_mangle] -pub unsafe extern "C" fn voicevox_synthesizer_delete(synthesizer: NonNull) { +pub extern "C" fn voicevox_synthesizer_delete(synthesizer: *mut VoicevoxSynthesizer) { init_logger_once(); - unsafe { synthesizer.as_ref() }.drop_body(); // SAFETY: ユーザーに要求しているもので十分 + synthesizer.drop_body(); } /// 音声モデルを読み込む。 @@ -560,15 +539,10 @@ pub unsafe extern "C" fn voicevox_synthesizer_delete(synthesizer: NonNull VoicevoxResultCode { init_logger_once(); into_result_code_with_error(synthesizer.load_voice_model(&model.body())) @@ -582,12 +556,11 @@ pub extern "C" fn voicevox_synthesizer_load_voice_model( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `model_id`は読み込みについて有効でなければならない。 /// } #[no_mangle] pub extern "C" fn voicevox_synthesizer_unload_voice_model( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, model_id: VoicevoxVoiceModelId<'_>, ) -> VoicevoxResultCode { init_logger_once(); @@ -600,13 +573,9 @@ pub extern "C" fn voicevox_synthesizer_unload_voice_model( /// @param [in] synthesizer 音声シンセサイザ /// /// @returns ::VoicevoxOnnxruntime のインスタンス -/// -/// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 -/// } #[no_mangle] pub extern "C" fn voicevox_synthesizer_get_onnxruntime( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, ) -> &'static VoicevoxOnnxruntime { synthesizer.onnxruntime() } @@ -616,12 +585,10 @@ pub extern "C" fn voicevox_synthesizer_get_onnxruntime( /// @param [in] synthesizer 音声シンセサイザ /// /// @returns GPUモードかどうか -/// -/// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 -/// } #[no_mangle] -pub extern "C" fn voicevox_synthesizer_is_gpu_mode(synthesizer: &VoicevoxSynthesizer) -> bool { +pub extern "C" fn voicevox_synthesizer_is_gpu_mode( + synthesizer: *const VoicevoxSynthesizer, +) -> bool { init_logger_once(); synthesizer.body().is_gpu_mode() } @@ -634,12 +601,11 @@ pub extern "C" fn voicevox_synthesizer_is_gpu_mode(synthesizer: &VoicevoxSynthes /// @returns モデルが読み込まれているかどうか /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `model_id`は読み込みについて有効でなければならない。 /// } #[no_mangle] pub extern "C" fn voicevox_synthesizer_is_loaded_voice_model( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, model_id: VoicevoxVoiceModelId<'_>, ) -> bool { init_logger_once(); @@ -654,13 +620,9 @@ pub extern "C" fn voicevox_synthesizer_is_loaded_voice_model( /// @param [in] synthesizer 音声シンセサイザ /// /// @return メタ情報のJSON文字列 -/// -/// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 -/// } #[no_mangle] pub extern "C" fn voicevox_synthesizer_create_metas_json( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, ) -> *mut c_char { init_logger_once(); let metas = synthesizer.metas(); @@ -728,13 +690,12 @@ pub unsafe extern "C" fn voicevox_onnxruntime_create_supported_devices_json( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query_from_kana( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, kana: *const c_char, style_id: VoicevoxStyleId, output_audio_query_json: NonNull<*mut c_char>, @@ -776,13 +737,12 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query_from_kana( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, text: *const c_char, style_id: VoicevoxStyleId, output_audio_query_json: NonNull<*mut c_char>, @@ -825,13 +785,12 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases_from_kana( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, kana: *const c_char, style_id: VoicevoxStyleId, output_accent_phrases_json: NonNull<*mut c_char>, @@ -871,13 +830,12 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases_from_kana( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, text: *const c_char, style_id: VoicevoxStyleId, output_accent_phrases_json: NonNull<*mut c_char>, @@ -908,13 +866,12 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_data( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, accent_phrases_json: *const c_char, style_id: VoicevoxStyleId, output_accent_phrases_json: NonNull<*mut c_char>, @@ -947,13 +904,12 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_data( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_replace_phoneme_length( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, accent_phrases_json: *const c_char, style_id: VoicevoxStyleId, output_accent_phrases_json: NonNull<*mut c_char>, @@ -986,13 +942,12 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_phoneme_length( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_pitch( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, accent_phrases_json: *const c_char, style_id: VoicevoxStyleId, output_accent_phrases_json: NonNull<*mut c_char>, @@ -1042,14 +997,13 @@ pub extern "C" fn voicevox_make_default_synthesis_options() -> VoicevoxSynthesis /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `audio_query_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_wav_length`は書き込みについて有効でなければならない。 /// - `output_wav`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_synthesis( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, audio_query_json: *const c_char, style_id: VoicevoxStyleId, options: VoicevoxSynthesisOptions, @@ -1102,14 +1056,13 @@ pub extern "C" fn voicevox_make_default_tts_options() -> VoicevoxTtsOptions { /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_wav_length`は書き込みについて有効でなければならない。 /// - `output_wav`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_tts_from_kana( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, kana: *const c_char, style_id: VoicevoxStyleId, options: VoicevoxTtsOptions, @@ -1143,14 +1096,13 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts_from_kana( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならない。 /// - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_wav_length`は書き込みについて有効でなければならない。 /// - `output_wav`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_tts( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, text: *const c_char, style_id: VoicevoxStyleId, options: VoicevoxTtsOptions, @@ -1309,7 +1261,7 @@ pub extern "C" fn voicevox_user_dict_word_make( #[no_mangle] pub extern "C" fn voicevox_user_dict_new() -> NonNull { init_logger_once(); - VoicevoxUserDict::new(Default::default()).into() + VoicevoxUserDict::new(Default::default()) } /// ユーザー辞書にファイルを読み込ませる。 @@ -1319,12 +1271,11 @@ pub extern "C" fn voicevox_user_dict_new() -> NonNull { /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `dict_path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_user_dict_load( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, dict_path: *const c_char, ) -> VoicevoxResultCode { init_logger_once(); @@ -1347,13 +1298,12 @@ pub unsafe extern "C" fn voicevox_user_dict_load( /// @param user_dict は有効な :VoicevoxUserDict のポインタであること /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_word_uuid`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_user_dict_add_word( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, word: *const VoicevoxUserDictWord, output_word_uuid: NonNull<[u8; 16]>, ) -> VoicevoxResultCode { @@ -1375,13 +1325,12 @@ pub unsafe extern "C" fn voicevox_user_dict_add_word( /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `word_uuid`は読み込みについて有効でなければならない。 /// - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_user_dict_update_word( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, word_uuid: &[u8; 16], word: *const VoicevoxUserDictWord, ) -> VoicevoxResultCode { @@ -1402,12 +1351,11 @@ pub unsafe extern "C" fn voicevox_user_dict_update_word( /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `word_uuid`は読み込みについて有効でなければならない。 /// } #[no_mangle] pub extern "C" fn voicevox_user_dict_remove_word( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, word_uuid: &[u8; 16], ) -> VoicevoxResultCode { init_logger_once(); @@ -1428,12 +1376,11 @@ pub extern "C" fn voicevox_user_dict_remove_word( /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `output_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_user_dict_to_json( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, output_json: NonNull<*mut c_char>, ) -> VoicevoxResultCode { init_logger_once(); @@ -1448,14 +1395,10 @@ pub unsafe extern "C" fn voicevox_user_dict_to_json( /// @param [in] user_dict ユーザー辞書 /// @param [in] other_dict インポートするユーザー辞書 /// @returns 結果コード -/// -/// \safety{ -/// - `user_dict`と`other_dict`は ::voicevox_user_dict_new で得たものでなければならない。 -/// } #[no_mangle] pub extern "C" fn voicevox_user_dict_import( - user_dict: &VoicevoxUserDict, - other_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, + other_dict: *const VoicevoxUserDict, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { @@ -1470,12 +1413,11 @@ pub extern "C" fn voicevox_user_dict_import( /// @param [in] path 保存先のファイルパス /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 /// - `path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_user_dict_save( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, path: *const c_char, ) -> VoicevoxResultCode { init_logger_once(); @@ -1493,12 +1435,8 @@ pub unsafe extern "C" fn voicevox_user_dict_save( /// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 /// /// @param [in] user_dict 破棄対象 -/// -/// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならない。 -/// } #[no_mangle] -pub unsafe extern "C" fn voicevox_user_dict_delete(user_dict: NonNull) { +pub extern "C" fn voicevox_user_dict_delete(user_dict: *mut VoicevoxUserDict) { init_logger_once(); - unsafe { user_dict.as_ref() }.drop_body(); // SAFETY: ユーザーに要求しているもので十分 + user_dict.drop_body(); } diff --git a/crates/voicevox_core_c_api/src/object.rs b/crates/voicevox_core_c_api/src/object.rs new file mode 100644 index 000000000..a3aea19f1 --- /dev/null +++ b/crates/voicevox_core_c_api/src/object.rs @@ -0,0 +1,145 @@ +use std::{ + any, + collections::HashMap, + fmt::{Debug, Display}, + mem, + ops::{Deref, DerefMut}, + ptr::NonNull, + sync::Arc, +}; + +use easy_ext::ext; +use tracing::warn; + +/// プロセスの終わりまでデストラクトされない、ユーザーにオブジェクトとして貸し出す1-bit長の構造体。 +/// +/// インスタンスは次のような形。 +/// +/// ``` +/// pub struct VoicevoxSynthesizer { +/// _padding: MaybeUninit<[u8; 1]>, +/// } +/// ``` +/// +/// `RustApiObject`そのものではなくこのトレイトのインスタンスをユーザーに渡すようにすることで、次のことを実現する。 +/// +/// 1. "delete"時に対象オブジェクトに対するアクセスがあった場合、アクセスが終わるまで待つ +/// 2. 次のユーザー操作に対するセーフティネットを張り、パニックするようにする +/// 1. "delete"後に他の通常のメソッド関数の利用を試みる +/// 2. "delete"後に"delete"を試みる +/// 3. そもそもオブジェクトとして変なダングリングポインタが渡される +pub(crate) trait CApiObject: Default + Debug + 'static { + type RustApiObject: 'static; + + // 書き込み操作としては`push`のみ + fn heads() -> &'static std::sync::Mutex>; + + #[expect( + clippy::type_complexity, + reason = "型を分離するとかえって可読性を失う。その代わりコメントを入れている" + )] + fn bodies() -> &'static std::sync::Mutex< + HashMap< + usize, // `heads`の要素へのポインタのアドレス + Arc< + parking_lot::RwLock< + Option, // `RwLock`をdropする直前まで`Some` + >, + >, + >, + >; + + fn new(body: Self::RustApiObject) -> NonNull { + assert!(mem::size_of::() > 0); + + let this = { + let mut heads = Self::lock_heads(); + heads.push(Default::default()); + NonNull::from(heads.last().expect("just pushed")) + }; + let body = parking_lot::RwLock::new(body.into()).into(); + Self::lock_bodies().insert(this.as_ptr() as _, body); + this + } +} + +#[ext(CApiObjectPtrExt)] +impl *const T { + /// # Panics + /// + /// 同じ対象に対して`drop_body`を呼んでいるとパニックする。 + pub(crate) fn body(self) -> impl Deref { + self.validate(); + + let body = T::lock_bodies() + .get(&(self as _)) + .unwrap_or_else(|| self.panic_for_deleted()) + .read_arc(); + + voicevox_core::__internal::interop::raii::try_map_guard(body, |body| { + body.as_ref().ok_or(()) + }) + .unwrap_or_else(|()| self.panic_for_deleted()) + } + + /// # Panics + /// + /// 同じ対象に対してこの関数を二度呼ぶとパニックする。 + pub(crate) fn drop_body(self) { + self.validate(); + + let body = T::lock_bodies() + .remove(&(self as _)) + .unwrap_or_else(|| self.panic_for_deleted()); + + drop( + body.try_write_arc() + .unwrap_or_else(|| { + warn!( + "{this} is still in use. Waiting before closing", + this = self.display(), + ); + body.write_arc() + }) + .take() + .unwrap_or_else(|| self.panic_for_deleted()), + ); + } +} + +#[ext] +impl *const T { + fn validate(self) { + if self.is_null() { + panic!("the argument must not be null"); + } + if !T::lock_heads().as_ptr_range().contains(&self) { + panic!("{self:018p} does not seem to be valid object"); + } + } + + fn display(self) -> impl Display { + let type_name = any::type_name::() + .split("::") + .last() + .expect("should not empty"); + format!("`{type_name}` ({self:018p})") + } + + fn panic_for_deleted(self) -> ! { + panic!("{}は既に破棄されています", self.display()); + } +} + +#[ext] +impl T { + fn lock_heads() -> impl DerefMut> { + Self::heads().lock().unwrap_or_else(|e| panic!("{e}")) + } + + fn lock_bodies( + ) -> impl DerefMut>>>> + { + Self::bodies().lock().unwrap_or_else(|e| panic!("{e}")) + } +} From f69e793640db7b7669e66639c36a0482fe5ac3f9 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Wed, 9 Oct 2024 01:22:11 +0900 Subject: [PATCH 8/8] =?UTF-8?q?`sed=20-i=20s/=E3=82=A2=E3=83=9C=E3=83=BC?= =?UTF-8?q?=E3=83=88/=E7=95=B0=E5=B8=B8=E7=B5=82=E4=BA=86/=20./crates/voic?= =?UTF-8?q?evox=5Fcore=5Fc=5Fapi/src/lib.rs`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/VOICEVOX/voicevox_core/pull/849#discussion_r1792131640 --- crates/voicevox_core_c_api/include/voicevox_core.h | 8 ++++---- crates/voicevox_core_c_api/src/lib.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/voicevox_core_c_api/include/voicevox_core.h b/crates/voicevox_core_c_api/include/voicevox_core.h index 43a1a3b62..9fc461000 100644 --- a/crates/voicevox_core_c_api/include/voicevox_core.h +++ b/crates/voicevox_core_c_api/include/voicevox_core.h @@ -553,7 +553,7 @@ VoicevoxResultCode voicevox_open_jtalk_rc_use_user_dict(const struct OpenJtalkRc * * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 * - * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 * * @param [in] open_jtalk 破棄対象 * @@ -640,7 +640,7 @@ char *voicevox_voice_model_file_create_metas_json(const struct VoicevoxVoiceMode * * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 * - * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 * * @param [in] model 破棄対象 */ @@ -677,7 +677,7 @@ VoicevoxResultCode voicevox_synthesizer_new(const struct VoicevoxOnnxruntime *on * * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 * - * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 * * @param [in] synthesizer 破棄対象 */ @@ -1348,7 +1348,7 @@ VoicevoxResultCode voicevox_user_dict_save(const struct VoicevoxUserDict *user_d * * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 * - * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 * * @param [in] user_dict 破棄対象 */ diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index f830e7a72..76a23a442 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -327,7 +327,7 @@ pub extern "C" fn voicevox_open_jtalk_rc_use_user_dict( /// /// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 /// -/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 /// /// @param [in] open_jtalk 破棄対象 /// @@ -472,7 +472,7 @@ pub extern "C" fn voicevox_voice_model_file_create_metas_json( /// /// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 /// -/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 /// /// @param [in] model 破棄対象 #[no_mangle] @@ -524,7 +524,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_new( /// /// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 /// -/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 /// /// @param [in] synthesizer 破棄対象 #[no_mangle] @@ -1432,7 +1432,7 @@ pub unsafe extern "C" fn voicevox_user_dict_save( /// /// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 /// -/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスをアボートする。 +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 /// /// @param [in] user_dict 破棄対象 #[no_mangle]