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..e4f8ea475 100644 --- a/crates/voicevox_core_c_api/src/c_impls.rs +++ b/crates/voicevox_core_c_api/src/c_impls.rs @@ -1,10 +1,6 @@ use std::{ - any, collections::HashMap, ffi::CString, - fmt::{Debug, Display}, - mem, - ops::{Deref, DerefMut}, path::Path, sync::{Arc, LazyLock}, }; @@ -13,11 +9,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, }; @@ -82,35 +79,38 @@ impl OpenJtalkRc { impl VoicevoxSynthesizer { pub(crate) fn new( onnxruntime: &'static VoicevoxOnnxruntime, - open_jtalk: &OpenJtalkRc, + open_jtalk: *const OpenJtalkRc, options: &InitializeOptions, ) -> Result<&'static Self> { 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()) } } @@ -120,8 +120,11 @@ impl VoicevoxVoiceModelFile { 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 +134,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 ]; diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index 6879b89c5..34415d01c 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; @@ -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(); } /// ハードウェアアクセラレーションモードを設定する設定値。 @@ -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 { @@ -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, @@ -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..a2eddd3aa --- /dev/null +++ b/crates/voicevox_core_c_api/src/object.rs @@ -0,0 +1,133 @@ +use std::{ + any, + collections::HashMap, + fmt::{Debug, Display}, + mem, + ops::{Deref, DerefMut}, + 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; + + 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 + } +} + +#[ext(CApiObjectPtrExt)] +impl *const T { + /// # 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()), + ); + } + + /// # Panics + /// + /// 同じ対象に対して`drop_body`を呼んでいるとパニックする。 + pub(crate) fn body(self) -> impl Deref { + self.validate(); + + let body = T::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 *const T { + fn validate(self) { + if self.is_null() { + panic!("the argument must not be null"); + } + if T::heads().iter().all(|(_, e)| (e as *const _) != 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_bodies( + ) -> impl DerefMut>>>> + { + Self::bodies().lock().unwrap_or_else(|e| panic!("{e}")) + } +}