From eaa8c2832ce92f7baa5b0a0cd9e74d60d8f9f45d Mon Sep 17 00:00:00 2001 From: IQuant Date: Mon, 16 Sep 2024 20:36:13 +0300 Subject: [PATCH 01/10] Log addr of get_cell --- quant.ew/files/system/world_sync/world.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quant.ew/files/system/world_sync/world.lua b/quant.ew/files/system/world_sync/world.lua index 040d01d3..195aeffb 100644 --- a/quant.ew/files/system/world_sync/world.lua +++ b/quant.ew/files/system/world_sync/world.lua @@ -5,6 +5,8 @@ local world = {} local ffi = require("ffi") local world_ffi = require("noitapatcher.nsew.world_ffi") +print("get_cell: " .. tostring(world_ffi.get_cell)) + local C = ffi.C ffi.cdef([[ From da2cb9b62548b645d6c4587ead923cf038ad2d00 Mon Sep 17 00:00:00 2001 From: IQuant Date: Tue, 17 Sep 2024 14:31:08 +0300 Subject: [PATCH 02/10] get_cell impl in ewext (maybe) --- .vscode/c_cpp_properties.json | 15 +++++++++++++++ .vscode/settings.json | 3 ++- ewext/src/lib.rs | 2 ++ ewext/src/noita.rs | 16 ++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 ewext/src/noita.rs diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..64d3f903 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,15 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "linux-clang-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index bcad2d74..095aab46 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,6 @@ "Lua.diagnostics.globals": [ "wait", "async" - ] + ], + "C_Cpp.default.compilerPath": "/usr/bin/clang++" } \ No newline at end of file diff --git a/ewext/src/lib.rs b/ewext/src/lib.rs index 8486b869..5e2715bf 100644 --- a/ewext/src/lib.rs +++ b/ewext/src/lib.rs @@ -4,6 +4,8 @@ use lua_bindings::{lua_State, Lua51}; mod lua_bindings; +mod noita; + static LUA: LazyLock = LazyLock::new(|| unsafe { let lib = libloading::Library::new("./lua51.dll").expect("library to exist"); Lua51::from_library(lib).expect("library to be lua") diff --git a/ewext/src/noita.rs b/ewext/src/noita.rs new file mode 100644 index 00000000..2425cb40 --- /dev/null +++ b/ewext/src/noita.rs @@ -0,0 +1,16 @@ +use std::ffi::c_void; + +pub(crate) struct ChunkMap { + this: *mut c_void, +} + +impl ChunkMap { + unsafe fn get_cell(&self, x: u32, y: u32) { + let x = x as isize; + let y = y as isize; + let index = ((((y) >> 9) - 256 & 511) * 512 + (((x) >> 9) - 256 & 511)) * 4; + let chunk_arr = self.this.offset(8).cast::<*const c_void>().read(); + let chunk = chunk_arr.offset(index).cast::<*const c_void>().read(); + let pixel = chunk.offset(((y & 511) << 9 | x & 511) * 4); + } +} From a0fee57d0c61a1a0184b9284eac885dd1df92a03 Mon Sep 17 00:00:00 2001 From: IQuant Date: Mon, 23 Sep 2024 19:48:56 +0300 Subject: [PATCH 03/10] ewext world state init stuff --- ewext/src/lib.rs | 65 +++++++++---------- ewext/src/noita.rs | 27 ++++++-- .../files/system/ewext_init/ewext_init.lua | 14 ++++ quant.ew/init.lua | 7 +- 4 files changed, 69 insertions(+), 44 deletions(-) create mode 100644 quant.ew/files/system/ewext_init/ewext_init.lua diff --git a/ewext/src/lib.rs b/ewext/src/lib.rs index 5e2715bf..771ef463 100644 --- a/ewext/src/lib.rs +++ b/ewext/src/lib.rs @@ -1,6 +1,12 @@ -use std::{ffi::c_int, sync::LazyLock}; +use std::{ + borrow::BorrowMut, + cell::{LazyCell, RefCell}, + ffi::{c_int, c_void}, + sync::{LazyLock, Mutex}, +}; use lua_bindings::{lua_State, Lua51}; +use noita::ParticleWorldState; mod lua_bindings; @@ -11,39 +17,29 @@ static LUA: LazyLock = LazyLock::new(|| unsafe { Lua51::from_library(lib).expect("library to be lua") }); +thread_local! { + static STATE: LazyCell = LazyCell::new(|| ExtState::default()); +} + +#[derive(Default)] +struct ExtState { + particle_world_state: Option, +} + // const EWEXT: [(&'static str, Function); 1] = [("testfn", None)]; -extern "C" fn test_fn(_lua: *mut lua_State) -> c_int { - println!("\nStarting trace"); - backtrace::trace(|frame| { - // let ip = frame.ip(); - let symbol_address = frame.symbol_address(); +extern "C" fn init_particle_world_state(lua: *mut lua_State) -> c_int { + println!("\nInitializing particle world state"); + let world_pointer = unsafe { LUA.lua_tointeger(lua, 1) }; + let chunk_map_pointer = unsafe { LUA.lua_tointeger(lua, 2) }; + println!("pws stuff: {world_pointer:?} {chunk_map_pointer:?}"); - print!("symbol: {:#08X}", symbol_address as usize); - if let Some(base) = frame.module_base_address() { - print!(" base: {:#08X}", base as usize); - } - // Resolve this instruction pointer to a symbol name - backtrace::resolve_frame(frame, |symbol| { - if let Some(name) = symbol.name() { - print!(" name: {name}"); - } - if let Some(filename) = symbol.filename() { - print!(" file: {}", filename.display()); - } - }); - println!(); - - for i in 0..16 { - let b: u8 = - unsafe { std::ptr::read_volatile((symbol_address as *const u8).wrapping_add(i)) }; - print!("{:02X} ", b); - } - println!(); - - true // keep going to the next frame + STATE.with(|mut state| { + state.particle_world_state = Some(ParticleWorldState::new( + world_pointer as *mut c_void, + chunk_map_pointer as *mut c_void, + )) }); - println!("End trace\n"); 0 } @@ -51,10 +47,11 @@ extern "C" fn test_fn(_lua: *mut lua_State) -> c_int { pub extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int { println!("Initializing ewext"); unsafe { - LUA.lua_pushcclosure(lua, Some(test_fn), 0); - // LUA.lua_setfield(lua, LUA_GLOBALSINDEX, c"ewext".as_ptr()) + LUA.lua_createtable(lua, 0, 0); + + LUA.lua_pushcclosure(lua, Some(init_particle_world_state), 0); + LUA.lua_setfield(lua, -2, c"init_particle_world_state".as_ptr()); } - // let mut luastate = unsafe { State::from_ptr(luastateptr) }; - // luastate.new_lib(&EWEXT); + println!("Initializing ewext - Ok"); 1 } diff --git a/ewext/src/noita.rs b/ewext/src/noita.rs index 2425cb40..33ab8453 100644 --- a/ewext/src/noita.rs +++ b/ewext/src/noita.rs @@ -1,16 +1,29 @@ use std::ffi::c_void; -pub(crate) struct ChunkMap { - this: *mut c_void, +pub(crate) struct ParticleWorldState { + world_pointer: *mut c_void, + chunk_map_this: *mut c_void, } -impl ChunkMap { - unsafe fn get_cell(&self, x: u32, y: u32) { +impl ParticleWorldState { + unsafe fn get_cell(&self, x: u32, y: u32) -> *const c_void { let x = x as isize; let y = y as isize; - let index = ((((y) >> 9) - 256 & 511) * 512 + (((x) >> 9) - 256 & 511)) * 4; - let chunk_arr = self.this.offset(8).cast::<*const c_void>().read(); - let chunk = chunk_arr.offset(index).cast::<*const c_void>().read(); + let chunk_index = (((((y) >> 9) - 256) & 511) * 512 + ((((x) >> 9) - 256) & 511)) * 4; + let chunk_arr = self + .chunk_map_this + .offset(8) + .cast::<*const *const c_void>() + .read(); + let chunk = chunk_arr.offset(chunk_index).read(); let pixel = chunk.offset(((y & 511) << 9 | x & 511) * 4); + pixel + } + + pub(crate) fn new(world_pointer: *mut c_void, chunk_map_pointer: *mut c_void) -> Self { + Self { + world_pointer, + chunk_map_this: chunk_map_pointer, + } } } diff --git a/quant.ew/files/system/ewext_init/ewext_init.lua b/quant.ew/files/system/ewext_init/ewext_init.lua new file mode 100644 index 00000000..b5bad2d8 --- /dev/null +++ b/quant.ew/files/system/ewext_init/ewext_init.lua @@ -0,0 +1,14 @@ +local ffi = require("ffi") + +local module = {} + +function module.on_world_initialized() + local world_ffi = require("noitapatcher.nsew.world_ffi") + local grid_world = world_ffi.get_grid_world() + local chunk_map = grid_world.vtable.get_chunk_map(grid_world) + grid_world = tonumber(ffi.cast("intptr_t", grid_world)) + chunk_map = tonumber(ffi.cast("intptr_t", chunk_map)) + ewext.init_particle_world_state(grid_world, chunk_map) +end + +return module \ No newline at end of file diff --git a/quant.ew/init.lua b/quant.ew/init.lua index 542ebe3f..5fb83fd2 100755 --- a/quant.ew/init.lua +++ b/quant.ew/init.lua @@ -5,9 +5,6 @@ package.cpath = package.cpath .. ";./mods/quant.ew/?.dll" package.path = package.path .. ";./mods/quant.ew/?.lua" print(package.cpath) --- ewext = require("ewext") --- ewext() - dofile_once( "data/scripts/lib/utilities.lua" ) dofile_once("mods/quant.ew/files/system/player/player_cosmetics.lua") @@ -17,6 +14,8 @@ np.EnableGameSimulatePausing(false) np.InstallDamageDetailsPatch() np.SilenceLogs("Warning - streaming didn\'t find any chunks it could stream away...\n") +ewext = require("ewext") + -- Make some stuff global, as it's way too annoying to import each time. ctx = dofile_once("mods/quant.ew/files/core/ctx.lua") player_fns = dofile_once("mods/quant.ew/files/core/player_fns.lua") @@ -42,6 +41,8 @@ np.CrossCallAdd("ew_per_peer_seed", function() end) local function load_modules() + ctx.load_system("ewext_init") + ctx.dofile_and_add_hooks("mods/quant.ew/files/system/item_sync.lua") ctx.dofile_and_add_hooks("mods/quant.ew/files/system/player_sync.lua") From 42e720959e45a9bb927531095b130dc29b884409 Mon Sep 17 00:00:00 2001 From: IQuant Date: Mon, 23 Sep 2024 22:42:03 +0300 Subject: [PATCH 04/10] get_pixel_pointer seems to work --- ewext/src/lib.rs | 26 ++++++++++++++----- ewext/src/noita.rs | 23 ++++++++-------- .../files/system/ewext_init/ewext_init.lua | 7 +++++ 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/ewext/src/lib.rs b/ewext/src/lib.rs index 771ef463..4d3e011a 100644 --- a/ewext/src/lib.rs +++ b/ewext/src/lib.rs @@ -1,8 +1,7 @@ use std::{ - borrow::BorrowMut, cell::{LazyCell, RefCell}, ffi::{c_int, c_void}, - sync::{LazyLock, Mutex}, + sync::LazyLock, }; use lua_bindings::{lua_State, Lua51}; @@ -18,7 +17,7 @@ static LUA: LazyLock = LazyLock::new(|| unsafe { }); thread_local! { - static STATE: LazyCell = LazyCell::new(|| ExtState::default()); + static STATE: LazyCell> = LazyCell::new(|| ExtState::default().into()); } #[derive(Default)] @@ -34,15 +33,28 @@ extern "C" fn init_particle_world_state(lua: *mut lua_State) -> c_int { let chunk_map_pointer = unsafe { LUA.lua_tointeger(lua, 2) }; println!("pws stuff: {world_pointer:?} {chunk_map_pointer:?}"); - STATE.with(|mut state| { - state.particle_world_state = Some(ParticleWorldState::new( + STATE.with(|state| { + state.borrow_mut().particle_world_state = Some(ParticleWorldState::new( world_pointer as *mut c_void, chunk_map_pointer as *mut c_void, - )) + )); }); 0 } +extern "C" fn get_pixel_pointer(lua: *mut lua_State) -> c_int { + let x = unsafe { LUA.lua_tointeger(lua, 1) } as i32; + let y = unsafe { LUA.lua_tointeger(lua, 2) } as i32; + + STATE.with(|state| { + let state = state.borrow_mut(); + let pws = state.particle_world_state.as_ref().unwrap(); + let pixel_pointer = unsafe { pws.get_cell(x, y) }; + unsafe { LUA.lua_pushinteger(lua, pixel_pointer as isize) }; + }); + 1 +} + #[no_mangle] pub extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int { println!("Initializing ewext"); @@ -51,6 +63,8 @@ pub extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int { LUA.lua_pushcclosure(lua, Some(init_particle_world_state), 0); LUA.lua_setfield(lua, -2, c"init_particle_world_state".as_ptr()); + LUA.lua_pushcclosure(lua, Some(get_pixel_pointer), 0); + LUA.lua_setfield(lua, -2, c"get_pixel_pointer".as_ptr()); } println!("Initializing ewext - Ok"); 1 diff --git a/ewext/src/noita.rs b/ewext/src/noita.rs index 33ab8453..7bf63d7f 100644 --- a/ewext/src/noita.rs +++ b/ewext/src/noita.rs @@ -1,4 +1,4 @@ -use std::ffi::c_void; +use std::{ffi::c_void, ptr::null}; pub(crate) struct ParticleWorldState { world_pointer: *mut c_void, @@ -6,16 +6,17 @@ pub(crate) struct ParticleWorldState { } impl ParticleWorldState { - unsafe fn get_cell(&self, x: u32, y: u32) -> *const c_void { - let x = x as isize; - let y = y as isize; - let chunk_index = (((((y) >> 9) - 256) & 511) * 512 + ((((x) >> 9) - 256) & 511)) * 4; - let chunk_arr = self - .chunk_map_this - .offset(8) - .cast::<*const *const c_void>() - .read(); - let chunk = chunk_arr.offset(chunk_index).read(); + pub(crate) unsafe fn get_cell(&self, x: i32, y: i32) -> *const c_void { + let x = dbg!(x as isize); + let y = dbg!(y as isize); + let chunk_index = dbg!((((((y) >> 9) - 256) & 511) * 512 + ((((x) >> 9) - 256) & 511)) * 4); + let chunk_arr = self.chunk_map_this.offset(8).cast::<*const c_void>().read(); + // dbg!(chunk_arr); + let chunk = chunk_arr.offset(chunk_index).cast::<*const c_void>().read(); + dbg!(chunk); + if chunk.is_null() { + return null(); + } let pixel = chunk.offset(((y & 511) << 9 | x & 511) * 4); pixel } diff --git a/quant.ew/files/system/ewext_init/ewext_init.lua b/quant.ew/files/system/ewext_init/ewext_init.lua index b5bad2d8..7946c717 100644 --- a/quant.ew/files/system/ewext_init/ewext_init.lua +++ b/quant.ew/files/system/ewext_init/ewext_init.lua @@ -11,4 +11,11 @@ function module.on_world_initialized() ewext.init_particle_world_state(grid_world, chunk_map) end +function module.on_local_player_spawn() + local pix_p = ewext.get_pixel_pointer(0, 0) + -- assert(pix_p ~= 0) + -- assert(tonumber(ffi.cast("intptr_t", ppixel)) == pix_p) + +end + return module \ No newline at end of file From 6b9b5254f3e5d66cc1863a04ea65968f88a5bdab Mon Sep 17 00:00:00 2001 From: IQuant Date: Mon, 23 Sep 2024 23:29:21 +0300 Subject: [PATCH 05/10] No it doesn't quite work --- ewext/src/noita.rs | 2 +- quant.ew/files/system/ewext_init/ewext_init.lua | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ewext/src/noita.rs b/ewext/src/noita.rs index 7bf63d7f..6c56e186 100644 --- a/ewext/src/noita.rs +++ b/ewext/src/noita.rs @@ -9,7 +9,7 @@ impl ParticleWorldState { pub(crate) unsafe fn get_cell(&self, x: i32, y: i32) -> *const c_void { let x = dbg!(x as isize); let y = dbg!(y as isize); - let chunk_index = dbg!((((((y) >> 9) - 256) & 511) * 512 + ((((x) >> 9) - 256) & 511)) * 4); + let chunk_index = (((((y) >> 9) - 256) & 511) * 512 + ((((x) >> 9) - 256) & 511)) * 4; let chunk_arr = self.chunk_map_this.offset(8).cast::<*const c_void>().read(); // dbg!(chunk_arr); let chunk = chunk_arr.offset(chunk_index).cast::<*const c_void>().read(); diff --git a/quant.ew/files/system/ewext_init/ewext_init.lua b/quant.ew/files/system/ewext_init/ewext_init.lua index 7946c717..842bcdae 100644 --- a/quant.ew/files/system/ewext_init/ewext_init.lua +++ b/quant.ew/files/system/ewext_init/ewext_init.lua @@ -1,9 +1,9 @@ local ffi = require("ffi") +local world_ffi = require("noitapatcher.nsew.world_ffi") local module = {} function module.on_world_initialized() - local world_ffi = require("noitapatcher.nsew.world_ffi") local grid_world = world_ffi.get_grid_world() local chunk_map = grid_world.vtable.get_chunk_map(grid_world) grid_world = tonumber(ffi.cast("intptr_t", grid_world)) @@ -12,9 +12,16 @@ function module.on_world_initialized() end 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) - -- assert(tonumber(ffi.cast("intptr_t", ppixel)) == pix_p) + print(tonumber(ffi.cast("intptr_t", ppixel))) + print(pix_p) + + assert(tonumber(ffi.cast("intptr_t", ppixel)) == pix_p) end From 985bb6dbe23642d193687a42b81e4e075015767c Mon Sep 17 00:00:00 2001 From: IQuant Date: Mon, 23 Sep 2024 23:40:48 +0300 Subject: [PATCH 06/10] Turns out I missed a deref. get_cell should work now. --- ewext/src/noita.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ewext/src/noita.rs b/ewext/src/noita.rs index 6c56e186..df12bc1a 100644 --- a/ewext/src/noita.rs +++ b/ewext/src/noita.rs @@ -7,17 +7,20 @@ pub(crate) struct ParticleWorldState { impl ParticleWorldState { pub(crate) unsafe fn get_cell(&self, x: i32, y: i32) -> *const c_void { - let x = dbg!(x as isize); - let y = dbg!(y as isize); + let x = x as isize; + let y = y as isize; let chunk_index = (((((y) >> 9) - 256) & 511) * 512 + ((((x) >> 9) - 256) & 511)) * 4; + // Deref 1/3 let chunk_arr = self.chunk_map_this.offset(8).cast::<*const c_void>().read(); - // dbg!(chunk_arr); + // Deref 2/3 let chunk = chunk_arr.offset(chunk_index).cast::<*const c_void>().read(); - dbg!(chunk); if chunk.is_null() { + // TODO this normally returns air material return null(); } - let pixel = chunk.offset(((y & 511) << 9 | x & 511) * 4); + // Deref 3/3 + let pixel_array = chunk.cast::<*const c_void>().read(); + let pixel = pixel_array.offset(((y & 511) << 9 | x & 511) * 4); pixel } From fb4a392b97c264ebd44c423ff73bbe8835095ee5 Mon Sep 17 00:00:00 2001 From: IQuant Date: Fri, 27 Sep 2024 17:42:42 +0300 Subject: [PATCH 07/10] encode_area seems to work, ~4.6ns/pixel --- Justfile | 4 + ewext/src/lib.rs | 38 +++- ewext/src/noita.rs | 164 ++++++++++++++++-- ewext/src/noita/ntypes.rs | 66 +++++++ .../src/net/world/world_model/encoding.rs | 8 +- .../files/system/ewext_init/ewext_init.lua | 15 +- quant.ew/files/system/world_sync/world.lua | 15 +- .../files/system/world_sync/world_sync.lua | 1 + 8 files changed, 266 insertions(+), 45 deletions(-) create mode 100644 ewext/src/noita/ntypes.rs diff --git a/Justfile b/Justfile index 78805c6e..9758c635 100644 --- a/Justfile +++ b/Justfile @@ -26,6 +26,10 @@ build_ext: cd ewext && cargo build --release --target=i686-pc-windows-gnu 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 diff --git a/ewext/src/lib.rs b/ewext/src/lib.rs index 4d3e011a..2d3c3ce7 100644 --- a/ewext/src/lib.rs +++ b/ewext/src/lib.rs @@ -5,7 +5,7 @@ use std::{ }; use lua_bindings::{lua_State, Lua51}; -use noita::ParticleWorldState; +use noita::{NoitaPixelRun, ParticleWorldState}; 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"); let world_pointer = unsafe { LUA.lua_tointeger(lua, 1) }; 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:?}"); STATE.with(|state| { - state.borrow_mut().particle_world_state = Some(ParticleWorldState::new( - world_pointer as *mut c_void, - chunk_map_pointer as *mut c_void, - )); + state.borrow_mut().particle_world_state = Some(ParticleWorldState { + world_ptr: world_pointer as *mut c_void, + chunk_map_ptr: chunk_map_pointer as *mut c_void, + material_list_ptr: material_list_pointer as _, + }); }); 0 } @@ -49,8 +51,24 @@ extern "C" fn get_pixel_pointer(lua: *mut lua_State) -> c_int { STATE.with(|state| { let state = state.borrow_mut(); let pws = state.particle_world_state.as_ref().unwrap(); - let pixel_pointer = unsafe { pws.get_cell(x, y) }; - unsafe { LUA.lua_pushinteger(lua, pixel_pointer as isize) }; + // let pixel_pointer = pws.get_cell_raw(x, y); + // 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 } @@ -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_setfield(lua, -2, c"init_particle_world_state".as_ptr()); - LUA.lua_pushcclosure(lua, Some(get_pixel_pointer), 0); - LUA.lua_setfield(lua, -2, c"get_pixel_pointer".as_ptr()); + // LUA.lua_pushcclosure(lua, Some(get_pixel_pointer), 0); + // 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"); 1 diff --git a/ewext/src/noita.rs b/ewext/src/noita.rs index df12bc1a..94b8d68b 100644 --- a/ewext/src/noita.rs +++ b/ewext/src/noita.rs @@ -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 { + pub length: u32, + pub data: Pixel, +} + +/// Copied from proxy. +/// Converts a normal sequence of pixels to a run-length-encoded one. +struct PixelRunner { + current_pixel: Option, + current_run_len: u32, + runs: Vec>, +} + +impl Default for PixelRunner { + fn default() -> Self { + Self::new() + } +} + +impl PixelRunner { + 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> { + 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 { - world_pointer: *mut c_void, - chunk_map_this: *mut c_void, + pub(crate) world_ptr: *mut c_void, + pub(crate) chunk_map_ptr: *mut c_void, + pub(crate) material_list_ptr: *const c_void, } 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 y = y as isize; let chunk_index = (((((y) >> 9) - 256) & 511) * 512 + ((((x) >> 9) - 256) & 511)) * 4; // 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 - 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() { - // TODO this normally returns air material - return null(); + return None; } // Deref 3/3 - let pixel_array = chunk.cast::<*const c_void>().read(); - let pixel = pixel_array.offset(((y & 511) << 9 | x & 511) * 4); - pixel + let pixel_array = unsafe { chunk.cast::<*const c_void>().read() }; + let pixel = unsafe { pixel_array.offset(((y & 511) << 9 | x & 511) * 4) }; + 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 { - Self { - world_pointer, - chunk_map_this: chunk_map_pointer, + fn get_cell_material_id(&self, cell: &ntypes::Cell) -> u16 { + let mat_ptr = cell.material_ptr(); + let offset = unsafe { mat_ptr.cast::().offset_from(self.material_list_ptr) }; + 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 } } diff --git a/ewext/src/noita/ntypes.rs b/ewext/src/noita/ntypes.rs new file mode 100644 index 00000000..8d24f48d --- /dev/null +++ b/ewext/src/noita/ntypes.rs @@ -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 + } +} diff --git a/noita-proxy/src/net/world/world_model/encoding.rs b/noita-proxy/src/net/world/world_model/encoding.rs index c9d48b32..2450bb1a 100644 --- a/noita-proxy/src/net/world/world_model/encoding.rs +++ b/noita-proxy/src/net/world/world_model/encoding.rs @@ -25,6 +25,10 @@ pub(crate) struct RawPixel { pub flags: u8, } +struct ByteParser<'a> { + data: &'a [u8], +} + /// Stores a run of pixels. /// Not specific to Noita side - length is an actual length #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Encode, Decode)] @@ -33,10 +37,6 @@ pub struct PixelRun { pub data: Pixel, } -struct ByteParser<'a> { - data: &'a [u8], -} - /// Converts a normal sequence of pixels to a run-length-encoded one. pub struct PixelRunner { current_pixel: Option, diff --git a/quant.ew/files/system/ewext_init/ewext_init.lua b/quant.ew/files/system/ewext_init/ewext_init.lua index 842bcdae..5017e4ec 100644 --- a/quant.ew/files/system/ewext_init/ewext_init.lua +++ b/quant.ew/files/system/ewext_init/ewext_init.lua @@ -8,21 +8,12 @@ function module.on_world_initialized() local chunk_map = grid_world.vtable.get_chunk_map(grid_world) grid_world = tonumber(ffi.cast("intptr_t", grid_world)) 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 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 return module \ No newline at end of file diff --git a/quant.ew/files/system/world_sync/world.lua b/quant.ew/files/system/world_sync/world.lua index 195aeffb..fc25eb7a 100644 --- a/quant.ew/files/system/world_sync/world.lua +++ b/quant.ew/files/system/world_sync/world.lua @@ -68,11 +68,11 @@ end -- @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 -- @see decode -function world.encode_area(chunk_map, start_x, start_y, end_x, end_y, encoded_area) - start_x = ffi.cast('int32_t', start_x) - start_y = ffi.cast('int32_t', start_y) - end_x = ffi.cast('int32_t', end_x) - end_y = ffi.cast('int32_t', end_y) +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_ini) + start_y = ffi.cast('int32_t', start_y_ini) + end_x = ffi.cast('int32_t', end_x_ini) + end_y = ffi.cast('int32_t', end_y_ini) 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.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 current_run = encoded_area.pixel_runs[0] diff --git a/quant.ew/files/system/world_sync/world_sync.lua b/quant.ew/files/system/world_sync/world_sync.lua index 6fed5ff6..d136e7d8 100644 --- a/quant.ew/files/system/world_sync/world_sync.lua +++ b/quant.ew/files/system/world_sync/world_sync.lua @@ -51,6 +51,7 @@ function world_sync.on_world_initialized() c = c - 1 print("Last material id: "..c) world.last_material_id = c + do_benchmark() end local function send_chunks(cx, cy, chunk_map) From b9291ba2128f5f1d803d378ae9e2c29579b9b6d3 Mon Sep 17 00:00:00 2001 From: IQuant Date: Fri, 27 Sep 2024 17:46:42 +0300 Subject: [PATCH 08/10] Cleanup a bit. --- ewext/src/lib.rs | 17 +---- ewext/src/noita.rs | 4 +- ewext/src/noita/ntypes.rs | 3 +- quant.ew/files/system/world_sync/world.lua | 76 +--------------------- 4 files changed, 7 insertions(+), 93 deletions(-) diff --git a/ewext/src/lib.rs b/ewext/src/lib.rs index 2d3c3ce7..e86f7073 100644 --- a/ewext/src/lib.rs +++ b/ewext/src/lib.rs @@ -36,7 +36,7 @@ extern "C" fn init_particle_world_state(lua: *mut lua_State) -> c_int { STATE.with(|state| { state.borrow_mut().particle_world_state = Some(ParticleWorldState { - world_ptr: world_pointer as *mut c_void, + _world_ptr: world_pointer as *mut c_void, chunk_map_ptr: chunk_map_pointer as *mut c_void, material_list_ptr: material_list_pointer as _, }); @@ -44,19 +44,6 @@ extern "C" fn init_particle_world_state(lua: *mut lua_State) -> c_int { 0 } -extern "C" fn get_pixel_pointer(lua: *mut lua_State) -> c_int { - let x = unsafe { LUA.lua_tointeger(lua, 1) } as i32; - let y = unsafe { LUA.lua_tointeger(lua, 2) } as i32; - - STATE.with(|state| { - let state = state.borrow_mut(); - let pws = state.particle_world_state.as_ref().unwrap(); - // let pixel_pointer = pws.get_cell_raw(x, y); - // 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; @@ -81,8 +68,6 @@ pub extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int { LUA.lua_pushcclosure(lua, Some(init_particle_world_state), 0); LUA.lua_setfield(lua, -2, c"init_particle_world_state".as_ptr()); - // LUA.lua_pushcclosure(lua, Some(get_pixel_pointer), 0); - // 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()); } diff --git a/ewext/src/noita.rs b/ewext/src/noita.rs index 94b8d68b..1342b2e1 100644 --- a/ewext/src/noita.rs +++ b/ewext/src/noita.rs @@ -1,4 +1,4 @@ -use std::{ffi::c_void, mem, ptr::null}; +use std::{ffi::c_void, mem}; mod ntypes; @@ -76,7 +76,7 @@ impl PixelRunner { } pub(crate) struct ParticleWorldState { - pub(crate) world_ptr: *mut c_void, + pub(crate) _world_ptr: *mut c_void, pub(crate) chunk_map_ptr: *mut c_void, pub(crate) material_list_ptr: *const c_void, } diff --git a/ewext/src/noita/ntypes.rs b/ewext/src/noita/ntypes.rs index 8d24f48d..52d0fc59 100644 --- a/ewext/src/noita/ntypes.rs +++ b/ewext/src/noita/ntypes.rs @@ -1,6 +1,6 @@ // Type defs borrowed from NoitaPatcher. -use std::ffi::{c_char, c_void}; +use std::ffi::c_char; pub(crate) const CELLDATA_SIZE: isize = 0x290; @@ -15,6 +15,7 @@ pub(crate) struct StdString { #[repr(u32)] #[derive(Debug, PartialEq, Clone, Copy)] +#[expect(dead_code)] pub(crate) enum CellType { None = 0, Liquid = 1, diff --git a/quant.ew/files/system/world_sync/world.lua b/quant.ew/files/system/world_sync/world.lua index fc25eb7a..7ce70bf0 100644 --- a/quant.ew/files/system/world_sync/world.lua +++ b/quant.ew/files/system/world_sync/world.lua @@ -84,8 +84,8 @@ function world.encode_area(chunk_map, start_x_ini, start_y_ini, end_x_ini, end_y return nil end - if width > 256 or height > 256 then - print("Invalid world part, dimension greater than 256") + if width > 128 or height > 128 then + print("Invalid world part, dimension greater than 128") return nil end @@ -95,78 +95,6 @@ function world.encode_area(chunk_map, start_x_ini, start_y_ini, end_x_ini, end_y 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 current_run = encoded_area.pixel_runs[0] - local run_length = 0 - local current_material = 0 - local current_flags = 0 - - local y = start_y - while y < end_y do - local x = start_x - while x < end_x do - local material_number = 0 - local flags = 0 - - local ppixel = world_ffi.get_cell(chunk_map, x, y) - local pixel = ppixel[0] - if pixel ~= nil then - local cell_type = pixel.vtable.get_cell_type(pixel) - - if cell_type ~= C.CELL_TYPE_SOLID then - local material_ptr = pixel.vtable.get_material(pixel) - material_number = world_ffi.get_material_id(material_ptr) - end - - if cell_type == C.CELL_TYPE_LIQUID then - local liquid_cell = ffi.cast(pliquid_cell, pixel) - if liquid_cell.is_static then - flags = bit.bor(flags, C.LIQUID_FLAG_STATIC) - end - end - end - - if x == start_x and y == start_y then - -- Initial run - current_material = material_number - current_flags = flags - elseif current_material ~= material_number or current_flags ~= flags then - -- Next run - current_run.length = run_length - 1 - current_run.material = current_material - current_run.flags = current_flags - - if run_count == C.PIXEL_RUN_MAX then - print("Area too complicated to encode") - return nil - end - - current_run = encoded_area.pixel_runs[run_count] - run_count = run_count + 1 - - run_length = 0 - current_material = material_number - current_flags = flags - end - - run_length = run_length + 1 - - x = x + 1 - end - y = y + 1 - end - - current_run.length = run_length - 1 - current_run.material = current_material - current_run.flags = current_flags - - encoded_area.header.pixel_run_count = run_count - return encoded_area end From b24a004d1f676206fe8b8f9061d25f3f4a27a543 Mon Sep 17 00:00:00 2001 From: IQuant Date: Fri, 27 Sep 2024 18:06:16 +0300 Subject: [PATCH 09/10] Don't allocate memory for PixelRunner every time, ~4ns/pixel. --- ewext/src/lib.rs | 5 +++-- ewext/src/noita.rs | 22 +++++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/ewext/src/lib.rs b/ewext/src/lib.rs index e86f7073..fe3cd9c1 100644 --- a/ewext/src/lib.rs +++ b/ewext/src/lib.rs @@ -39,6 +39,7 @@ extern "C" fn init_particle_world_state(lua: *mut lua_State) -> c_int { _world_ptr: world_pointer as *mut c_void, chunk_map_ptr: chunk_map_pointer as *mut c_void, material_list_ptr: material_list_pointer as _, + runner: Default::default(), }); }); 0 @@ -52,8 +53,8 @@ extern "C" fn encode_area(lua: *mut lua_State) -> c_int { 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 mut state = state.borrow_mut(); + let pws = state.particle_world_state.as_mut().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) }; }); diff --git a/ewext/src/noita.rs b/ewext/src/noita.rs index 1342b2e1..e746d92a 100644 --- a/ewext/src/noita.rs +++ b/ewext/src/noita.rs @@ -27,7 +27,7 @@ struct PixelRun { /// Copied from proxy. /// Converts a normal sequence of pixels to a run-length-encoded one. -struct PixelRunner { +pub(crate) struct PixelRunner { current_pixel: Option, current_run_len: u32, runs: Vec>, @@ -64,14 +64,20 @@ impl PixelRunner { self.current_run_len = 1; } } - fn build(mut self) -> Vec> { + fn build(&mut self) -> &[PixelRun] { 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 + &mut self.runs + } + + fn clear(&mut self) { + self.current_pixel = None; + self.current_run_len = 0; + self.runs.clear(); } } @@ -79,6 +85,8 @@ pub(crate) struct ParticleWorldState { pub(crate) _world_ptr: *mut c_void, pub(crate) chunk_map_ptr: *mut c_void, pub(crate) material_list_ptr: *const c_void, + + pub(crate) runner: PixelRunner, } impl ParticleWorldState { @@ -115,14 +123,13 @@ impl ParticleWorldState { } pub(crate) unsafe fn encode_area( - &self, + &mut 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 { @@ -148,12 +155,12 @@ impl ParticleWorldState { ntypes::CellType::Invalid => {} } } - runner.put_pixel(raw_pixel); + self.runner.put_pixel(raw_pixel); } } let mut pixel_runs = pixel_runs; - let built_runner = runner.build(); + let built_runner = self.runner.build(); let runs = built_runner.len(); for run in built_runner { let noita_pixel_run = pixel_runs.as_mut().unwrap(); @@ -162,6 +169,7 @@ impl ParticleWorldState { noita_pixel_run.flags = run.data.flags; pixel_runs = pixel_runs.offset(1); } + self.runner.clear(); runs } } From 54a7e4fc14e44faff6127c08de83b4e69712cf5e Mon Sep 17 00:00:00 2001 From: IQuant Date: Fri, 27 Sep 2024 18:22:19 +0300 Subject: [PATCH 10/10] Allow compiler to generate better code. ~1.8ns/pixel. --- ewext/src/noita.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ewext/src/noita.rs b/ewext/src/noita.rs index e746d92a..6987ec24 100644 --- a/ewext/src/noita.rs +++ b/ewext/src/noita.rs @@ -130,6 +130,12 @@ impl ParticleWorldState { end_y: i32, pixel_runs: *mut NoitaPixelRun, ) -> usize { + // Allow compiler to generate better code. + assert!(start_x % 128 == 0); + assert!(start_y % 128 == 0); + assert!((end_x - start_x) <= 128); + assert!((end_y - start_y) <= 128); + for y in start_y..end_y { for x in start_x..end_x { let mut raw_pixel = RawPixel {