noita_entangled_worlds/ewext/noita_api/src/lua.rs

247 lines
5.8 KiB
Rust
Raw Normal View History

pub mod lua_bindings;
use std::{
borrow::Cow,
cell::Cell,
ffi::{c_char, c_int, CStr},
2024-11-24 23:12:35 +03:00
mem, slice,
sync::LazyLock,
};
2024-11-24 23:12:35 +03:00
use eyre::{bail, Context, OptionExt};
use lua_bindings::{lua_CFunction, lua_State, Lua51, LUA_GLOBALSINDEX};
2024-11-21 17:08:03 +03:00
use crate::{Color, ComponentID, EntityID, Obj};
thread_local! {
static CURRENT_LUA_STATE: Cell<Option<LuaState>> = Cell::default();
}
pub static LUA: LazyLock<Lua51> = LazyLock::new(|| unsafe {
let lib = libloading::Library::new("./lua51.dll").expect("library to exist");
Lua51::from_library(lib).expect("library to be lua")
});
2024-11-21 17:08:03 +03:00
#[derive(Clone, Copy)]
2024-11-25 02:33:02 +03:00
pub struct LuaState {
2024-11-21 18:50:10 +03:00
lua: *mut lua_State,
}
2024-11-21 17:08:03 +03:00
impl LuaState {
2024-11-25 02:33:02 +03:00
pub fn new(lua: *mut lua_State) -> Self {
2024-11-21 18:50:10 +03:00
Self { lua }
}
/// Returns a lua state that is considered "current". Usually set when we get called from noita.
2024-11-25 02:33:02 +03:00
pub fn current() -> eyre::Result<Self> {
CURRENT_LUA_STATE
.get()
.ok_or_eyre("No current lua state available")
}
2024-11-25 02:33:02 +03:00
pub fn make_current(self) {
CURRENT_LUA_STATE.set(Some(self));
}
pub fn raw(&self) -> *mut lua_State {
2024-11-21 18:50:10 +03:00
self.lua
2024-11-21 17:08:03 +03:00
}
2024-11-25 02:33:02 +03:00
pub fn to_integer(&self, index: i32) -> isize {
2024-11-21 18:50:10 +03:00
unsafe { LUA.lua_tointeger(self.lua, index) }
}
2024-11-25 02:33:02 +03:00
pub fn to_number(&self, index: i32) -> f64 {
2024-11-24 23:12:35 +03:00
unsafe { LUA.lua_tonumber(self.lua, index) }
}
2024-11-25 02:33:02 +03:00
pub fn to_bool(&self, index: i32) -> bool {
2024-11-24 23:12:35 +03:00
unsafe { LUA.lua_toboolean(self.lua, index) > 0 }
}
2024-11-25 02:33:02 +03:00
pub fn to_string(&self, index: i32) -> eyre::Result<String> {
2024-11-24 23:12:35 +03:00
let mut size = 0;
let buf = unsafe { LUA.lua_tolstring(self.lua, index, &mut size) };
if buf.is_null() {
bail!("Expected a string, but got a null pointer");
}
let slice = unsafe { slice::from_raw_parts(buf as *const u8, size) };
Ok(String::from_utf8(slice.to_owned())
.context("Attempting to get lua string, expecting it to be utf-8")?)
}
2024-11-25 02:33:02 +03:00
pub fn to_cfunction(&self, index: i32) -> lua_CFunction {
2024-11-21 18:50:10 +03:00
unsafe { LUA.lua_tocfunction(self.lua, index) }
}
2024-11-25 02:33:02 +03:00
pub fn push_number(&self, val: f64) {
2024-11-24 23:12:35 +03:00
unsafe { LUA.lua_pushnumber(self.lua, val) };
}
2024-11-25 02:33:02 +03:00
pub fn push_integer(&self, val: isize) {
2024-11-24 23:12:35 +03:00
unsafe { LUA.lua_pushinteger(self.lua, val) };
}
2024-11-25 02:33:02 +03:00
pub fn push_bool(&self, val: bool) {
2024-11-24 23:12:35 +03:00
unsafe { LUA.lua_pushboolean(self.lua, val as i32) };
}
2024-11-25 02:33:02 +03:00
pub fn push_string(&self, s: &str) {
unsafe {
2024-11-24 23:12:35 +03:00
LUA.lua_pushlstring(self.lua, s.as_bytes().as_ptr() as *const c_char, s.len());
}
}
2024-11-25 02:33:02 +03:00
pub fn push_nil(&self) {
unsafe { LUA.lua_pushnil(self.lua) }
}
pub fn call(&self, nargs: i32, nresults: i32) {
2024-11-24 23:12:35 +03:00
unsafe { LUA.lua_call(self.lua, nargs, nresults) };
}
2024-11-25 02:33:02 +03:00
pub fn get_global(&self, name: &CStr) {
2024-11-21 18:50:10 +03:00
unsafe { LUA.lua_getfield(self.lua, LUA_GLOBALSINDEX, name.as_ptr()) };
}
2024-11-25 02:33:02 +03:00
pub fn pop_last(&self) {
2024-11-21 18:50:10 +03:00
unsafe { LUA.lua_settop(self.lua, -2) };
2024-11-21 17:08:03 +03:00
}
2024-11-25 02:33:02 +03:00
pub fn pop_last_n(&self, n: i32) {
unsafe { LUA.lua_settop(self.lua, -1 - (n)) };
}
/// Raise an error with message `s`
///
/// This takes String so that it gets deallocated properly, as this functions doesn't return.
unsafe fn raise_error(&self, s: String) -> ! {
self.push_string(&s);
mem::drop(s);
unsafe { LUA.lua_error(self.lua) };
// lua_error does not return.
unreachable!()
}
}
/// Used for types that can be returned from functions that were defined in rust to lua.
pub trait LuaFnRet {
fn do_return(self, lua: LuaState) -> c_int;
}
/// Function intends to return several values that it has on stack.
pub struct ValuesOnStack(pub c_int);
impl LuaFnRet for ValuesOnStack {
fn do_return(self, _lua: LuaState) -> c_int {
self.0
}
}
impl LuaFnRet for () {
fn do_return(self, _lua: LuaState) -> c_int {
0
}
}
impl<R: LuaFnRet> LuaFnRet for eyre::Result<R> {
fn do_return(self, lua: LuaState) -> c_int {
match self {
Ok(ok) => ok.do_return(lua),
Err(err) => unsafe {
lua.raise_error(format!("Error in rust call: {:?}", err));
},
}
}
2024-11-21 17:08:03 +03:00
}
/// Trait for arguments that can be put on lua stack.
pub(crate) trait LuaPutValue {
fn put(&self, lua: LuaState);
fn is_non_empty(&self) -> bool {
true
}
}
impl LuaPutValue for i32 {
fn put(&self, lua: LuaState) {
lua.push_integer(*self as isize);
}
}
impl LuaPutValue for isize {
fn put(&self, lua: LuaState) {
lua.push_integer(*self);
}
}
impl LuaPutValue for u32 {
fn put(&self, lua: LuaState) {
lua.push_integer(unsafe { mem::transmute::<_, i32>(*self) as isize });
}
}
impl LuaPutValue for f64 {
fn put(&self, lua: LuaState) {
lua.push_number(*self);
}
}
impl LuaPutValue for bool {
fn put(&self, lua: LuaState) {
lua.push_bool(*self);
}
}
impl LuaPutValue for Cow<'_, str> {
fn put(&self, lua: LuaState) {
lua.push_string(self.as_ref());
}
}
impl LuaPutValue for str {
fn put(&self, lua: LuaState) {
lua.push_string(self);
}
}
impl LuaPutValue for EntityID {
fn put(&self, lua: LuaState) {
self.0.put(lua);
}
}
impl LuaPutValue for ComponentID {
fn put(&self, lua: LuaState) {
self.0.put(lua);
}
}
impl LuaPutValue for Color {
fn put(&self, _lua: LuaState) {
todo!()
}
}
impl LuaPutValue for Obj {
fn put(&self, _lua: LuaState) {
todo!()
}
}
impl<T: LuaPutValue> LuaPutValue for Option<T> {
fn put(&self, lua: LuaState) {
match self {
Some(val) => val.put(lua),
None => lua.push_nil(),
}
}
fn is_non_empty(&self) -> bool {
match self {
Some(val) => val.is_non_empty(),
None => false,
}
}
}