mirror of
https://github.com/IntQuant/noita_entangled_worlds.git
synced 2025-10-19 07:03:16 +00:00
encode_area seems to work, ~4.6ns/pixel
This commit is contained in:
parent
985bb6dbe2
commit
fb4a392b97
8 changed files with 266 additions and 45 deletions
4
Justfile
4
Justfile
|
@ -26,6 +26,10 @@ build_ext:
|
||||||
cd ewext && cargo build --release --target=i686-pc-windows-gnu
|
cd ewext && cargo build --release --target=i686-pc-windows-gnu
|
||||||
cp ewext/target/i686-pc-windows-gnu/release/ewext.dll quant.ew/ewext.dll
|
cp ewext/target/i686-pc-windows-gnu/release/ewext.dll quant.ew/ewext.dll
|
||||||
|
|
||||||
|
build_ext_debug:
|
||||||
|
cd ewext && cargo build --target=i686-pc-windows-gnu
|
||||||
|
cp ewext/target/i686-pc-windows-gnu/debug/ewext.dll quant.ew/ewext.dll
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
||||||
run-rel: add_dylib_release
|
run-rel: add_dylib_release
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use lua_bindings::{lua_State, Lua51};
|
use lua_bindings::{lua_State, Lua51};
|
||||||
use noita::ParticleWorldState;
|
use noita::{NoitaPixelRun, ParticleWorldState};
|
||||||
|
|
||||||
mod lua_bindings;
|
mod lua_bindings;
|
||||||
|
|
||||||
|
@ -31,13 +31,15 @@ extern "C" fn init_particle_world_state(lua: *mut lua_State) -> c_int {
|
||||||
println!("\nInitializing particle world state");
|
println!("\nInitializing particle world state");
|
||||||
let world_pointer = unsafe { LUA.lua_tointeger(lua, 1) };
|
let world_pointer = unsafe { LUA.lua_tointeger(lua, 1) };
|
||||||
let chunk_map_pointer = unsafe { LUA.lua_tointeger(lua, 2) };
|
let chunk_map_pointer = unsafe { LUA.lua_tointeger(lua, 2) };
|
||||||
|
let material_list_pointer = unsafe { LUA.lua_tointeger(lua, 3) };
|
||||||
println!("pws stuff: {world_pointer:?} {chunk_map_pointer:?}");
|
println!("pws stuff: {world_pointer:?} {chunk_map_pointer:?}");
|
||||||
|
|
||||||
STATE.with(|state| {
|
STATE.with(|state| {
|
||||||
state.borrow_mut().particle_world_state = Some(ParticleWorldState::new(
|
state.borrow_mut().particle_world_state = Some(ParticleWorldState {
|
||||||
world_pointer as *mut c_void,
|
world_ptr: world_pointer as *mut c_void,
|
||||||
chunk_map_pointer as *mut c_void,
|
chunk_map_ptr: chunk_map_pointer as *mut c_void,
|
||||||
));
|
material_list_ptr: material_list_pointer as _,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
@ -49,8 +51,24 @@ extern "C" fn get_pixel_pointer(lua: *mut lua_State) -> c_int {
|
||||||
STATE.with(|state| {
|
STATE.with(|state| {
|
||||||
let state = state.borrow_mut();
|
let state = state.borrow_mut();
|
||||||
let pws = state.particle_world_state.as_ref().unwrap();
|
let pws = state.particle_world_state.as_ref().unwrap();
|
||||||
let pixel_pointer = unsafe { pws.get_cell(x, y) };
|
// let pixel_pointer = pws.get_cell_raw(x, y);
|
||||||
unsafe { LUA.lua_pushinteger(lua, pixel_pointer as isize) };
|
// unsafe { LUA.lua_pushinteger(lua, pixel_pointer as isize) };
|
||||||
|
});
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn encode_area(lua: *mut lua_State) -> c_int {
|
||||||
|
let start_x = unsafe { LUA.lua_tointeger(lua, 1) } as i32;
|
||||||
|
let start_y = unsafe { LUA.lua_tointeger(lua, 2) } as i32;
|
||||||
|
let end_x = unsafe { LUA.lua_tointeger(lua, 3) } as i32;
|
||||||
|
let end_y = unsafe { LUA.lua_tointeger(lua, 4) } as i32;
|
||||||
|
let encoded_buffer = unsafe { LUA.lua_tointeger(lua, 5) } as *mut NoitaPixelRun;
|
||||||
|
|
||||||
|
STATE.with(|state| {
|
||||||
|
let state = state.borrow_mut();
|
||||||
|
let pws = state.particle_world_state.as_ref().unwrap();
|
||||||
|
let runs = unsafe { pws.encode_area(start_x, start_y, end_x, end_y, encoded_buffer) };
|
||||||
|
unsafe { LUA.lua_pushinteger(lua, runs as isize) };
|
||||||
});
|
});
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
@ -63,8 +81,10 @@ pub extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
||||||
|
|
||||||
LUA.lua_pushcclosure(lua, Some(init_particle_world_state), 0);
|
LUA.lua_pushcclosure(lua, Some(init_particle_world_state), 0);
|
||||||
LUA.lua_setfield(lua, -2, c"init_particle_world_state".as_ptr());
|
LUA.lua_setfield(lua, -2, c"init_particle_world_state".as_ptr());
|
||||||
LUA.lua_pushcclosure(lua, Some(get_pixel_pointer), 0);
|
// LUA.lua_pushcclosure(lua, Some(get_pixel_pointer), 0);
|
||||||
LUA.lua_setfield(lua, -2, c"get_pixel_pointer".as_ptr());
|
// LUA.lua_setfield(lua, -2, c"get_pixel_pointer".as_ptr());
|
||||||
|
LUA.lua_pushcclosure(lua, Some(encode_area), 0);
|
||||||
|
LUA.lua_setfield(lua, -2, c"encode_area".as_ptr());
|
||||||
}
|
}
|
||||||
println!("Initializing ewext - Ok");
|
println!("Initializing ewext - Ok");
|
||||||
1
|
1
|
||||||
|
|
|
@ -1,33 +1,167 @@
|
||||||
use std::{ffi::c_void, ptr::null};
|
use std::{ffi::c_void, mem, ptr::null};
|
||||||
|
|
||||||
|
mod ntypes;
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
pub(crate) struct NoitaPixelRun {
|
||||||
|
length: u16,
|
||||||
|
material: u16,
|
||||||
|
flags: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copied from proxy.
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
pub(crate) struct RawPixel {
|
||||||
|
pub material: u16,
|
||||||
|
pub flags: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copied from proxy.
|
||||||
|
/// Stores a run of pixels.
|
||||||
|
/// Not specific to Noita side - length is an actual length
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PixelRun<Pixel> {
|
||||||
|
pub length: u32,
|
||||||
|
pub data: Pixel,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copied from proxy.
|
||||||
|
/// Converts a normal sequence of pixels to a run-length-encoded one.
|
||||||
|
struct PixelRunner<Pixel> {
|
||||||
|
current_pixel: Option<Pixel>,
|
||||||
|
current_run_len: u32,
|
||||||
|
runs: Vec<PixelRun<Pixel>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pixel: Eq + Copy> Default for PixelRunner<Pixel> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pixel: Eq + Copy> PixelRunner<Pixel> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
current_pixel: None,
|
||||||
|
current_run_len: 0,
|
||||||
|
runs: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn put_pixel(&mut self, pixel: Pixel) {
|
||||||
|
if let Some(current) = self.current_pixel {
|
||||||
|
if pixel != current {
|
||||||
|
self.runs.push(PixelRun {
|
||||||
|
length: self.current_run_len,
|
||||||
|
data: current,
|
||||||
|
});
|
||||||
|
self.current_pixel = Some(pixel);
|
||||||
|
self.current_run_len = 1;
|
||||||
|
} else {
|
||||||
|
self.current_run_len += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.current_pixel = Some(pixel);
|
||||||
|
self.current_run_len = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn build(mut self) -> Vec<PixelRun<Pixel>> {
|
||||||
|
if self.current_run_len > 0 {
|
||||||
|
self.runs.push(PixelRun {
|
||||||
|
length: self.current_run_len,
|
||||||
|
data: self.current_pixel.expect("has current pixel"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.runs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct ParticleWorldState {
|
pub(crate) struct ParticleWorldState {
|
||||||
world_pointer: *mut c_void,
|
pub(crate) world_ptr: *mut c_void,
|
||||||
chunk_map_this: *mut c_void,
|
pub(crate) chunk_map_ptr: *mut c_void,
|
||||||
|
pub(crate) material_list_ptr: *const c_void,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParticleWorldState {
|
impl ParticleWorldState {
|
||||||
pub(crate) unsafe fn get_cell(&self, x: i32, y: i32) -> *const c_void {
|
fn get_cell_raw(&self, x: i32, y: i32) -> Option<&ntypes::Cell> {
|
||||||
let x = x as isize;
|
let x = x as isize;
|
||||||
let y = y as isize;
|
let y = y as isize;
|
||||||
let chunk_index = (((((y) >> 9) - 256) & 511) * 512 + ((((x) >> 9) - 256) & 511)) * 4;
|
let chunk_index = (((((y) >> 9) - 256) & 511) * 512 + ((((x) >> 9) - 256) & 511)) * 4;
|
||||||
// Deref 1/3
|
// Deref 1/3
|
||||||
let chunk_arr = self.chunk_map_this.offset(8).cast::<*const c_void>().read();
|
let chunk_arr = unsafe { self.chunk_map_ptr.offset(8).cast::<*const c_void>().read() };
|
||||||
// Deref 2/3
|
// Deref 2/3
|
||||||
let chunk = chunk_arr.offset(chunk_index).cast::<*const c_void>().read();
|
let chunk = unsafe { chunk_arr.offset(chunk_index).cast::<*const c_void>().read() };
|
||||||
if chunk.is_null() {
|
if chunk.is_null() {
|
||||||
// TODO this normally returns air material
|
return None;
|
||||||
return null();
|
|
||||||
}
|
}
|
||||||
// Deref 3/3
|
// Deref 3/3
|
||||||
let pixel_array = chunk.cast::<*const c_void>().read();
|
let pixel_array = unsafe { chunk.cast::<*const c_void>().read() };
|
||||||
let pixel = pixel_array.offset(((y & 511) << 9 | x & 511) * 4);
|
let pixel = unsafe { pixel_array.offset(((y & 511) << 9 | x & 511) * 4) };
|
||||||
pixel
|
if pixel.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { pixel.cast::<*const ntypes::Cell>().read().as_ref() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new(world_pointer: *mut c_void, chunk_map_pointer: *mut c_void) -> Self {
|
fn get_cell_material_id(&self, cell: &ntypes::Cell) -> u16 {
|
||||||
Self {
|
let mat_ptr = cell.material_ptr();
|
||||||
world_pointer,
|
let offset = unsafe { mat_ptr.cast::<c_void>().offset_from(self.material_list_ptr) };
|
||||||
chunk_map_this: chunk_map_pointer,
|
let mat_id = (offset / ntypes::CELLDATA_SIZE) as u16;
|
||||||
|
mat_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cell_type(&self, cell: &ntypes::Cell) -> ntypes::CellType {
|
||||||
|
unsafe { cell.material_ptr().as_ref().unwrap().cell_type }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn encode_area(
|
||||||
|
&self,
|
||||||
|
start_x: i32,
|
||||||
|
start_y: i32,
|
||||||
|
end_x: i32,
|
||||||
|
end_y: i32,
|
||||||
|
pixel_runs: *mut NoitaPixelRun,
|
||||||
|
) -> usize {
|
||||||
|
let mut runner = PixelRunner::default();
|
||||||
|
for y in start_y..end_y {
|
||||||
|
for x in start_x..end_x {
|
||||||
|
let mut raw_pixel = RawPixel {
|
||||||
|
material: 0,
|
||||||
|
flags: 0,
|
||||||
|
};
|
||||||
|
let cell = self.get_cell_raw(x, y);
|
||||||
|
if let Some(cell) = cell {
|
||||||
|
let cell_type = self.get_cell_type(cell);
|
||||||
|
match cell_type {
|
||||||
|
ntypes::CellType::None => {}
|
||||||
|
// Nobody knows how box2d pixels work.
|
||||||
|
ntypes::CellType::Solid => {}
|
||||||
|
ntypes::CellType::Liquid => {
|
||||||
|
raw_pixel.material = self.get_cell_material_id(cell);
|
||||||
|
let cell: &ntypes::LiquidCell = unsafe { mem::transmute(cell) };
|
||||||
|
raw_pixel.flags = cell.is_static as u8;
|
||||||
|
}
|
||||||
|
ntypes::CellType::Gas | ntypes::CellType::Fire => {
|
||||||
|
raw_pixel.material = self.get_cell_material_id(cell);
|
||||||
|
}
|
||||||
|
// ???
|
||||||
|
ntypes::CellType::Invalid => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runner.put_pixel(raw_pixel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut pixel_runs = pixel_runs;
|
||||||
|
let built_runner = runner.build();
|
||||||
|
let runs = built_runner.len();
|
||||||
|
for run in built_runner {
|
||||||
|
let noita_pixel_run = pixel_runs.as_mut().unwrap();
|
||||||
|
noita_pixel_run.length = (run.length - 1) as u16;
|
||||||
|
noita_pixel_run.material = run.data.material;
|
||||||
|
noita_pixel_run.flags = run.data.flags;
|
||||||
|
pixel_runs = pixel_runs.offset(1);
|
||||||
|
}
|
||||||
|
runs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
66
ewext/src/noita/ntypes.rs
Normal file
66
ewext/src/noita/ntypes.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// Type defs borrowed from NoitaPatcher.
|
||||||
|
|
||||||
|
use std::ffi::{c_char, c_void};
|
||||||
|
|
||||||
|
pub(crate) const CELLDATA_SIZE: isize = 0x290;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct StdString {
|
||||||
|
buffer: *const i8,
|
||||||
|
sso_buffer: [i8; 12],
|
||||||
|
size: usize,
|
||||||
|
capacity: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u32)]
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub(crate) enum CellType {
|
||||||
|
None = 0,
|
||||||
|
Liquid = 1,
|
||||||
|
Gas = 2,
|
||||||
|
Solid = 3,
|
||||||
|
Fire = 4,
|
||||||
|
Invalid = 4294967295,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) struct CellData {
|
||||||
|
name: StdString,
|
||||||
|
ui_name: StdString,
|
||||||
|
material_type: i32,
|
||||||
|
id_2: i32,
|
||||||
|
pub(crate) cell_type: CellType,
|
||||||
|
// Has a bunch of other fields that aren't that relevant.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) struct CellVTable {}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) struct Cell {
|
||||||
|
pub(crate) vtable: *const CellVTable,
|
||||||
|
|
||||||
|
hp: i32,
|
||||||
|
unknown1: [u8; 8],
|
||||||
|
is_burning: bool,
|
||||||
|
unknown2: [u8; 3],
|
||||||
|
material_ptr: *const CellData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) struct LiquidCell {
|
||||||
|
cell: Cell,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
unknown1: c_char,
|
||||||
|
unknown2: c_char,
|
||||||
|
pub(crate) is_static: bool,
|
||||||
|
// Has a bunch of other fields that aren't that relevant.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cell {
|
||||||
|
pub(crate) fn material_ptr(&self) -> *const CellData {
|
||||||
|
self.material_ptr
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,10 @@ pub(crate) struct RawPixel {
|
||||||
pub flags: u8,
|
pub flags: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ByteParser<'a> {
|
||||||
|
data: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
/// Stores a run of pixels.
|
/// Stores a run of pixels.
|
||||||
/// Not specific to Noita side - length is an actual length
|
/// Not specific to Noita side - length is an actual length
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Encode, Decode)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Encode, Decode)]
|
||||||
|
@ -33,10 +37,6 @@ pub struct PixelRun<Pixel> {
|
||||||
pub data: Pixel,
|
pub data: Pixel,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ByteParser<'a> {
|
|
||||||
data: &'a [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a normal sequence of pixels to a run-length-encoded one.
|
/// Converts a normal sequence of pixels to a run-length-encoded one.
|
||||||
pub struct PixelRunner<Pixel> {
|
pub struct PixelRunner<Pixel> {
|
||||||
current_pixel: Option<Pixel>,
|
current_pixel: Option<Pixel>,
|
||||||
|
|
|
@ -8,20 +8,11 @@ function module.on_world_initialized()
|
||||||
local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
||||||
grid_world = tonumber(ffi.cast("intptr_t", grid_world))
|
grid_world = tonumber(ffi.cast("intptr_t", grid_world))
|
||||||
chunk_map = tonumber(ffi.cast("intptr_t", chunk_map))
|
chunk_map = tonumber(ffi.cast("intptr_t", chunk_map))
|
||||||
ewext.init_particle_world_state(grid_world, chunk_map)
|
local material_list = tonumber(ffi.cast("intptr_t", world_ffi.get_material_ptr(0)))
|
||||||
|
ewext.init_particle_world_state(grid_world, chunk_map, material_list)
|
||||||
end
|
end
|
||||||
|
|
||||||
function module.on_local_player_spawn()
|
function module.on_local_player_spawn()
|
||||||
local grid_world = world_ffi.get_grid_world()
|
|
||||||
local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
|
||||||
local pix_p = ewext.get_pixel_pointer(0, 0)
|
|
||||||
|
|
||||||
local ppixel = world_ffi.get_cell(chunk_map, 0, 0)
|
|
||||||
-- assert(pix_p ~= 0)
|
|
||||||
print(tonumber(ffi.cast("intptr_t", ppixel)))
|
|
||||||
print(pix_p)
|
|
||||||
|
|
||||||
assert(tonumber(ffi.cast("intptr_t", ppixel)) == pix_p)
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -68,11 +68,11 @@ end
|
||||||
-- @tparam EncodedArea encoded_area memory to use, if nil this function allocates its own memory
|
-- @tparam EncodedArea encoded_area memory to use, if nil this function allocates its own memory
|
||||||
-- @return returns an EncodedArea or nil if the area could not be encoded
|
-- @return returns an EncodedArea or nil if the area could not be encoded
|
||||||
-- @see decode
|
-- @see decode
|
||||||
function world.encode_area(chunk_map, start_x, start_y, end_x, end_y, encoded_area)
|
function world.encode_area(chunk_map, start_x_ini, start_y_ini, end_x_ini, end_y_ini, encoded_area)
|
||||||
start_x = ffi.cast('int32_t', start_x)
|
start_x = ffi.cast('int32_t', start_x_ini)
|
||||||
start_y = ffi.cast('int32_t', start_y)
|
start_y = ffi.cast('int32_t', start_y_ini)
|
||||||
end_x = ffi.cast('int32_t', end_x)
|
end_x = ffi.cast('int32_t', end_x_ini)
|
||||||
end_y = ffi.cast('int32_t', end_y)
|
end_y = ffi.cast('int32_t', end_y_ini)
|
||||||
|
|
||||||
encoded_area = encoded_area or world.EncodedArea()
|
encoded_area = encoded_area or world.EncodedArea()
|
||||||
|
|
||||||
|
@ -94,6 +94,11 @@ function world.encode_area(chunk_map, start_x, start_y, end_x, end_y, encoded_ar
|
||||||
encoded_area.header.width = width - 1
|
encoded_area.header.width = width - 1
|
||||||
encoded_area.header.height = height - 1
|
encoded_area.header.height = height - 1
|
||||||
|
|
||||||
|
encoded_area.header.pixel_run_count = ewext.encode_area(start_x_ini, start_y_ini, end_x_ini, end_y_ini, tonumber(ffi.cast("intptr_t", encoded_area.pixel_runs)))
|
||||||
|
if true then
|
||||||
|
return encoded_area
|
||||||
|
end
|
||||||
|
|
||||||
local run_count = 1
|
local run_count = 1
|
||||||
|
|
||||||
local current_run = encoded_area.pixel_runs[0]
|
local current_run = encoded_area.pixel_runs[0]
|
||||||
|
|
|
@ -51,6 +51,7 @@ function world_sync.on_world_initialized()
|
||||||
c = c - 1
|
c = c - 1
|
||||||
print("Last material id: "..c)
|
print("Last material id: "..c)
|
||||||
world.last_material_id = c
|
world.last_material_id = c
|
||||||
|
do_benchmark()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function send_chunks(cx, cy, chunk_map)
|
local function send_chunks(cx, cy, chunk_map)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue