use std::os::raw::c_char;
use std::os::raw::c_int;
use std::ffi::{CStr, CString};
use std::ptr;
use std::collections::HashMap;
#[cfg(feature = "edm_roguelite")]
use crate::edm::core::EmotionDataModel;
#[cfg(feature = "edm_roguelite")]
use crate::edm::roguelite::core::RogueliteEdm;
#[cfg(feature = "edm_roguelite")]
use crate::edm::core::EmotionState;
#[cfg(feature = "director_roguelite")]
use crate::director::core::InteractionStrategy;
#[cfg(feature = "director_roguelite")]
use crate::director::roguelite::core::RogueliteDirector;
#[cfg(feature = "director_roguelite")]
use crate::director::core::{InteractionState, InteractionParams, EmotionStats};
#[repr(C)]
pub struct AipEmotionResult {
pub valence: f32,
pub arousal: f32,
pub dominance: f32,
pub error_code: c_int,
}
#[repr(C)]
pub struct AipDecisionResult {
pub intensity_factor: f32,
pub feedback_intensity: f32,
pub pace_speed: f32,
pub reward_scarcity: f32,
pub env_arousal: f32,
pub rhythm_modulation: f32,
pub challenge_curve: f32,
pub error_code: c_int,
}
#[repr(C)]
pub struct AipFeatureArray {
pub data: *const f32,
pub len: usize,
}
#[repr(C)]
pub struct AipUserTraits {
pub data: *const f32,
pub len: usize,
}
#[repr(C)]
pub struct AipEnvState {
pub data: *const f32,
pub len: usize,
}
#[repr(C)]
pub struct AipEmotionInput {
pub valence: f32,
pub arousal: f32,
pub dominance: f32,
}
pub const AIP_SUCCESS: c_int = 0;
pub const AIP_ERROR_NULL_POINTER: c_int = -1;
pub const AIP_ERROR_INVALID_DATA: c_int = -2;
pub const AIP_ERROR_MODEL_NOT_LOADED: c_int = -3;
pub const AIP_ERROR_INFERENCE_FAILED: c_int = -4;
pub const AIP_ERROR_FILE_NOT_FOUND: c_int = -5;
pub const AIP_ERROR_LOAD_FAILED: c_int = -6;
#[cfg(feature = "edm_roguelite")]
pub struct AipEdmModel {
inner: RogueliteEdm,
}
#[cfg(feature = "director_roguelite")]
pub struct AipDirectorModel {
inner: RogueliteDirector,
}
#[cfg(feature = "edm_roguelite")]
#[no_mangle]
pub extern "C" fn aip_edm_create() -> *mut AipEdmModel {
let device = candle_core::Device::Cpu;
match RogueliteEdm::new(device) {
Ok(model) => {
let boxed = Box::new(AipEdmModel { inner: model });
Box::into_raw(boxed)
}
Err(_) => ptr::null_mut(),
}
}
#[cfg(feature = "edm_roguelite")]
#[no_mangle]
pub extern "C" fn aip_edm_destroy(model: *mut AipEdmModel) {
if !model.is_null() {
unsafe {
drop(Box::from_raw(model));
}
}
}
#[cfg(feature = "edm_roguelite")]
#[no_mangle]
pub extern "C" fn aip_edm_load(model: *mut AipEdmModel, path: *const c_char) -> c_int {
if model.is_null() || path.is_null() {
return AIP_ERROR_NULL_POINTER;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return AIP_ERROR_INVALID_DATA,
};
let model = unsafe { &mut *model };
let path = std::path::Path::new(path_str);
if !path.exists() {
return AIP_ERROR_FILE_NOT_FOUND;
}
match EmotionDataModel::load(&mut model.inner, path) {
Ok(_) => AIP_SUCCESS,
Err(_) => AIP_ERROR_LOAD_FAILED,
}
}
#[cfg(feature = "edm_roguelite")]
#[no_mangle]
pub extern "C" fn aip_edm_infer(
model: *const AipEdmModel,
features: AipFeatureArray,
) -> AipEmotionResult {
if model.is_null() || features.data.is_null() {
return AipEmotionResult {
valence: 0.0,
arousal: 0.0,
dominance: 0.0,
error_code: AIP_ERROR_NULL_POINTER,
};
}
if features.len != 15 {
return AipEmotionResult {
valence: 0.0,
arousal: 0.0,
dominance: 0.0,
error_code: AIP_ERROR_INVALID_DATA,
};
}
let model = unsafe { &*model };
let feature_slice = unsafe { std::slice::from_raw_parts(features.data, features.len) };
let mut feature_map = HashMap::new();
for (i, &val) in feature_slice.iter().enumerate() {
feature_map.insert(i as u32, val);
}
match model.inner.infer(&feature_map) {
Ok(emotion) => AipEmotionResult {
valence: emotion.valence,
arousal: emotion.arousal,
dominance: emotion.dominance,
error_code: AIP_SUCCESS,
},
Err(_) => AipEmotionResult {
valence: 0.0,
arousal: 0.0,
dominance: 0.0,
error_code: AIP_ERROR_INFERENCE_FAILED,
},
}
}
#[cfg(feature = "director_roguelite")]
#[no_mangle]
pub extern "C" fn aip_director_create() -> *mut AipDirectorModel {
let device = candle_core::Device::Cpu;
match RogueliteDirector::new(device) {
Ok(model) => {
let boxed = Box::new(AipDirectorModel { inner: model });
Box::into_raw(boxed)
}
Err(_) => ptr::null_mut(),
}
}
#[cfg(feature = "director_roguelite")]
#[no_mangle]
pub extern "C" fn aip_director_destroy(model: *mut AipDirectorModel) {
if !model.is_null() {
unsafe {
drop(Box::from_raw(model));
}
}
}
#[cfg(feature = "director_roguelite")]
#[no_mangle]
pub extern "C" fn aip_director_load(model: *mut AipDirectorModel, path: *const c_char) -> c_int {
if model.is_null() || path.is_null() {
return AIP_ERROR_NULL_POINTER;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return AIP_ERROR_INVALID_DATA,
};
let model = unsafe { &mut *model };
let path = std::path::Path::new(path_str);
if !path.exists() {
return AIP_ERROR_FILE_NOT_FOUND;
}
match InteractionStrategy::load(&mut model.inner, path) {
Ok(_) => AIP_SUCCESS,
Err(_) => AIP_ERROR_LOAD_FAILED,
}
}
#[cfg(feature = "director_roguelite")]
#[no_mangle]
pub extern "C" fn aip_director_decide(
model: *const AipDirectorModel,
user_traits: AipUserTraits,
env_state: AipEnvState,
emotion: AipEmotionInput,
) -> AipDecisionResult {
if model.is_null() {
return AipDecisionResult {
intensity_factor: 1.0,
feedback_intensity: 0.5,
pace_speed: 1.0,
reward_scarcity: 0.5,
env_arousal: 0.5,
rhythm_modulation: 1.0,
challenge_curve: 0.0,
error_code: AIP_ERROR_NULL_POINTER,
};
}
if user_traits.len != 8 || env_state.len != 6 {
return AipDecisionResult {
intensity_factor: 1.0,
feedback_intensity: 0.5,
pace_speed: 1.0,
reward_scarcity: 0.5,
env_arousal: 0.5,
rhythm_modulation: 1.0,
challenge_curve: 0.0,
error_code: AIP_ERROR_INVALID_DATA,
};
}
let model = unsafe { &*model };
let mut user_traits_map = HashMap::new();
if !user_traits.data.is_null() {
let slice = unsafe { std::slice::from_raw_parts(user_traits.data, user_traits.len) };
for (i, &val) in slice.iter().enumerate() {
user_traits_map.insert(i as u32, val);
}
}
let mut env_state_map = HashMap::new();
if !env_state.data.is_null() {
let slice = unsafe { std::slice::from_raw_parts(env_state.data, env_state.len) };
for (i, &val) in slice.iter().enumerate() {
env_state_map.insert(i as u32, val);
}
}
let state = InteractionState {
user_traits: user_traits_map,
env_state: env_state_map,
emotion: EmotionState::new(emotion.valence, emotion.arousal, emotion.dominance),
emotion_stats: EmotionStats::default(),
};
match model.inner.decide(&state) {
Ok(params) => AipDecisionResult {
intensity_factor: params.intensity_factor,
feedback_intensity: params.feedback_intensity,
pace_speed: params.pace_speed,
reward_scarcity: params.reward_scarcity,
env_arousal: params.env_arousal,
rhythm_modulation: params.rhythm_modulation,
challenge_curve: params.challenge_curve,
error_code: AIP_SUCCESS,
},
Err(_) => AipDecisionResult {
intensity_factor: 1.0,
feedback_intensity: 0.5,
pace_speed: 1.0,
reward_scarcity: 0.5,
env_arousal: 0.5,
rhythm_modulation: 1.0,
challenge_curve: 0.0,
error_code: AIP_ERROR_INFERENCE_FAILED,
},
}
}
#[no_mangle]
pub extern "C" fn aip_get_version() -> *mut c_char {
let version = CString::new(env!("CARGO_PKG_VERSION")).unwrap();
version.into_raw()
}
#[no_mangle]
pub extern "C" fn aip_free_string(s: *mut c_char) {
if !s.is_null() {
unsafe {
drop(CString::from_raw(s));
}
}
}
#[no_mangle]
pub extern "C" fn aip_error_message(code: c_int) -> *mut c_char {
let msg = match code {
AIP_SUCCESS => "Success",
AIP_ERROR_NULL_POINTER => "Null pointer",
AIP_ERROR_INVALID_DATA => "Invalid data",
AIP_ERROR_MODEL_NOT_LOADED => "Model not loaded",
AIP_ERROR_INFERENCE_FAILED => "Inference failed",
AIP_ERROR_FILE_NOT_FOUND => "File not found",
AIP_ERROR_LOAD_FAILED => "Load failed",
_ => "Unknown error",
};
let c_msg = CString::new(msg).unwrap();
c_msg.into_raw()
}