Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
cbaedf8ae5
|
|||
|
3782c43737
|
|||
|
be266d5397
|
|||
|
bf46d27df6
|
|||
|
d0ddc92c6d
|
|||
|
7d424a4a50
|
|||
|
d564ec7a21
|
|||
|
05a77a7ec8
|
|||
|
a05113baa5
|
|||
|
9ad8a70ad0
|
|||
|
e890623f2e
|
|||
|
7d55e872e7
|
|||
|
1e1cbeb180
|
|||
|
e19c46d851
|
|||
|
59bb8eee12
|
36
Cargo.lock
generated
36
Cargo.lock
generated
@@ -1080,6 +1080,15 @@ dependencies = [
|
||||
"rand_core 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
@@ -1109,7 +1118,7 @@ checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"termcolor",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1120,7 +1129,7 @@ checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"termcolor",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1135,7 +1144,7 @@ version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2232,7 +2241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2919,7 +2928,7 @@ dependencies = [
|
||||
"log",
|
||||
"presser",
|
||||
"thiserror 2.0.18",
|
||||
"windows 0.61.3",
|
||||
"windows 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5054,7 +5063,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.12.1",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5451,7 +5460,8 @@ name = "strafe-ai"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"burn",
|
||||
"png",
|
||||
"chrono",
|
||||
"glam",
|
||||
"pollster",
|
||||
"strafesnet_common",
|
||||
"strafesnet_graphics",
|
||||
@@ -5479,9 +5489,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strafesnet_graphics"
|
||||
version = "0.0.11-depth"
|
||||
version = "0.0.11-depth2"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "16266ca7e57ce802b7abd24c6cd8f9b8d95752f7eaead27e42b431b9768d6135"
|
||||
checksum = "829804ab9c167365e576de8ebd8a245ad979cb24558b086e693e840697d7956c"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"ddsfile",
|
||||
@@ -5516,9 +5526,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strafesnet_roblox_bot_player"
|
||||
version = "0.6.2-depth"
|
||||
version = "0.6.2-depth2"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "12d1aa21c174f23f7f7ede583292a8c82e4b3c483fb0d950e58f84d52807f6ed"
|
||||
checksum = "f39e7dfc0cb23e482089dc7eac235ad4b274ccfdb8df7617889a90e64a1e247a"
|
||||
dependencies = [
|
||||
"glam",
|
||||
"strafesnet_common",
|
||||
@@ -5730,7 +5740,7 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6989,7 +6999,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -5,13 +5,14 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
burn = { version = "0.20.1", features = ["cuda", "autodiff"] }
|
||||
glam = "0.32.1"
|
||||
pollster = "0.4.0"
|
||||
wgpu = "29.0.0"
|
||||
|
||||
strafesnet_common = { version = "0.9.0", registry = "strafesnet" }
|
||||
strafesnet_graphics = { version = "=0.0.11-depth", registry = "strafesnet" }
|
||||
strafesnet_graphics = { version = "=0.0.11-depth2", registry = "strafesnet" }
|
||||
strafesnet_physics = { version = "=0.0.2-surf", registry = "strafesnet" }
|
||||
strafesnet_roblox_bot_file = { version = "0.9.4", registry = "strafesnet" }
|
||||
strafesnet_roblox_bot_player = { version = "=0.6.2-depth", registry = "strafesnet" }
|
||||
strafesnet_roblox_bot_player = { version = "=0.6.2-depth2", registry = "strafesnet" }
|
||||
strafesnet_snf = { version = "0.4.0", registry = "strafesnet" }
|
||||
pollster = "0.4.0"
|
||||
png = "0.18.1"
|
||||
chrono = { version = "0.4.44", default-features = false, features = ["now"] }
|
||||
|
||||
572
src/main.rs
572
src/main.rs
@@ -1,43 +1,21 @@
|
||||
use burn::backend::Autodiff;
|
||||
use burn::nn::loss::{MseLoss, Reduction};
|
||||
use burn::nn::{Linear, LinearConfig, Relu};
|
||||
use burn::optim::{GradientsParams, Optimizer, SgdConfig};
|
||||
use burn::nn::{Dropout, DropoutConfig, Linear, LinearConfig, Relu};
|
||||
use burn::optim::{AdamConfig, GradientsParams, Optimizer};
|
||||
use burn::prelude::*;
|
||||
|
||||
type InferenceBackend = burn::backend::Cuda<f32>;
|
||||
type TrainingBackend = Autodiff<InferenceBackend>;
|
||||
|
||||
const LIMITS: wgpu::Limits = wgpu::Limits::defaults();
|
||||
const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
|
||||
use strafesnet_graphics::setup;
|
||||
use strafesnet_roblox_bot_file::v0;
|
||||
|
||||
pub fn output_image_native(image_data: &[u8], texture_dims: (usize, usize), path: String) {
|
||||
use std::io::Write;
|
||||
|
||||
let mut png_data = Vec::<u8>::with_capacity(image_data.len());
|
||||
let mut encoder =
|
||||
png::Encoder::new(&mut png_data, texture_dims.0 as u32, texture_dims.1 as u32);
|
||||
encoder.set_color(png::ColorType::Grayscale);
|
||||
let mut png_writer = encoder.write_header().unwrap();
|
||||
png_writer.write_image_data(image_data).unwrap();
|
||||
png_writer.finish().unwrap();
|
||||
|
||||
let mut file = std::fs::File::create(&path).unwrap();
|
||||
file.write_all(&png_data).unwrap();
|
||||
}
|
||||
|
||||
const SIZE_X: usize = 64;
|
||||
const SIZE_Y: usize = 36;
|
||||
const INPUT: usize = SIZE_X * SIZE_Y;
|
||||
const HIDDEN: [usize; 7] = [
|
||||
INPUT >> 1,
|
||||
INPUT >> 2,
|
||||
INPUT >> 3,
|
||||
INPUT >> 4,
|
||||
INPUT >> 5,
|
||||
INPUT >> 6,
|
||||
INPUT >> 7,
|
||||
];
|
||||
const SIZE: glam::UVec2 = glam::uvec2(64, 36);
|
||||
const POSITION_HISTORY: usize = 4;
|
||||
const INPUT: usize = (SIZE.x * SIZE.y) as usize + POSITION_HISTORY * 3;
|
||||
const HIDDEN: [usize; 2] = [INPUT >> 3, INPUT >> 7];
|
||||
// MoveForward
|
||||
// MoveLeft
|
||||
// MoveBack
|
||||
@@ -47,9 +25,13 @@ const HIDDEN: [usize; 7] = [
|
||||
// mouse_dy
|
||||
const OUTPUT: usize = 7;
|
||||
|
||||
// bytes_per_row needs to be a multiple of 256.
|
||||
const STRIDE_SIZE: u32 = (SIZE.x * size_of::<f32>() as u32).next_multiple_of(256);
|
||||
|
||||
#[derive(Module, Debug)]
|
||||
struct Net<B: Backend> {
|
||||
input: Linear<B>,
|
||||
dropout: Dropout,
|
||||
hidden: [Linear<B>; HIDDEN.len() - 1],
|
||||
output: Linear<B>,
|
||||
activation: Relu,
|
||||
@@ -66,8 +48,10 @@ impl<B: Backend> Net<B> {
|
||||
layer
|
||||
});
|
||||
let output = LinearConfig::new(last_size, OUTPUT).init(device);
|
||||
let dropout = DropoutConfig::new(0.1).init();
|
||||
Self {
|
||||
input,
|
||||
dropout,
|
||||
hidden,
|
||||
output,
|
||||
activation: Relu::new(),
|
||||
@@ -75,6 +59,7 @@ impl<B: Backend> Net<B> {
|
||||
}
|
||||
fn forward(&self, input: Tensor<B, 2>) -> Tensor<B, 2> {
|
||||
let x = self.input.forward(input);
|
||||
let x = self.dropout.forward(x);
|
||||
let mut x = self.activation.forward(x);
|
||||
for layer in &self.hidden {
|
||||
x = layer.forward(x);
|
||||
@@ -84,6 +69,164 @@ impl<B: Backend> Net<B> {
|
||||
}
|
||||
}
|
||||
|
||||
struct GraphicsState {
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
graphics: strafesnet_roblox_bot_player::graphics::Graphics,
|
||||
graphics_texture_view: wgpu::TextureView,
|
||||
output_staging_buffer: wgpu::Buffer,
|
||||
texture_data: Vec<u8>,
|
||||
position_history: Vec<glam::Vec3>,
|
||||
}
|
||||
impl GraphicsState {
|
||||
fn new(map: &strafesnet_common::map::CompleteMap) -> Self {
|
||||
let desc = wgpu::InstanceDescriptor::new_without_display_handle_from_env();
|
||||
let instance = wgpu::Instance::new(desc);
|
||||
let (device, queue) = pollster::block_on(async {
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
force_fallback_adapter: false,
|
||||
compatible_surface: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
setup::step4::request_device(&adapter, LIMITS)
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
let mut graphics = strafesnet_roblox_bot_player::graphics::Graphics::new(
|
||||
&device, &queue, SIZE, FORMAT, LIMITS,
|
||||
);
|
||||
graphics.change_map(&device, &queue, map).unwrap();
|
||||
let graphics_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("RGB texture"),
|
||||
format: FORMAT,
|
||||
size: wgpu::Extent3d {
|
||||
width: SIZE.x,
|
||||
height: SIZE.y,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
let graphics_texture_view = graphics_texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
label: Some("RGB texture view"),
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
usage: Some(
|
||||
wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
),
|
||||
..Default::default()
|
||||
});
|
||||
let texture_data = Vec::<u8>::with_capacity((STRIDE_SIZE * SIZE.y) as usize);
|
||||
let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Output staging buffer"),
|
||||
size: texture_data.capacity() as u64,
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let position_history = Vec::with_capacity(POSITION_HISTORY);
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
graphics,
|
||||
graphics_texture_view,
|
||||
output_staging_buffer,
|
||||
texture_data,
|
||||
position_history,
|
||||
}
|
||||
}
|
||||
fn generate_inputs(&mut self, pos: glam::Vec3, angles: glam::Vec2, inputs: &mut Vec<f32>) {
|
||||
// write position history to model inputs
|
||||
if !self.position_history.is_empty() {
|
||||
let camera = strafesnet_graphics::graphics::view_inv(pos, angles).inverse();
|
||||
for &pos in self.position_history.iter().rev() {
|
||||
let relative_pos = camera.transform_vector3(pos);
|
||||
inputs.extend_from_slice(&relative_pos.to_array());
|
||||
}
|
||||
}
|
||||
// fill remaining history with zeroes
|
||||
for _ in self.position_history.len()..POSITION_HISTORY {
|
||||
inputs.extend_from_slice(&[0.0, 0.0, 0.0]);
|
||||
}
|
||||
|
||||
// track position history
|
||||
if self.position_history.len() < POSITION_HISTORY {
|
||||
self.position_history.push(pos);
|
||||
} else {
|
||||
self.position_history.rotate_left(1);
|
||||
*self.position_history.last_mut().unwrap() = pos;
|
||||
}
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("wgpu encoder"),
|
||||
});
|
||||
|
||||
// render!
|
||||
self.graphics
|
||||
.encode_commands(&mut encoder, &self.graphics_texture_view, pos, angles);
|
||||
|
||||
// copy the depth texture into ram
|
||||
encoder.copy_texture_to_buffer(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: self.graphics.depth_texture(),
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
wgpu::TexelCopyBufferInfo {
|
||||
buffer: &self.output_staging_buffer,
|
||||
layout: wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
// This needs to be a multiple of 256.
|
||||
bytes_per_row: Some(STRIDE_SIZE),
|
||||
rows_per_image: Some(SIZE.y),
|
||||
},
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: SIZE.x,
|
||||
height: SIZE.y,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
|
||||
self.queue.submit([encoder.finish()]);
|
||||
|
||||
// map buffer
|
||||
let buffer_slice = self.output_staging_buffer.slice(..);
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap());
|
||||
self.device
|
||||
.poll(wgpu::PollType::wait_indefinitely())
|
||||
.unwrap();
|
||||
receiver.recv().unwrap().unwrap();
|
||||
|
||||
// copy texture inside a scope so the mapped view gets dropped
|
||||
{
|
||||
let view = buffer_slice.get_mapped_range();
|
||||
self.texture_data.extend_from_slice(&view[..]);
|
||||
}
|
||||
self.output_staging_buffer.unmap();
|
||||
|
||||
// discombolulate stride
|
||||
for y in 0..SIZE.y {
|
||||
inputs.extend(
|
||||
self.texture_data[(STRIDE_SIZE * y) as usize
|
||||
..(STRIDE_SIZE * y + SIZE.x * size_of::<f32>() as u32) as usize]
|
||||
.chunks_exact(4)
|
||||
.map(|b| 1.0 - 2.0 * f32::from_le_bytes(b.try_into().unwrap())),
|
||||
)
|
||||
}
|
||||
|
||||
self.texture_data.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn training() {
|
||||
let gpu_id: usize = std::env::args()
|
||||
.skip(1)
|
||||
@@ -108,70 +251,16 @@ fn training() {
|
||||
let world_offset = bot.world_offset();
|
||||
let timelines = bot.timelines();
|
||||
|
||||
// setup graphics
|
||||
let desc = wgpu::InstanceDescriptor::new_without_display_handle_from_env();
|
||||
let instance = wgpu::Instance::new(desc);
|
||||
let (device, queue) = pollster::block_on(async {
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
force_fallback_adapter: false,
|
||||
compatible_surface: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
setup::step4::request_device(&adapter, LIMITS)
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
|
||||
let size = [SIZE_X as u32, SIZE_Y as u32].into();
|
||||
let mut graphics = strafesnet_roblox_bot_player::graphics::Graphics::new(
|
||||
&device, &queue, size, FORMAT, LIMITS,
|
||||
);
|
||||
graphics.change_map(&device, &queue, &map).unwrap();
|
||||
|
||||
// setup simulation
|
||||
// run progressively longer segments of the map, starting very close to the end of the run and working the starting time backwards until the ai can run the whole map
|
||||
|
||||
// set up textures
|
||||
let graphics_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("RGB texture"),
|
||||
format: FORMAT,
|
||||
size: wgpu::Extent3d {
|
||||
width: size.x,
|
||||
height: size.y,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING
|
||||
| wgpu::TextureUsages::COPY_SRC,
|
||||
view_formats: &[],
|
||||
});
|
||||
let graphics_texture_view = graphics_texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
label: Some("RGB texture view"),
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
usage: Some(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING),
|
||||
..Default::default()
|
||||
});
|
||||
// bytes_per_row needs to be a multiple of 256.
|
||||
let stride_size = (size.x * size_of::<f32>() as u32).next_multiple_of(256);
|
||||
let mut texture_data = Vec::<u8>::with_capacity((stride_size * size.y) as usize);
|
||||
let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Output staging buffer"),
|
||||
size: texture_data.capacity() as u64,
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
// set up graphics
|
||||
let mut g = GraphicsState::new(&map);
|
||||
|
||||
// training data
|
||||
let training_samples = timelines.input_events.len() - 1;
|
||||
|
||||
let input_size = (size.x * size.y) as usize * size_of::<f32>();
|
||||
let input_size = INPUT * size_of::<f32>();
|
||||
let mut inputs = Vec::with_capacity(input_size * training_samples);
|
||||
let mut targets = Vec::with_capacity(OUTPUT * training_samples);
|
||||
|
||||
@@ -184,7 +273,7 @@ fn training() {
|
||||
let mut last_mx = first.event.mouse_pos.x;
|
||||
let mut last_my = first.event.mouse_pos.y;
|
||||
|
||||
for (i, input_event) in it.enumerate() {
|
||||
for input_event in it {
|
||||
let mouse_dx = input_event.event.mouse_pos.x - last_mx;
|
||||
let mouse_dy = input_event.event.mouse_pos.y - last_my;
|
||||
last_mx = input_event.event.mouse_pos.x;
|
||||
@@ -249,92 +338,17 @@ fn training() {
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
fn p(v: v0::Vector3) -> [f32; 3] {
|
||||
[v.x, v.y, v.z]
|
||||
fn vec3(v: v0::Vector3) -> glam::Vec3 {
|
||||
glam::vec3(v.x, v.y, v.z)
|
||||
}
|
||||
fn a(a: v0::Vector3) -> [f32; 2] {
|
||||
[a.y, a.x]
|
||||
}
|
||||
fn add<T: core::ops::Add>(lhs: T, rhs: T) -> T::Output {
|
||||
lhs + rhs
|
||||
fn angles(a: v0::Vector3) -> glam::Vec2 {
|
||||
glam::vec2(a.y, a.x)
|
||||
}
|
||||
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("wgpu encoder"),
|
||||
});
|
||||
let pos = vec3(output_event.event.position) - world_offset;
|
||||
let angles = angles(output_event.event.angles);
|
||||
|
||||
// render!
|
||||
graphics.encode_commands(
|
||||
&mut encoder,
|
||||
&graphics_texture_view,
|
||||
add(world_offset, p(output_event.event.position).into()),
|
||||
a(output_event.event.angles).into(),
|
||||
);
|
||||
|
||||
// copy the depth texture into ram
|
||||
encoder.copy_texture_to_buffer(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &graphics_texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
wgpu::TexelCopyBufferInfo {
|
||||
buffer: &output_staging_buffer,
|
||||
layout: wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
// This needs to be a multiple of 256.
|
||||
bytes_per_row: Some(stride_size as u32),
|
||||
rows_per_image: None,
|
||||
},
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: size.x,
|
||||
height: size.y,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
|
||||
queue.submit([encoder.finish()]);
|
||||
|
||||
// map buffer
|
||||
let buffer_slice = output_staging_buffer.slice(..);
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap());
|
||||
device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
|
||||
receiver.recv().unwrap().unwrap();
|
||||
|
||||
// copy texture inside a scope so the mapped view gets dropped
|
||||
{
|
||||
let view = buffer_slice.get_mapped_range();
|
||||
texture_data.extend_from_slice(&view[..]);
|
||||
}
|
||||
output_staging_buffer.unmap();
|
||||
|
||||
println!("{texture_data:?}");
|
||||
|
||||
// discombolulate stride
|
||||
for y in 0..size.y {
|
||||
inputs.extend(
|
||||
texture_data[(stride_size * y) as usize
|
||||
..(stride_size * y + size.x * size_of::<f32>() as u32) as usize]
|
||||
.chunks_exact(4)
|
||||
.map(|b| b[0] as f32 + b[1] as f32 + b[2] as f32),
|
||||
)
|
||||
}
|
||||
|
||||
// write a png
|
||||
output_image_native(
|
||||
&inputs[i * INPUT..(i + 1) * INPUT]
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|f| f as u8)
|
||||
.collect::<Vec<u8>>(),
|
||||
(SIZE_X, SIZE_Y),
|
||||
format!("depth_images/{i}.png").into(),
|
||||
);
|
||||
|
||||
texture_data.clear();
|
||||
g.generate_inputs(pos, angles, &mut inputs);
|
||||
}
|
||||
|
||||
let device = burn::backend::cuda::CudaDevice::new(gpu_id);
|
||||
@@ -342,13 +356,10 @@ fn training() {
|
||||
let mut model: Net<TrainingBackend> = Net::init(&device);
|
||||
println!("Training model ({} parameters)", model.num_params());
|
||||
|
||||
let mut optim = SgdConfig::new().init();
|
||||
let mut optim = AdamConfig::new().init();
|
||||
|
||||
let inputs = Tensor::from_data(
|
||||
TensorData::new(
|
||||
inputs,
|
||||
Shape::new([training_samples, (size.x * size.y) as usize]),
|
||||
),
|
||||
TensorData::new(inputs, Shape::new([training_samples, INPUT])),
|
||||
&device,
|
||||
);
|
||||
let targets = Tensor::from_data(
|
||||
@@ -356,8 +367,11 @@ fn training() {
|
||||
&device,
|
||||
);
|
||||
|
||||
const LEARNING_RATE: f64 = 0.5;
|
||||
const EPOCHS: usize = 10000;
|
||||
const LEARNING_RATE: f64 = 0.001;
|
||||
const EPOCHS: usize = 100000;
|
||||
|
||||
let mut best_model = model.clone();
|
||||
let mut best_loss = f32::INFINITY;
|
||||
|
||||
for epoch in 0..EPOCHS {
|
||||
let predictions = model.forward(inputs.clone());
|
||||
@@ -374,6 +388,12 @@ fn training() {
|
||||
let grads = loss.backward();
|
||||
let grads = GradientsParams::from_grads(grads, &model);
|
||||
|
||||
// get the best model
|
||||
if loss_scalar < best_loss {
|
||||
best_loss = loss_scalar;
|
||||
best_model = model.clone();
|
||||
}
|
||||
|
||||
model = optim.step(LEARNING_RATE, model, grads);
|
||||
|
||||
if epoch % (EPOCHS >> 4) == 0 || epoch == EPOCHS - 1 {
|
||||
@@ -381,15 +401,211 @@ fn training() {
|
||||
println!(" epoch {:>5} | loss = {:.8}", epoch, loss_scalar);
|
||||
}
|
||||
}
|
||||
|
||||
let date_string = format!("{}_{}.model", chrono::Utc::now(), best_loss);
|
||||
best_model
|
||||
.save_file(
|
||||
date_string,
|
||||
&burn::record::BinFileRecorder::<burn::record::FullPrecisionSettings>::new(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
use strafesnet_common::instruction::TimedInstruction;
|
||||
use strafesnet_common::mouse::MouseState;
|
||||
use strafesnet_common::physics::{
|
||||
Instruction as PhysicsInputInstruction, MiscInstruction, ModeInstruction, MouseInstruction,
|
||||
SetControlInstruction, Time as PhysicsTime,
|
||||
};
|
||||
use strafesnet_physics::physics::{PhysicsContext, PhysicsData, PhysicsState};
|
||||
|
||||
pub struct Recording {
|
||||
instructions: Vec<TimedInstruction<PhysicsInputInstruction, PhysicsTime>>,
|
||||
}
|
||||
struct FrameState {
|
||||
trajectory: strafesnet_physics::physics::Trajectory,
|
||||
camera: strafesnet_physics::physics::PhysicsCamera,
|
||||
}
|
||||
impl FrameState {
|
||||
fn pos(&self, time: PhysicsTime) -> glam::Vec3 {
|
||||
self.trajectory
|
||||
.extrapolated_position(time)
|
||||
.map(Into::<f32>::into)
|
||||
.to_array()
|
||||
.into()
|
||||
}
|
||||
fn angles(&self) -> glam::Vec2 {
|
||||
self.camera.simulate_move_angles(glam::IVec2::ZERO)
|
||||
}
|
||||
}
|
||||
struct Session {
|
||||
geometry_shared: PhysicsData,
|
||||
simulation: PhysicsState,
|
||||
recording: Recording,
|
||||
}
|
||||
impl Session {
|
||||
fn get_frame_state(&self) -> FrameState {
|
||||
FrameState {
|
||||
trajectory: self.simulation.camera_trajectory(&self.geometry_shared),
|
||||
camera: self.simulation.camera(),
|
||||
}
|
||||
}
|
||||
fn run(&mut self, time: PhysicsTime, instruction: PhysicsInputInstruction) {
|
||||
let instruction = TimedInstruction { time, instruction };
|
||||
self.recording.instructions.push(instruction.clone());
|
||||
PhysicsContext::run_input_instruction(
|
||||
&mut self.simulation,
|
||||
&self.geometry_shared,
|
||||
instruction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn inference() {
|
||||
let mut args = std::env::args().skip(1);
|
||||
|
||||
// pick device
|
||||
let gpu_id: usize = args
|
||||
.next()
|
||||
.map(|id| id.parse().unwrap())
|
||||
.unwrap_or_default();
|
||||
let device = burn::backend::cuda::CudaDevice::new(gpu_id);
|
||||
|
||||
// load model
|
||||
let path: std::path::PathBuf = args.next().unwrap().parse().unwrap();
|
||||
let mut model: Net<InferenceBackend> = Net::init(&device);
|
||||
model = model
|
||||
.load_file(
|
||||
path,
|
||||
&burn::record::BinFileRecorder::<burn::record::FullPrecisionSettings>::new(),
|
||||
&device,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// load map
|
||||
let map_file = include_bytes!("../files/bhop_marble_5692093612.snfm");
|
||||
let map = strafesnet_snf::read_map(std::io::Cursor::new(map_file))
|
||||
.unwrap()
|
||||
.into_complete_map()
|
||||
.unwrap();
|
||||
let modes = map.modes.clone().denormalize();
|
||||
let mode = modes
|
||||
.get_mode(strafesnet_common::gameplay_modes::ModeId::MAIN)
|
||||
.unwrap();
|
||||
let start_zone = map.models.get(mode.get_start().get() as usize).unwrap();
|
||||
let start_offset = glam::Vec3::from_array(
|
||||
start_zone
|
||||
.transform
|
||||
.translation
|
||||
.map(|f| f.into())
|
||||
.to_array(),
|
||||
);
|
||||
|
||||
// setup graphics
|
||||
let mut g = GraphicsState::new(&map);
|
||||
|
||||
// setup simulation
|
||||
let mut session = Session {
|
||||
geometry_shared: PhysicsData::new(&map),
|
||||
simulation: PhysicsState::default(),
|
||||
recording: Recording {
|
||||
instructions: Vec::new(),
|
||||
},
|
||||
};
|
||||
|
||||
let mut time = PhysicsTime::ZERO;
|
||||
|
||||
// reset to start zone
|
||||
session.run(time, PhysicsInputInstruction::Mode(ModeInstruction::Reset));
|
||||
// session.run(
|
||||
// time,
|
||||
// PhysicsInputInstruction::Misc(MiscInstruction::SetSensitivity(?)),
|
||||
// );
|
||||
session.run(
|
||||
time,
|
||||
PhysicsInputInstruction::Mode(ModeInstruction::Restart(
|
||||
strafesnet_common::gameplay_modes::ModeId::MAIN,
|
||||
)),
|
||||
);
|
||||
|
||||
// TEMP: turn mouse left
|
||||
let mut mouse_pos = glam::ivec2(-5300, 0);
|
||||
|
||||
const STEP: PhysicsTime = PhysicsTime::from_millis(10);
|
||||
let mut input_floats = Vec::new();
|
||||
// setup agent-simulation feedback loop
|
||||
// go!
|
||||
for _ in 0..20 * 100 {
|
||||
// generate inputs
|
||||
let frame_state = session.get_frame_state();
|
||||
g.generate_inputs(
|
||||
frame_state.pos(time) - start_offset,
|
||||
frame_state.angles(),
|
||||
&mut input_floats,
|
||||
);
|
||||
|
||||
// inference
|
||||
let inputs = Tensor::from_data(
|
||||
TensorData::new(input_floats.clone(), Shape::new([1, INPUT])),
|
||||
&device,
|
||||
);
|
||||
let outputs = model.forward(inputs).into_data().into_vec::<f32>().unwrap();
|
||||
|
||||
let &[
|
||||
move_forward,
|
||||
move_left,
|
||||
move_back,
|
||||
move_right,
|
||||
jump,
|
||||
mouse_dx,
|
||||
mouse_dy,
|
||||
] = outputs.as_slice()
|
||||
else {
|
||||
panic!()
|
||||
};
|
||||
|
||||
macro_rules! set_control {
|
||||
($control:ident,$output:expr) => {
|
||||
session.run(
|
||||
time,
|
||||
PhysicsInputInstruction::SetControl(SetControlInstruction::$control(
|
||||
0.5 < $output,
|
||||
)),
|
||||
);
|
||||
};
|
||||
}
|
||||
set_control!(SetMoveForward, move_forward);
|
||||
set_control!(SetMoveLeft, move_left);
|
||||
set_control!(SetMoveBack, move_back);
|
||||
set_control!(SetMoveRight, move_right);
|
||||
set_control!(SetJump, jump);
|
||||
|
||||
mouse_pos += glam::vec2(mouse_dx, mouse_dy).round().as_ivec2();
|
||||
let next_time = time + STEP;
|
||||
session.run(
|
||||
time,
|
||||
PhysicsInputInstruction::Mouse(MouseInstruction::SetNextMouse(MouseState {
|
||||
pos: mouse_pos,
|
||||
time: next_time,
|
||||
})),
|
||||
);
|
||||
|
||||
time = next_time;
|
||||
|
||||
// clear
|
||||
input_floats.clear();
|
||||
}
|
||||
|
||||
let date_string = format!("{}.snfb", chrono::Utc::now());
|
||||
let file = std::fs::File::create(date_string).unwrap();
|
||||
strafesnet_snf::bot::write_bot(
|
||||
std::io::BufWriter::new(file),
|
||||
strafesnet_physics::VERSION.get(),
|
||||
core::mem::take(&mut session.recording.instructions),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
training();
|
||||
// training();
|
||||
inference();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user