mirror of
https://github.com/lune-org/lune.git
synced 2025-04-10 21:40:54 +01:00
160 lines
5.4 KiB
Rust
160 lines
5.4 KiB
Rust
#![allow(clippy::cargo_common_metadata)]
|
|
|
|
use std::vec::Vec;
|
|
|
|
use libffi::{
|
|
low,
|
|
middle::{Cif, Type},
|
|
raw,
|
|
};
|
|
use mlua::prelude::*;
|
|
|
|
use crate::ctype::{libffi_types_from_table, type_userdata_stringify, CType};
|
|
use crate::FFI_STATUS_NAMES;
|
|
use crate::{
|
|
association::{get_association, set_association},
|
|
ctype::type_name_from_userdata,
|
|
};
|
|
use crate::{carr::CArr, cptr::CPtr};
|
|
|
|
pub struct CStruct {
|
|
libffi_cif: Cif,
|
|
libffi_type: Type,
|
|
fields: Vec<Type>,
|
|
offsets: Vec<usize>,
|
|
size: usize,
|
|
}
|
|
|
|
const CSTRUCT_INNER: &str = "__cstruct_inner";
|
|
|
|
impl CStruct {
|
|
pub fn new(fields: Vec<Type>) -> LuaResult<Self> {
|
|
let libffi_type = Type::structure(fields.clone());
|
|
let libffi_cfi = Cif::new(vec![libffi_type.clone()], Type::void());
|
|
|
|
// Get field offsets with ffi_get_struct_offsets
|
|
let mut offsets = Vec::<usize>::with_capacity(fields.len());
|
|
unsafe {
|
|
let offset_result: raw::ffi_status = raw::ffi_get_struct_offsets(
|
|
low::ffi_abi_FFI_DEFAULT_ABI,
|
|
libffi_type.as_raw_ptr(),
|
|
offsets.as_mut_ptr(),
|
|
);
|
|
if offset_result != raw::ffi_status_FFI_OK {
|
|
return Err(LuaError::external(format!(
|
|
"ffi_get_struct_offsets failed. expected result {}, got {}",
|
|
FFI_STATUS_NAMES[0], FFI_STATUS_NAMES[offset_result as usize]
|
|
)));
|
|
}
|
|
offsets.set_len(offsets.capacity());
|
|
}
|
|
|
|
// Get tailing padded size of struct
|
|
// See http://www.chiark.greenend.org.uk/doc/libffi-dev/html/Size-and-Alignment.html
|
|
let size = unsafe { (*libffi_type.as_raw_ptr()).size };
|
|
|
|
Ok(Self {
|
|
libffi_cif: libffi_cfi,
|
|
libffi_type,
|
|
fields,
|
|
offsets,
|
|
size,
|
|
})
|
|
}
|
|
|
|
// Create new CStruct UserData with LuaTable.
|
|
// Lock and hold table for .inner ref
|
|
pub fn from_lua_table<'lua>(
|
|
lua: &'lua Lua,
|
|
table: LuaTable<'lua>,
|
|
) -> LuaResult<LuaAnyUserData<'lua>> {
|
|
let fields = libffi_types_from_table(&table)?;
|
|
let cstruct = lua.create_userdata(Self::new(fields)?)?;
|
|
table.set_readonly(true);
|
|
set_association(lua, CSTRUCT_INNER, cstruct.clone(), table)?;
|
|
Ok(cstruct)
|
|
}
|
|
|
|
// Stringify cstruct for pretty printing something like:
|
|
// <CStruct( u8, i32, size = 8 )>
|
|
pub fn stringify(userdata: &LuaAnyUserData) -> LuaResult<String> {
|
|
let field: LuaValue = userdata.get("inner")?;
|
|
if field.is_table() {
|
|
let table = field
|
|
.as_table()
|
|
.ok_or(LuaError::external("failed to get inner type table."))?;
|
|
// iterate for field
|
|
let mut result = String::from(" ");
|
|
for i in 0..table.raw_len() {
|
|
let child: LuaAnyUserData = table.raw_get(i + 1)?;
|
|
if child.is::<CType>() {
|
|
result.push_str(format!("{}, ", type_userdata_stringify(&child)?).as_str());
|
|
} else {
|
|
result.push_str(
|
|
format!(
|
|
"<{}({})>, ",
|
|
type_name_from_userdata(&child),
|
|
type_userdata_stringify(&child)?
|
|
)
|
|
.as_str(),
|
|
);
|
|
}
|
|
}
|
|
|
|
// size of
|
|
result.push_str(format!("size = {} ", userdata.borrow::<CStruct>()?.size).as_str());
|
|
Ok(result)
|
|
} else {
|
|
Err(LuaError::external("failed to get inner type table."))
|
|
}
|
|
}
|
|
|
|
// Get byte offset of nth field
|
|
pub fn offset(&self, index: usize) -> LuaResult<usize> {
|
|
let offset = self
|
|
.offsets
|
|
.get(index)
|
|
.ok_or(LuaError::external("Out of index"))?
|
|
.to_owned();
|
|
Ok(offset)
|
|
}
|
|
|
|
pub fn get_type(&self) -> Type {
|
|
self.libffi_type.clone()
|
|
}
|
|
}
|
|
|
|
impl LuaUserData for CStruct {
|
|
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
|
fields.add_field_method_get("size", |_, this| Ok(this.size));
|
|
|
|
// Simply pass in the locked table used when first creating this object.
|
|
// By strongly referencing the table, the types inside do not disappear
|
|
// and the user can read the contents as needed. (good recycling!)
|
|
fields.add_field_function_get("inner", |lua, this: LuaAnyUserData| {
|
|
let table: LuaValue = get_association(lua, CSTRUCT_INNER, this)?
|
|
// It shouldn't happen.
|
|
.ok_or(LuaError::external("inner field not found"))?;
|
|
Ok(table)
|
|
});
|
|
}
|
|
|
|
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
|
methods.add_method("offset", |_, this, index: usize| {
|
|
let offset = this.offset(index)?;
|
|
Ok(offset)
|
|
});
|
|
methods.add_function("ptr", |lua, this: LuaAnyUserData| {
|
|
let pointer = CPtr::from_lua_userdata(lua, &this)?;
|
|
Ok(pointer)
|
|
});
|
|
methods.add_function("arr", |lua, (this, length): (LuaAnyUserData, usize)| {
|
|
let carr = CArr::from_lua_userdata(lua, &this, length)?;
|
|
Ok(carr)
|
|
});
|
|
methods.add_meta_function(LuaMetaMethod::ToString, |_, this: LuaAnyUserData| {
|
|
let result = CStruct::stringify(&this)?;
|
|
Ok(result)
|
|
});
|
|
}
|
|
}
|