diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..fed9a2ce --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(dir:*)" + ] + } +} diff --git a/Cargo.toml b/Cargo.toml index 10d89ca7..0e2ca27b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,22 @@ +[workspace] +resolver = "2" +members = [ + ".", + "plugins/empty", + "plugins/ecs", + "plugins/ecs_examples", + "plugins/export_macros", +] + +[workspace.dependencies] +hotline-rs = { path = "." } +maths-rs = "0.2.3" +bitflags = "1.3.2" +bevy_ecs = "0.15.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.81" +font-awesome = "0.2.0" + [package] name = "hotline-rs" version = "0.3.2" @@ -13,16 +32,16 @@ license-file = "license" exclude = ["config.user.jsn", "hotline-data"] [dependencies] -maths-rs = "0.2.3" -bitflags = "1.3.2" +maths-rs.workspace = true +bitflags.workspace = true stb_image_rust = "2.27.2" stb_image_write_rust = "1.16.1" -font-awesome = "0.2.0" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.81" +font-awesome.workspace = true +serde.workspace = true +serde_json.workspace = true hot-lib-reloader = "0.6.4" libloading = "0.7.4" -bevy_ecs = "0.15.0" +bevy_ecs.workspace = true ddsfile = "0.5.1" [dependencies.imgui-sys] @@ -98,6 +117,13 @@ required-features = ["client"] [profile.dev] opt-level = 0 +[profile.dev.package.empty] +opt-level = 1 +[profile.dev.package.ecs] +opt-level = 1 +[profile.dev.package.ecs_examples] +opt-level = 1 + [profile.release] lto = "off" -incremental = true \ No newline at end of file +incremental = true diff --git a/config.jsn b/config.jsn index f86f9054..b9077ab7 100644 --- a/config.jsn +++ b/config.jsn @@ -39,7 +39,7 @@ client: { type: shell commands: [ - "cargo build --manifest-path plugins/Cargo.toml ${config_flags}" + "cargo build -p empty -p ecs -p ecs_examples ${config_flags}" ] } plugins: { diff --git a/examples/imgui_demo/main.rs b/examples/imgui_demo/main.rs index 11741794..764a089e 100644 --- a/examples/imgui_demo/main.rs +++ b/examples/imgui_demo/main.rs @@ -107,7 +107,7 @@ fn main() -> Result<(), hotline_rs::Error> { dev.execute(&cmdbuffer); - swap_chain.swap(&dev); + swap_chain.swap(&mut dev); ci = (ci + 1) % 4; } diff --git a/examples/play_video/main.rs b/examples/play_video/main.rs index 7b18717f..4d6b9b0c 100644 --- a/examples/play_video/main.rs +++ b/examples/play_video/main.rs @@ -140,7 +140,7 @@ fn main() -> Result<(), hotline_rs::Error> { dev.execute(&cmdbuffer); - swap_chain.swap(&dev); + swap_chain.swap(&mut dev); ci = (ci + 1) % 4; } diff --git a/examples/raytraced_triangle/main.rs b/examples/raytraced_triangle/main.rs index e2c1bd44..5831b929 100644 --- a/examples/raytraced_triangle/main.rs +++ b/examples/raytraced_triangle/main.rs @@ -230,7 +230,7 @@ fn main() -> Result<(), hotline_rs::Error> { device.execute(&cmd); // swap for the next frame - swap_chain.swap(&device); + swap_chain.swap(&mut device); } // must wait for the final frame to be completed diff --git a/examples/resource_tests/main.rs b/examples/resource_tests/main.rs index 4f8a6018..09935366 100644 --- a/examples/resource_tests/main.rs +++ b/examples/resource_tests/main.rs @@ -243,7 +243,7 @@ fn main() -> Result<(), hotline_rs::Error> { dev.execute(&cmdbuffer); - swap_chain.swap(&dev); + swap_chain.swap(&mut dev); ci = (ci + 1) % 4; } diff --git a/examples/swap_chain/main.rs b/examples/swap_chain/main.rs index 87b0e76a..0b8fe098 100644 --- a/examples/swap_chain/main.rs +++ b/examples/swap_chain/main.rs @@ -88,7 +88,7 @@ fn main() -> Result<(), hotline_rs::Error> { device.execute(&cmd); println!("swap {}", t); - swap_chain.swap(&device); + swap_chain.swap(&mut device); t += 1.0 / 600.0; } diff --git a/examples/triangle/main.rs b/examples/triangle/main.rs index 88b57b41..b8c243f6 100644 --- a/examples/triangle/main.rs +++ b/examples/triangle/main.rs @@ -168,7 +168,7 @@ fn main() -> Result<(), hotline_rs::Error> { device.execute(&cmd); // swap for the next frame - swap_chain.swap(&device); + swap_chain.swap(&mut device); } // must wait for the final frame to be completed diff --git a/plugins/Cargo.toml b/plugins/Cargo.toml deleted file mode 100644 index ce1943ac..00000000 --- a/plugins/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[workspace] -resolver = "2" -members = ["empty", "ecs", "ecs_examples"] - -[profile.dev] -opt-level = 1 -lto = "off" - -[profile.release] -lto = "off" -incremental = true \ No newline at end of file diff --git a/plugins/config.toml b/plugins/config.toml deleted file mode 100644 index 678266e5..00000000 --- a/plugins/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] -rustflags = ["-C", "link-arg=-fuse-ld=lld"] \ No newline at end of file diff --git a/plugins/ecs/Cargo.toml b/plugins/ecs/Cargo.toml index df6f5aae..dcce13d8 100644 --- a/plugins/ecs/Cargo.toml +++ b/plugins/ecs/Cargo.toml @@ -7,10 +7,10 @@ edition = "2021" crate-type = ["rlib", "dylib"] [dependencies] -hotline-rs = { path = "../.." } -maths-rs = "0.2.3" -bevy_ecs = "0.15.0" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.81" -font-awesome = "0.2.0" -bitflags = "1.3.2" \ No newline at end of file +hotline-rs.workspace = true +maths-rs.workspace = true +bevy_ecs.workspace = true +serde.workspace = true +serde_json.workspace = true +font-awesome.workspace = true +bitflags.workspace = true diff --git a/plugins/ecs/src/lib.rs b/plugins/ecs/src/lib.rs index d2c3d061..00c0a02f 100644 --- a/plugins/ecs/src/lib.rs +++ b/plugins/ecs/src/lib.rs @@ -290,14 +290,14 @@ impl BevyPlugin { let exported_function_name = format!("export_{}", name); if view_name.is_empty() { // function with () no args - let hook = lib.get_symbol:: SystemConfigs>(exported_function_name.as_bytes()); + let hook = lib.get_symbol:: SystemConfigs>(exported_function_name.as_bytes()); if let Ok(hook_fn) = hook { return Some(hook_fn()); } } else { // function with pass name args - let hook = lib.get_symbol:: SystemConfigs>(exported_function_name.as_bytes()); + let hook = lib.get_symbol:: SystemConfigs>(exported_function_name.as_bytes()); if let Ok(hook_fn) = hook { return Some(hook_fn(view_name.to_string())); } @@ -309,7 +309,7 @@ impl BevyPlugin { for (lib_name, lib) in &client.libs { unsafe { let function_name = format!("get_system_{}", lib_name).to_string(); - let hook = lib.get_symbol:: Option>(function_name.as_bytes()); + let hook = lib.get_symbol:: Option>(function_name.as_bytes()); if let Ok(hook_fn) = hook { let desc = hook_fn(name.to_string(), view_name.to_string()); if desc.is_some() { @@ -327,7 +327,7 @@ impl BevyPlugin { for (lib_name, lib) in &client.libs { unsafe { let function_name = format!("get_demos_{}", lib_name).to_string(); - let list = lib.get_symbol:: Vec>(function_name.as_bytes()); + let list = lib.get_symbol:: Vec>(function_name.as_bytes()); if let Ok(list_fn) = list { let mut lib_demos = list_fn(); demos.append(&mut lib_demos); @@ -352,7 +352,7 @@ impl BevyPlugin { for (_, lib) in &client.libs { unsafe { let function_name = format!("{}", self.session_info.active_demo).to_string(); - let demo = lib.get_symbol:: ScheduleInfo>(function_name.as_bytes()); + let demo = lib.get_symbol:: ScheduleInfo>(function_name.as_bytes()); if let Ok(demo_fn) = demo { return Some(demo_fn(client)); } @@ -364,24 +364,21 @@ impl BevyPlugin { } /// If we change demo or need to rebuild render graphs we need to invoke this, code changes will already invoke setup - fn resetup(&mut self, mut client: PlatformClient) -> PlatformClient { + fn resetup(&mut self, client: &mut PlatformClient) { // serialize client.serialise_plugin_data("ecs", &self.session_info); // unload / setup client.swap_chain.wait_for_last_frame(); - client = self.unload(client); + self.unload(client); client.pmfx.unload_views(); self.setup(client) } /// Custom function to handle custome data change events which can trigger resetup - fn check_for_changes(&mut self, client: PlatformClient) -> PlatformClient { + fn check_for_changes(&mut self, client: &mut PlatformClient) { // render graph itself has chaned if self.render_graph_hash != client.pmfx.get_render_graph_hash(&self.schedule_info.render_graph) { - self.resetup(client) - } - else { - client + self.resetup(client); } } @@ -403,7 +400,7 @@ impl BevyPlugin { } } - fn schedule_ui(&mut self, mut client: PlatformClient) -> PlatformClient { + fn schedule_ui(&mut self, client: &mut PlatformClient) { let error_col = vec4f(1.0, 0.0, 0.3, 1.0); let warning_col = vec4f(1.0, 7.0, 0.0, 1.0); let default_col = vec4f(1.0, 1.0, 1.0, 1.0); @@ -472,7 +469,6 @@ impl BevyPlugin { } } } - client } fn setup_camera(&mut self) -> (Camera, Mat4f, Position) { @@ -520,14 +516,14 @@ impl Plugin for BevyPlugin { } } - fn setup(&mut self, mut client: PlatformClient) -> PlatformClient { + fn setup(&mut self, client: &mut PlatformClient) { // clear errors self.errors = HashMap::new(); self.session_info = client.deserialise_plugin_data("ecs"); // dynamically change demos and lookup infos in other libs - let schedule_info = self.get_demo_schedule_info(&mut client); + let schedule_info = self.get_demo_schedule_info(client); // get schedule or use default and warn the user self.schedule_info = if let Some(info) = schedule_info { @@ -607,14 +603,13 @@ impl Plugin for BevyPlugin { // we defer the actual setup system calls until the update where resources will be inserted into the world self.run_setup = true; - client } fn update(&mut self, mut client: PlatformClient) -> PlatformClient { let session_info = self.session_info.clone(); // check for any changes - client = self.check_for_changes(client); + self.check_for_changes(&mut client); // clear pmfx view errors before we render client.pmfx.view_errors.lock().unwrap().clear(); @@ -662,15 +657,14 @@ impl Plugin for BevyPlugin { client } - fn unload(&mut self, client: PlatformClient) -> PlatformClient { + fn unload(&mut self, _client: &mut PlatformClient) { // drop everything while its safe self.setup_schedule = Schedule::default(); self.schedule = Schedule::default(); self.world = World::default(); - client } - fn ui(&mut self, mut client: PlatformClient) -> PlatformClient { + fn ui(&mut self, client: &mut PlatformClient) { let mut open = true; let mut resetup = false; if client.imgui.begin("ecs", &mut open, imgui::WindowFlags::NONE) { @@ -790,7 +784,7 @@ impl Plugin for BevyPlugin { } } - client = self.schedule_ui(client); + self.schedule_ui(client); } // preform any re-setup actions @@ -801,11 +795,10 @@ impl Plugin for BevyPlugin { self.session_info.main_camera = Some(default_cameras[&self.session_info.active_demo]); } } - client = self.resetup(client); + self.resetup(client); } client.imgui.end(); - client } } diff --git a/plugins/ecs_examples/Cargo.toml b/plugins/ecs_examples/Cargo.toml index b230f78a..cb0e9f9a 100644 --- a/plugins/ecs_examples/Cargo.toml +++ b/plugins/ecs_examples/Cargo.toml @@ -7,9 +7,9 @@ edition = "2021" crate-type = ["rlib", "dylib"] [dependencies] -hotline-rs = { path = "../.." } +hotline-rs.workspace = true export_macros = { path = "../export_macros" } -maths-rs = "0.2.3" -bevy_ecs = "0.15.0" +maths-rs.workspace = true +bevy_ecs.workspace = true rand = "0.8.5" -bitflags = "1.3.2" \ No newline at end of file +bitflags.workspace = true diff --git a/plugins/ecs_examples/src/claude.rs b/plugins/ecs_examples/src/claude.rs new file mode 100644 index 00000000..40b92e5d --- /dev/null +++ b/plugins/ecs_examples/src/claude.rs @@ -0,0 +1,233 @@ +// currently windows only because here we need a concrete gfx and os implementation +#![cfg(target_os = "windows")] + +use crate::prelude::*; + +/// +/// Claude - Spiral Galaxy +/// + +/// galaxy disk normal — tilted diagonal +fn galaxy_axis() -> Vec3f { + normalize(vec3f(1.0, 2.0, 1.0)) +} + +/// rotate point p around an arbitrary axis by angle (Rodrigues' formula) +fn rotate_around_axis(p: Vec3f, axis: Vec3f, angle: f32) -> Vec3f { + let c = cos(angle); + let s = sin(angle); + p * c + cross(axis, p) * s + axis * (p.x * axis.x + p.y * axis.y + p.z * axis.z) * (1.0 - c) +} + +#[no_mangle] +pub fn claude(client: &mut Client) -> ScheduleInfo { + client.pmfx.load(hotline_rs::get_data_path("shaders/ecs_examples").as_str()).unwrap(); + ScheduleInfo { + setup: systems![ + "setup_claude" + ], + update: systems![ + "update_claude", + "batch_lights" + ], + render_graph: "mesh_lit_dark" + } +} + +#[no_mangle] +#[export_update_fn] +pub fn setup_claude( + mut device: ResMut, + mut pmfx: ResMut, + mut commands: Commands) -> Result<(), hotline_rs::Error> { + + let sphere = hotline_rs::primitives::create_sphere_mesh(&mut device.0, 16); + let torus = hotline_rs::primitives::create_tourus_mesh(&mut device.0, 16); + + let axis = galaxy_axis(); + let num_arms = 4; + let particles_per_arm = 150; + let mut rng = rand::thread_rng(); + + for arm in 0..num_arms { + let arm_offset = f32::pi() * 2.0 * arm as f32 / num_arms as f32; + + for i in 0..particles_per_arm { + let t = i as f32 / particles_per_arm as f32; + + // logarithmic spiral in flat XZ + let theta = arm_offset + t * f32::pi() * 4.0; + let r = 20.0 + t * 400.0; + + let scatter_r = rng.gen::() * 20.0 * (1.0 + t); + let scatter_angle = rng.gen::() * f32::pi() * 2.0; + let scatter_y = (rng.gen::() - 0.5) * 10.0 * (1.0 - t * 0.5); + + let flat_x = r * cos(theta) + scatter_r * cos(scatter_angle); + let flat_z = r * sin(theta) + scatter_r * sin(scatter_angle); + let flat_y = scatter_y; + + // tilt from Y-up to galaxy axis + let flat_pos = vec3f(flat_x, flat_y, flat_z); + let pos = rotate_around_axis(flat_pos, normalize(cross(Vec3f::unit_y(), axis)), acos(axis.y)); + + let scale = 1.0 + rng.gen::() * 3.0; + + let mesh = if rng.gen::() > 0.3 { + sphere.clone() + } else { + torus.clone() + }; + + commands.spawn(( + MeshComponent(mesh), + Position(pos), + Rotation(Quatf::from_euler_angles( + rng.gen::() * f32::pi() * 2.0, + rng.gen::() * f32::pi() * 2.0, + rng.gen::() * f32::pi() * 2.0 + )), + Scale(splat3f(scale)), + WorldMatrix(Mat34f::identity()) + )); + } + } + + // dense central cluster + for _ in 0..50 { + let r = rng.gen::() * 30.0; + let angle = rng.gen::() * f32::pi() * 2.0; + let y = (rng.gen::() - 0.5) * 15.0; + let flat_pos = vec3f(r * cos(angle), y, r * sin(angle)); + let pos = rotate_around_axis(flat_pos, normalize(cross(Vec3f::unit_y(), axis)), acos(axis.y)); + let scale = 2.0 + rng.gen::() * 5.0; + + commands.spawn(( + MeshComponent(sphere.clone()), + Position(pos), + Rotation(Quatf::identity()), + Scale(splat3f(scale)), + WorldMatrix(Mat34f::identity()) + )); + } + + // --- Lighting --- + + // large warm point light at center to illuminate everything + commands.spawn(( + Position(vec3f(0.0, 50.0, 0.0)), + Colour(vec4f(1.0, 0.85, 0.6, 1.0) * 0.8), + LightComponent { + light_type: LightType::Point, + radius: 800.0, + ..Default::default() + } + )); + + // 8 spot lights that will orbit through the galaxy + let num_spots = 8; + for i in 0..num_spots { + let phase = f32::pi() * 2.0 * i as f32 / num_spots as f32; + let orbit_r = 200.0; + let flat_pos = vec3f(orbit_r * cos(phase), 30.0, orbit_r * sin(phase)); + let pos = rotate_around_axis(flat_pos, normalize(cross(Vec3f::unit_y(), axis)), acos(axis.y)); + + // cycle through warm/cool spot colors + let col = match i % 4 { + 0 => vec4f(1.0, 0.6, 0.3, 1.0), + 1 => vec4f(0.5, 0.7, 1.0, 1.0), + 2 => vec4f(1.0, 0.4, 0.6, 1.0), + _ => vec4f(0.6, 1.0, 0.8, 1.0), + }; + + commands.spawn(( + Position(pos), + Colour(col * 0.5), + LightComponent { + light_type: LightType::Spot, + radius: 400.0, + ..Default::default() + } + )); + } + + pmfx.reserve_world_buffers(&mut device, WorldBufferReserveInfo { + point_light_capacity: 1, + spot_light_capacity: num_spots, + ..Default::default() + }); + + Ok(()) +} + +#[no_mangle] +#[export_update_fn] +pub fn update_claude( + time: Res, + mut mesh_query: Query<(&mut Position, &mut Rotation), Without>, + mut light_query: Query<&mut Position, With>) -> Result<(), hotline_rs::Error> { + + let dt = time.0.delta; + let t = time.accumulated; + let axis = galaxy_axis(); + + // orbit galaxy meshes + for (mut position, mut rotation) in &mut mesh_query { + let p = position.0; + + // distance from the galaxy axis (perpendicular distance) + let along = p.x * axis.x + p.y * axis.y + p.z * axis.z; + let perp = p - axis * along; + let dist = mag(perp).max(1.0); + + // preserve radius to prevent numerical drift + let radius = dist; + + // gentle Keplerian orbit around the tilted axis + let angular_vel = 0.5 / sqrt(dist); + let angle = angular_vel * dt; + + // rotate around galaxy axis + let rotated = rotate_around_axis(p, axis, angle); + + // restore perpendicular distance to prevent drift + let new_along = rotated.x * axis.x + rotated.y * axis.y + rotated.z * axis.z; + let new_perp = rotated - axis * new_along; + let new_dist = mag(new_perp).max(0.001); + let corrected = axis * new_along + new_perp * (radius / new_dist); + position.0 = corrected; + + // gentle tumble + rotation.0 *= Quat::from_euler_angles(dt * 0.05, dt * 0.08, dt * 0.03); + rotation.0 = Quatf::normalize(rotation.0); + } + + // animate spot lights orbiting through the galaxy + let tilt_axis = normalize(cross(Vec3f::unit_y(), axis)); + let tilt_angle = acos(axis.y); + let mut i = 0; + for mut position in &mut light_query { + // skip the center point light (first one spawned) + if i == 0 { + i += 1; + continue; + } + + let si = (i - 1) as f32; + let phase = f32::pi() * 2.0 * si / 8.0; + let orbit_r = 150.0 + 100.0 * sin(t * 0.3 + phase); + let orbit_angle = t * 0.4 + phase; + let bob_y = 30.0 + 20.0 * sin(t * 0.5 + si); + + let flat_pos = vec3f( + orbit_r * cos(orbit_angle), + bob_y, + orbit_r * sin(orbit_angle) + ); + position.0 = rotate_around_axis(flat_pos, tilt_axis, tilt_angle); + + i += 1; + } + + Ok(()) +} diff --git a/plugins/ecs_examples/src/gpu_frustum_culling.rs b/plugins/ecs_examples/src/gpu_frustum_culling.rs index ed885c56..ee700d27 100644 --- a/plugins/ecs_examples/src/gpu_frustum_culling.rs +++ b/plugins/ecs_examples/src/gpu_frustum_culling.rs @@ -8,6 +8,7 @@ use crate::prelude::*; /// #[repr(packed)] +#[allow(dead_code)] pub struct DrawIndirectArgs { pub vertex_buffer: gfx::VertexBufferView, pub index_buffer: gfx::IndexBufferView, diff --git a/plugins/ecs_examples/src/lib.rs b/plugins/ecs_examples/src/lib.rs index 34aee2f3..2f60c036 100644 --- a/plugins/ecs_examples/src/lib.rs +++ b/plugins/ecs_examples/src/lib.rs @@ -31,6 +31,7 @@ mod dynamic_cubemap; mod pbr; mod raytracing_pipeline; mod raytraced_shadows; +mod claude; use prelude::*; use hotline_rs::gfx::{RaytracingTLAS}; @@ -630,6 +631,7 @@ pub fn setup_tlas( } #[export_compute_fn] +#[allow(unused_variables)] pub fn update_tlas( mut device: ResMut, pmfx: &Res, @@ -699,7 +701,8 @@ pub fn get_demos_ecs_examples() -> Vec { "omni_shadow_map", "pbr", "raytracing_pipeline", - "raytraced_shadows" + "raytraced_shadows", + "claude" ] } diff --git a/plugins/ecs_examples/src/raytraced_shadows.rs b/plugins/ecs_examples/src/raytraced_shadows.rs index ecd297e1..54a74a1c 100644 --- a/plugins/ecs_examples/src/raytraced_shadows.rs +++ b/plugins/ecs_examples/src/raytraced_shadows.rs @@ -33,7 +33,6 @@ pub fn setup_raytraced_shadows_scene( mut commands: Commands) -> Result<(), hotline_rs::Error> { let cube_mesh = hotline_rs::primitives::create_cube_mesh(&mut device.0); - let sphere_mesh = hotline_rs::primitives::create_sphere_mesh(&mut device.0, 32); let dodeca_mesh = hotline_rs::primitives::create_dodecahedron_mesh(&mut device.0); let teapot_mesh = hotline_rs::primitives::create_teapot_mesh(&mut device.0, 32); let tube_mesh = hotline_rs::primitives::create_tube_prism_mesh(&mut device.0, 5, 0, 4, false, true, 0.33, 0.33, 1.0); @@ -42,7 +41,6 @@ pub fn setup_raytraced_shadows_scene( let bounds = 100.0; // point light - let light_bounds = bounds * 0.75; let light_pos = vec3f(100.0, 0.0, 100.0); let light_radius = 256.0; commands.spawn(( @@ -71,7 +69,7 @@ pub fn setup_raytraced_shadows_scene( let shape_size = bounds * 0.1; // dodeca - let dodeca_blas = commands.spawn(( + let _dodeca_blas = commands.spawn(( Position(vec3f(shape_bounds * -0.75, shape_bounds * 0.7, -shape_bounds * 0.1)), Scale(splat3f(shape_size * 2.0)), Rotation(Quatf::identity()), diff --git a/plugins/ecs_examples/src/raytracing_pipeline.rs b/plugins/ecs_examples/src/raytracing_pipeline.rs index 176debf7..db768e6e 100644 --- a/plugins/ecs_examples/src/raytracing_pipeline.rs +++ b/plugins/ecs_examples/src/raytracing_pipeline.rs @@ -77,7 +77,7 @@ pub fn setup_raytracing_pipeline_scene( let mut instance_geometry_lookup = Vec::new(); - let dodeca_blas = commands.spawn(( + let _dodeca_blas = commands.spawn(( Position(vec3f(shape_bounds * -0.75, shape_bounds * 0.7, -shape_bounds * 0.1)), Scale(splat3f(shape_size * 2.0)), Rotation(Quatf::identity()), @@ -198,7 +198,7 @@ pub fn setup_raytracing_pipeline_scene( #[export_compute_fn] pub fn render_meshes_raytraced( - device: ResMut, + _device: ResMut, pmfx: &Res, pass: &pmfx::ComputePass, cmd_buf: &mut ::CmdBuf, @@ -206,10 +206,10 @@ pub fn render_meshes_raytraced( ) -> Result<(), hotline_rs::Error> { let pmfx = &pmfx.0; - let mut heap = pmfx.shader_heap.clone(); + let _heap = pmfx.shader_heap.clone(); let output_size = pmfx.get_texture_2d_size("staging_output").expect("expected staging_output"); - let output_tex = pmfx.get_texture("staging_output").expect("expected staging_output"); + let _output_tex = pmfx.get_texture("staging_output").expect("expected staging_output"); let camera = pmfx.get_camera_constants("main_camera"); if let Ok(camera) = camera { diff --git a/plugins/empty/Cargo.toml b/plugins/empty/Cargo.toml index f59cd172..661de298 100644 --- a/plugins/empty/Cargo.toml +++ b/plugins/empty/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["rlib", "dylib"] [dependencies] -hotline-rs = { path = "../.." } \ No newline at end of file +hotline-rs.workspace = true diff --git a/plugins/empty/src/lib.rs b/plugins/empty/src/lib.rs index b75ec165..a4954ee3 100644 --- a/plugins/empty/src/lib.rs +++ b/plugins/empty/src/lib.rs @@ -11,10 +11,8 @@ impl Plugin for EmptyPlugin { } } - fn setup(&mut self, client: Client) - -> Client { + fn setup(&mut self, _client: &mut Client) { println!("plugin setup"); - client } fn update(&mut self, client: client::Client) @@ -23,16 +21,12 @@ impl Plugin for EmptyPlugin { client } - fn unload(&mut self, client: Client) - -> Client { + fn unload(&mut self, _client: &mut Client) { println!("plugin unload"); - client } - fn ui(&mut self, client: Client) - -> Client { + fn ui(&mut self, _client: &mut Client) { println!("plugin ui"); - client } } diff --git a/readme.md b/readme.md index 869463ee..726c0d3d 100644 --- a/readme.md +++ b/readme.md @@ -54,14 +54,14 @@ git submodule update --init --recursive You can run the binary `client` which allows code to be reloaded through `plugins`. There are some [plugins](https://github.com/polymonster/hotline/tree/master/plugins) already provided with the repository: ```text -// build the hotline library and the client, fetch the hotline-data repository +// build the hotline library and client cargo build // build the data hotline-data\pmbuild win32-data -// then build plugins -cargo build --manifest-path plugins/Cargo.toml +// build plugins +cargo build -p ecs -p ecs_examples // run the client cargo run client diff --git a/shaders/draw.hlsl b/shaders/draw.hlsl index ebb9768b..19feca05 100644 --- a/shaders/draw.hlsl +++ b/shaders/draw.hlsl @@ -31,12 +31,14 @@ vs_output vs_mesh(vs_input_mesh input) { float4 pos = float4(input.position.xyz, 1.0); pos.xyz = mul(world_matrix, pos); + float3x3 rot = (float3x3)world_matrix; + vs_output output; output.position = mul(view_projection_matrix, pos); output.world_pos = pos; output.texcoord = float4(input.texcoord, 0.0, 0.0); output.colour = material_colour; - output.normal = input.normal.xyz; + output.normal = normalize(mul(rot, input.normal.xyz)); return output; } diff --git a/shaders/material.hlsl b/shaders/material.hlsl index d817db4e..1ca97753 100644 --- a/shaders/material.hlsl +++ b/shaders/material.hlsl @@ -166,12 +166,13 @@ ps_output ps_mesh_lit(vs_output input) { int i = 0; float ks = 2.0; float shininess = 32.0; - float roughness = 0.1; + float roughness = 0.7; float k = 0.3; float3 v = normalize(input.world_pos.xyz - view_position.xyz); float3 n = input.normal; + // point lights uint point_lights_id = world_buffer_info.point_light.x; uint point_lights_count = world_buffer_info.point_light.y; diff --git a/shaders/material.jsn b/shaders/material.jsn index 2f559163..1b763bd9 100644 --- a/shaders/material.jsn +++ b/shaders/material.jsn @@ -150,6 +150,19 @@ depends_on: ["debug"] } } + mesh_lit_dark: { + debug: { + view: "main_view_black" + pipelines: ["imdraw_3d"] + function: "render_debug" + } + meshes: { + view: "main_view_black" + pipelines: ["mesh_lit"] + function: "render_meshes_bindless" + depends_on: ["debug"] + } + } mesh_lit_rt_shadow2: { debug: { diff --git a/src/client.rs b/src/client.rs index 75c0fd50..317712e1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -127,6 +127,7 @@ impl Default for HotlineInfo { } /// Hotline client data members +#[repr(C)] pub struct Client { pub app: A, pub device: D, @@ -507,7 +508,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx // execute the main window command buffer + swap self.device.execute(&self.cmd_buf); - self.swap_chain.swap(&self.device); + self.swap_chain.swap(&mut self.device); } /// This assumes you pass the path to a `Cargo.toml` for a `dylib` which you want to load dynamically @@ -515,7 +516,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx /// You can also just load libs and use `lib.get_symbol` to find custom callable code for other plugins. pub fn add_plugin_lib(&mut self, name: &str, path: &str) { let abs_path = if path == "/plugins" { - super::get_data_path("../../plugins") + super::get_data_path("../..") } else { String::from(path) @@ -527,6 +528,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx .to_str().unwrap().to_string(); let src_path = PathBuf::from(abs_path.to_string()) + .join("plugins") .join(name) .join("src") .join("lib.rs") @@ -550,7 +552,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx let lib = hot_lib_reloader::LibReloader::new(&lib_path, name, None).unwrap(); unsafe { // create instance if it is a Plugin trait - let create = lib.get_symbol:: *mut core::ffi::c_void>("create".as_bytes()); + let create = lib.get_symbol:: *mut core::ffi::c_void>("create".as_bytes()); let instance = if let Ok(create) = create { // create function returns pointer to instance @@ -747,10 +749,10 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx for plugin in &mut plugins { let lib = self.libs.get(&plugin.name).expect("hotline::client: lib missing for plugin"); unsafe { - let ui = lib.get_symbol:: Self>("ui".as_bytes()); + let ui = lib.get_symbol::("ui".as_bytes()); if let Ok(ui_fn) = ui { let imgui_ctx = self.imgui.get_current_context(); - self = ui_fn(self, plugin.instance, imgui_ctx); + ui_fn(&mut self, plugin.instance, imgui_ctx); } } } @@ -781,9 +783,9 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx if plugin.state != PluginState::None { unsafe { let lib = self.libs.get(&plugin.name).expect("hotline::client: lib missing for plugin"); - let unload = lib.get_symbol:: Self>("unload".as_bytes()); + let unload = lib.get_symbol::("unload".as_bytes()); if let Ok(unload_fn) = unload { - self = unload_fn(self, plugin.instance); + unload_fn(&mut self, plugin.instance); } } } @@ -830,7 +832,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx // create a new instance of the plugin unsafe { - let create = lib.get_symbol:: *mut core::ffi::c_void>("create".as_bytes()); + let create = lib.get_symbol:: *mut core::ffi::c_void>("create".as_bytes()); if let Ok(create_fn) = create { plugin.instance = create_fn(); } @@ -845,9 +847,9 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx let lib = self.libs.get(&plugin.name).expect("hotline::client: lib missing for plugin"); unsafe { if plugin.state == PluginState::Setup { - let setup = lib.get_symbol:: Self>("setup".as_bytes()); + let setup = lib.get_symbol::("setup".as_bytes()); if let Ok(setup_fn) = setup { - self = setup_fn(self, plugin.instance); + setup_fn(&mut self, plugin.instance); } } } @@ -858,9 +860,9 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx for plugin in &mut plugins { let lib = self.libs.get(&plugin.name).expect("hotline::client: lib missing for plugin"); unsafe { - let update = lib.get_symbol:: Self>("update".as_bytes()); + let update = lib.get_symbol::("update".as_bytes()); if let Ok(update_fn) = update { - self = update_fn(self, plugin.instance); + update_fn(&mut self, plugin.instance); } } plugin.state = PluginState::None; @@ -878,9 +880,9 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx for plugin in &plugins { unsafe { let lib = self.libs.get(&plugin.name).expect("hotline::client: lib missing for plugin"); - let unload = lib.get_symbol:: Self>("unload".as_bytes()); + let unload = lib.get_symbol::("unload".as_bytes()); if let Ok(unload_fn) = unload { - self = unload_fn(self, plugin.instance); + unload_fn(&mut self, plugin.instance); } } } @@ -1032,7 +1034,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx // execute the main window command buffer + swap self.device.execute(&self.cmd_buf); - self.swap_chain.swap(&self.device); + self.swap_chain.swap(&mut self.device); self.swap_chain.wait_for_last_frame(); diff --git a/src/gfx.rs b/src/gfx.rs index e66762c6..2cccef57 100644 --- a/src/gfx.rs +++ b/src/gfx.rs @@ -9,12 +9,146 @@ use crate::os; use std::any::Any; use serde::{Deserialize, Serialize}; use std::hash::Hash; +use std::sync::{Arc, Mutex}; use maths_rs::max; use null::*; type Error = super::Error; +// +// Common types for deferred resource cleanup +// + +/// A thread-safe free list for tracking available heap slot indices +pub struct FreeList { + pub list: Mutex> +} + +/// Thread safe ref counted free-list that can be safely used in drop traits +pub type FreeListRef = Arc; + +impl FreeList { + /// Create a new empty free list + pub fn new() -> FreeListRef { + Arc::new(FreeList { + list: Mutex::new(Vec::new()) + }) + } + + /// Push an index back to the free list for reuse + pub fn push(&self, index: usize) { + let mut list = self.list.lock().unwrap(); + list.push(index); + } + + /// Pop an index from the free list, returns None if empty + pub fn pop(&self) -> Option { + let mut list = self.list.lock().unwrap(); + list.pop() + } + + /// Check if the free list is empty + pub fn is_empty(&self) -> bool { + let list = self.list.lock().unwrap(); + list.is_empty() + } +} + +impl Default for FreeList { + fn default() -> Self { + FreeList { + list: Mutex::new(Vec::new()) + } + } +} + +/// Structure to track resources and resource view allocations in `Drop` traits. +/// Generic over the resource type R (e.g. ID3D12Resource for D3D12) +pub struct DropResource { + /// Resources to be dropped after waiting for GPU completion + pub resources: Vec, + /// Frame number when the resource was dropped (0 = not yet initialised) + pub frame: usize, + /// Heap allocation indices to return to the free list + pub heap_allocs: Vec, +} + +impl DropResource { + /// Create a new drop resource entry + pub fn new(resources: Vec, heap_allocs: Vec) -> Self { + DropResource { + resources, + frame: 0, + heap_allocs, + } + } +} + +/// A thread-safe drop list for deferred resource cleanup. +/// Generic over the resource type R. +pub struct DropList { + pub list: Mutex>> +} + +/// Thread safe ref counted drop-list that can be safely used in drop traits, +/// tracks the frame a resource was dropped on so it can be waited on +pub type DropListRef = Arc>; + +impl DropList { + /// Create a new empty drop list + pub fn new() -> DropListRef { + Arc::new(DropList { + list: Mutex::new(Vec::new()) + }) + } + + /// Push a resource to the drop list for deferred cleanup + pub fn push(&self, resource: DropResource) { + let mut list = self.list.lock().unwrap(); + list.push(resource); + } + + /// Cleanup resources that have been waiting long enough. + /// Returns heap allocations to the free list when resources have waited more than `num_bb` frames. + pub fn cleanup(&self, current_frame: usize, num_bb: usize, free_list: &FreeListRef) { + let mut drop_list = self.list.lock().unwrap(); + let mut complete_indices = Vec::new(); + + for (res_index, drop_res) in drop_list.iter_mut().enumerate() { + // initialise the frame, and then wait + if drop_res.frame == 0 { + drop_res.frame = current_frame; + } else { + let diff = current_frame.saturating_sub(drop_res.frame); + if diff > num_bb { + // waited long enough — add heap allocations to free list + for alloc in &drop_res.heap_allocs { + free_list.push(*alloc); + } + drop_res.resources.clear(); + drop_res.heap_allocs.clear(); + complete_indices.push(res_index); + } + } + } + + // remove complete items in reverse order + complete_indices.reverse(); + for i in complete_indices { + drop_list.remove(i); + } + } +} + +impl Default for DropList { + fn default() -> Self { + DropList { + list: Mutex::new(Vec::new()) + } + } +} + /// Macro to pass data!\[expression\] or data!\[\] (None) to a create function, so you don't have to deduce a 'T'. #[macro_export] macro_rules! data { @@ -89,7 +223,7 @@ pub struct ScissorRect { /// u = unsigned integer, /// i = signed integer, /// f = float -#[derive(Copy, Clone, Serialize, Deserialize, Hash, PartialEq)] +#[derive(Copy, Clone, Serialize, Deserialize, Hash, PartialEq, Debug)] pub enum Format { Unknown, R16n, @@ -454,7 +588,7 @@ pub enum DescriptorType { } /// Describes the visibility of which shader stages can access a descriptor. -#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default, Debug)] pub enum ShaderVisibility { #[default] All, @@ -524,7 +658,7 @@ pub struct InputElementInfo { } /// Describes the frequency of which elements are fetched from a vertex input element. -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Copy, Serialize, Deserialize)] pub enum InputSlotClass { PerVertex, PerInstance, @@ -939,6 +1073,7 @@ pub struct ClearColour { } /// Values to clear depth stencil buffers during a `RenderPass` +#[derive(Copy, Clone)] pub struct ClearDepthStencil { /// Clear value for the depth buffer. Use `None` to preserve existing contents. pub depth: Option, @@ -1338,7 +1473,7 @@ pub trait Device: 'static + Send + Sync + Sized + Any + Clone { pipeline: Option<&Self::RenderPipeline> ) -> Result; /// Execute a command buffer on the internal device command queue which still hold references - fn execute(&self, cmd: &Self::CmdBuf); + fn execute(&mut self, cmd: &Self::CmdBuf); /// Borrow the internally managed shader resource heap the device creates, for binding buffers / textures in shaders fn get_shader_heap(&self) -> &Self::Heap; /// Mutably borrow the internally managed shader resource heap the device creates, for binding buffers / textures in shaders @@ -1400,7 +1535,7 @@ pub trait SwapChain: 'static + Sized + Any + Send + Sync + Clone { /// Returns the current backbuffer pass without a clear mutably fn get_backbuffer_pass_no_clear_mut(&mut self) -> &mut D::RenderPass; /// Call swap at the end of the frame to swap the back buffer, we rotate through n-buffers - fn swap(&mut self, device: &D); + fn swap(&mut self, device: &mut D); } /// Responsible for buffering graphics commands. Internally it will contain a platform specific @@ -1754,6 +1889,16 @@ pub const fn components_for_format(format: Format) -> u32 { } } +/// Returns true if `format` is a depth texture format +pub fn is_depth_format(format: Format) -> bool { + match format { + Format::D32f => true, + Format::D24nS8u => true, + Format::D16n => true, + _ => false + } +} + /// Returns the row pitch of an image in bytes: width * block size pub fn row_pitch_for_format(format: Format, width: u64) -> u64 { let tpb = texels_per_block_for_format(format); diff --git a/src/gfx/d3d12.rs b/src/gfx/d3d12.rs index 5b0b0027..cfbc2e1e 100644 --- a/src/gfx/d3d12.rs +++ b/src/gfx/d3d12.rs @@ -20,7 +20,6 @@ use std::collections::{HashMap, hash_map::DefaultHasher}; use std::ffi::{CStr, CString, c_void}; use std::result; use std::str; -use std::sync::Mutex; use windows::{ core::*, Win32::Foundation::*, Win32::Graphics::Direct3D::Fxc::*, Win32::Graphics::Direct3D::*, @@ -37,9 +36,9 @@ macro_rules! d3d12_debug_name { } } -type BeginEventOnCommandList = extern "stdcall" fn(*const core::ffi::c_void, u64, PSTR) -> i32; -type EndEventOnCommandList = extern "stdcall" fn(*const core::ffi::c_void) -> i32; -type SetMarkerOnCommandList = extern "stdcall" fn(*const core::ffi::c_void, u64, PSTR) -> i32; +type BeginEventOnCommandList = extern "system" fn(*const core::ffi::c_void, u64, PSTR) -> i32; +type EndEventOnCommandList = extern "system" fn(*const core::ffi::c_void) -> i32; +type SetMarkerOnCommandList = extern "system" fn(*const core::ffi::c_void, u64, PSTR) -> i32; #[derive(Copy, Clone)] struct WinPixEventRuntime { @@ -196,7 +195,7 @@ pub struct Buffer { cbv_index: Option, uav_index: Option, counter_offset: Option, - drop_list: Option, + drop_list: Option, persistent_mapped_data: *mut c_void } @@ -210,7 +209,7 @@ pub struct Shader { pub struct TextureTarget { ptr: D3D12_CPU_DESCRIPTOR_HANDLE, index: usize, - drop_list: DropListRef, + drop_list: D3d12DropListRef, } #[derive(Clone)] @@ -226,7 +225,7 @@ pub struct Texture { subresource_uav_index: Vec, shared_handle: Option, // drop list for srv, uav and resolved srv - drop_list: Option, + drop_list: Option, // the id of the shader heap for (uav, srv etc) shader_heap_id: Option } @@ -266,44 +265,8 @@ struct RootSignatureLookup { descriptor_slots: Vec } -/// A free list thread safe with mutex -struct FreeList { - list: Mutex> -} - -/// Thread safe ref counted free-list that can be safely used in drop traits -type FreeListRef = std::sync::Arc; - -impl FreeList { - fn new() -> std::sync::Arc { - std::sync::Arc::new(FreeList { - list: Mutex::new(Vec::new()) - }) - } -} - -/// Structure to track resources and resoure view allocations in `Drop` traits -struct DropResource { - resources: Vec, - frame: usize, - heap_allocs: Vec -} - -struct DropList { - list: Mutex> -} - -/// Thread safe ref counted drop-list that can be safely used in drop traits, -/// tracks the frame a resource was dropped on so it can be waited on -type DropListRef = std::sync::Arc; - -impl DropList { - fn new() -> std::sync::Arc { - std::sync::Arc::new(DropList { - list: Mutex::new(Vec::new()) - }) - } -} +/// D3D12-specific type alias for drop lists tracking ID3D12Resource +type D3d12DropListRef = DropListRef; #[derive(Clone)] pub struct Heap { @@ -313,7 +276,7 @@ pub struct Heap { capacity: usize, offset: usize, free_list: FreeListRef, - drop_list: DropListRef, + drop_list: D3d12DropListRef, id: u16 } @@ -3762,7 +3725,7 @@ impl super::Device for Device { }) } - fn execute(&self, cmd: &CmdBuf) { + fn execute(&mut self, cmd: &CmdBuf) { unsafe { let command_list = Some(cmd.command_list[cmd.bb_index].cast().unwrap()); self.command_queue.ExecuteCommandLists(&[command_list]); @@ -4049,7 +4012,7 @@ impl super::SwapChain for SwapChain { &mut self.backbuffer_passes_no_clear[self.bb_index] } - fn swap(&mut self, device: &Device) { + fn swap(&mut self, device: &mut Device) { unsafe { // present let hr = self.swap_chain.Present(1, DXGI_PRESENT::default()); diff --git a/src/gfx/mtl.rs b/src/gfx/mtl.rs index c3ab3da5..767da864 100644 --- a/src/gfx/mtl.rs +++ b/src/gfx/mtl.rs @@ -162,7 +162,7 @@ impl super::SwapChain for SwapChain { &mut self.backbuffer_pass_no_clear } - fn swap(&mut self, device: &Device) { + fn swap(&mut self, device: &mut Device) { objc::rc::autoreleasepool(|| { let cmd = device.command_queue.new_command_buffer(); cmd.present_drawable(&self.drawable); @@ -1476,7 +1476,7 @@ impl super::Device for Device { }) } - fn execute(&self, cmd: &CmdBuf) { + fn execute(&mut self, cmd: &CmdBuf) { } diff --git a/src/gfx/null.rs b/src/gfx/null.rs index 91a67772..4d296b5d 100644 --- a/src/gfx/null.rs +++ b/src/gfx/null.rs @@ -115,7 +115,7 @@ impl super::SwapChain for SwapChain { unimplemented!() } - fn swap(&mut self, device: &Device) { + fn swap(&mut self, device: &mut Device) { unimplemented!() } } @@ -475,7 +475,7 @@ impl super::Device for Device { unimplemented!() } - fn execute(&self, cmd: &Self::CmdBuf) { + fn execute(&mut self, cmd: &Self::CmdBuf) { unimplemented!() } diff --git a/src/lib.rs b/src/lib.rs index e3a551e7..46af43b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,6 +127,19 @@ pub fn get_exe_path(asset: &str) -> String { String::from(exe_path.join(asset).to_str().unwrap()) } +/// Read data wraps fs::read but provides better error messages appending the filepath +pub fn read_data>(filepath: P) -> Result, Error> { + let data = std::fs::read(&filepath); + if let Ok(data) = data { + Ok(data) + } + else { + Err(Error { + msg: format!("{:?} ({:?})", data, filepath.as_ref()) + }) + } +} + /// Recursivley get files from folder as a vector fn get_files_recursive(dir: &str, mut files: Vec) -> Vec { let paths = std::fs::read_dir(dir).unwrap(); diff --git a/src/plugin.rs b/src/plugin.rs index 514d0287..0c5d4ed9 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -26,13 +26,13 @@ pub trait Plugin { /// Create a new instance of the plugin fn create() -> Self where Self: Sized; /// Called when the plugin is loaded and after a reload has happened, setup resources and state in here - fn setup(&mut self, client: Client) -> Client; + fn setup(&mut self, client: &mut Client); /// Called each and every frame, here put your update and render logic fn update(&mut self, client: Client) -> Client; // Called where it is safe to make imgui calls - fn ui(&mut self, client: Client) -> Client; + fn ui(&mut self, client: &mut Client); // Called when the plugin is to be unloaded, this will clean up - fn unload(&mut self, client: Client) -> Client; + fn unload(&mut self, client: &mut Client); } /// Utility function to build all plugins, this can be used to bootstrap them if they don't exist @@ -72,7 +72,7 @@ impl reloader::ReloadResponder for PluginReloadResponder { fn get_files(&self) -> Vec { // scan for new files so we can dd them and pickup changes // TODO; this could be more easily be configured in a plugin meta data file - let src_path = self.path.to_string() + "/" + &self.name.to_string() + "/src"; + let src_path = self.path.to_string() + "/plugins/" + &self.name.to_string() + "/src"; let src_files = super::get_files_recursive(&src_path, Vec::new()); let mut result = self.files.to_vec(); result.extend(src_files); @@ -141,7 +141,7 @@ macro_rules! hotline_plugin { /// Plugins are created on the heap and the instance is passed from the client to the plugin function calls // c-abi wrapper for `Plugin::create` #[no_mangle] - pub fn create() -> *mut core::ffi::c_void { + pub extern "C" fn create() -> *mut core::ffi::c_void { let plugin = $input::create(); let ptr = Box::into_raw(Box::new(plugin)); ptr.cast() @@ -149,42 +149,45 @@ macro_rules! hotline_plugin { // c-abi wrapper for `Plugin::update` #[no_mangle] - pub fn update(mut client: client::Client, ptr: *mut core::ffi::c_void) -> client::Client { + pub extern "C" fn update(client_ptr: *mut client::Client, ptr: *mut core::ffi::c_void) { unsafe { let plugin = ptr.cast::<$input>(); let plugin = plugin.as_mut().unwrap(); - plugin.update(client) + // take client on the stack to pass ownership (bevy ecs needs to move resources in/out of World) + let client: client::Client = std::ptr::read(client_ptr); + let client = plugin.update(client); + std::ptr::write(client_ptr, client); } } // c-abi wrapper for `Plugin::setup` #[no_mangle] - pub fn setup(mut client: client::Client, ptr: *mut core::ffi::c_void) -> client::Client { + pub extern "C" fn setup(client: *mut client::Client, ptr: *mut core::ffi::c_void) { unsafe { let plugin = ptr.cast::<$input>(); let plugin = plugin.as_mut().unwrap(); - plugin.setup(client) + plugin.setup(&mut (*client)); } } - // c-abi wrapper for `Plugin::reload` + // c-abi wrapper for `Plugin::unload` #[no_mangle] - pub fn unload(mut client: client::Client, ptr: *mut core::ffi::c_void) -> client::Client { + pub extern "C" fn unload(client: *mut client::Client, ptr: *mut core::ffi::c_void) { unsafe { let plugin = ptr.cast::<$input>(); let plugin = plugin.as_mut().unwrap(); - plugin.unload(client) + plugin.unload(&mut (*client)); } } - // c-abi wrapper for `Plugin::reload` + // c-abi wrapper for `Plugin::ui` #[no_mangle] - pub fn ui(mut client: client::Client, ptr: *mut core::ffi::c_void, imgui_ctx: *mut core::ffi::c_void) -> client::Client { + pub extern "C" fn ui(client: *mut client::Client, ptr: *mut core::ffi::c_void, imgui_ctx: *mut core::ffi::c_void) { unsafe { let plugin = ptr.cast::<$input>(); let plugin = plugin.as_mut().unwrap(); - client.imgui.set_current_context(imgui_ctx); - plugin.ui(client) + (*client).imgui.set_current_context(imgui_ctx); + plugin.ui(&mut (*client)); } } } diff --git a/tests/tests.rs b/tests/tests.rs index c725313d..305f1ea9 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -143,7 +143,7 @@ fn swap_chain_buffer() -> Result<(), hotline_rs::Error> { cmd.close()?; dev.execute(&cmd); - swap_chain.swap(&dev); + swap_chain.swap(&mut dev); std::thread::sleep(std::time::Duration::from_millis(60)); i = (i + 1) % clears_colours.len(); @@ -329,7 +329,7 @@ fn draw_triangle() -> Result<(), hotline_rs::Error> { device.execute(&cmd); // swap for the next frame - swap_chain.swap(&device); + swap_chain.swap(&mut device); break; }