Skip to content

Commit

Permalink
optim: Add remote simulation support
Browse files Browse the repository at this point in the history
  • Loading branch information
YaLTeR committed May 28, 2022
1 parent 8671f07 commit 530b3b9
Show file tree
Hide file tree
Showing 8 changed files with 740 additions and 26 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ bitflags = "1.3.2"
libc = "0.2.99"
libloading = "0.7.0"
serde_json = "1.0.66"
serde = "1.0.127"
serde = { version = "1.0.127", features = ["derive"] }
bxt-macros = { path = "bxt-macros" }
bxt-patterns = { path = "bxt-patterns" }
bxt-strafe = { path = "bxt-strafe" }
Expand All @@ -24,10 +24,12 @@ thiserror = "1.0.26"
byte-slice-cast = "1.0.0"
crossbeam-channel = "0.5.1"
git-version = "0.3.5"
hltas = { git = "https://github.com/HLTAS/hltas.git" }
hltas = { git = "https://github.com/HLTAS/hltas.git", features = ["serde1"] }
glam = "0.20.2"
rand = "0.8.4"
ipc-channel = "0.16.0"
tap = "1.0.1"
parking_lot = "0.12.0"
mlua = { version = "0.7.3", features = ["luajit", "vendored", "serialize"] }
tracing = "0.1.34"
tracing-subscriber = "0.3.11"
Expand Down
60 changes: 60 additions & 0 deletions src/hooks/bxt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Bunnymod XT.
use std::os::raw::c_char;
use std::ptr::NonNull;

use hltas::HLTAS;

use crate::utils::{MainThreadMarker, Pointer, PointerTrait};

pub static BXT_TAS_LOAD_SCRIPT_FROM_STRING: Pointer<unsafe extern "C" fn(*const c_char)> =
Pointer::empty(b"bxt_tas_load_script_from_string\0");

static POINTERS: &[&dyn PointerTrait] = &[&BXT_TAS_LOAD_SCRIPT_FROM_STRING];

#[cfg(unix)]
fn open_library() -> Option<libloading::Library> {
use libc::{RTLD_NOLOAD, RTLD_NOW};

let library = unsafe {
libloading::os::unix::Library::open(Some("libBunnymodXT.so"), RTLD_NOW | RTLD_NOLOAD)
};
library.ok().map(libloading::Library::from)
}

#[cfg(windows)]
fn open_library() -> Option<libloading::Library> {
libloading::os::windows::Library::open_already_loaded("BunnymodXT.dll")
.ok()
.map(libloading::Library::from)
}

#[instrument(name = "bxt::find_pointers", skip_all)]
pub unsafe fn find_pointers(marker: MainThreadMarker) {
let library = match open_library() {
Some(library) => library,
None => {
debug!("could not find Bunnymod XT");
return;
}
};

for pointer in POINTERS {
let ptr = library
.get(pointer.symbol())
.ok()
.and_then(|sym| NonNull::new(*sym));
pointer.set(marker, ptr);
pointer.log(marker);
}
}

pub unsafe fn tas_load_script(marker: MainThreadMarker, script: &HLTAS) {
let mut buf = Vec::new();
script.to_writer(&mut buf).unwrap();

// Write the terminating NULL byte.
buf.push(0);

BXT_TAS_LOAD_SCRIPT_FROM_STRING.get(marker)(buf.as_ptr().cast());
}
8 changes: 7 additions & 1 deletion src/hooks/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::ffi::triangleapi::triangleapi_s;
use crate::ffi::usercmd::usercmd_s;
#[cfg(windows)]
use crate::hooks::opengl32;
use crate::hooks::{sdl, server};
use crate::hooks::{bxt, sdl, server};
use crate::modules::*;
use crate::utils::*;

Expand Down Expand Up @@ -1443,6 +1443,7 @@ pub mod exported {
sdl::find_pointers(marker);
#[cfg(windows)]
opengl32::find_pointers(marker);
bxt::find_pointers(marker);

let rv = Memory_Init.get(marker)(buf, size);

Expand All @@ -1451,6 +1452,10 @@ pub mod exported {
cvars::deregister_disabled_module_cvars(marker);
commands::deregister_disabled_module_commands(marker);

if bxt::BXT_TAS_LOAD_SCRIPT_FROM_STRING.is_set(marker) {
tas_editor::try_connecting_to_server(marker);
}

rv
})
}
Expand Down Expand Up @@ -1679,6 +1684,7 @@ pub mod exported {

if rv != 0 {
capture::time_passed(marker);
tas_editor::maybe_receive_messages_from_remote_server(marker);
}

rv
Expand Down
1 change: 1 addition & 0 deletions src/hooks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Hooked functions.
pub mod bxt;
pub mod engine;
pub mod sdl;
pub mod server;
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::ptr::NonNull;
use crate::ffi::playermove::playermove_s;
use crate::ffi::usercmd::usercmd_s;
use crate::hooks::engine;
use crate::modules::{tas_logging, tas_recording, tas_server_time_fix};
use crate::modules::{tas_editor, tas_logging, tas_recording, tas_server_time_fix};
use crate::utils::*;

pub static CmdStart: Pointer<unsafe extern "C" fn(*mut c_void, *mut usercmd_s, c_uint)> =
Expand Down Expand Up @@ -68,6 +68,7 @@ pub unsafe extern "C" fn my_CmdStart(

tas_logging::begin_cmd_frame(marker, *cmd, random_seed);
tas_recording::on_cmd_start(marker, *cmd, random_seed);
tas_editor::on_cmd_start(marker);

CmdStart.get(marker)(player, cmd, random_seed);
})
Expand Down
115 changes: 113 additions & 2 deletions src/modules/tas_editor/editor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::error::Error;
use std::io::Write;
use std::mem;
use std::num::NonZeroU32;
use std::rc::Rc;
use std::result::Result;
Expand All @@ -13,13 +14,15 @@ use mlua::{Lua, LuaSerdeExt};
use rand::distributions::Uniform;
use rand::prelude::Distribution;
use rand::Rng;
use tap::{Conv, Pipe, TryConv};
use serde::{Deserialize, Serialize};
use tap::{Conv, Pipe, Tap, TryConv};

use super::remote;
use crate::modules::triangle_drawing::triangle_api::{Primitive, RenderMode};
use crate::modules::triangle_drawing::TriangleApi;

/// A movement frame.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Frame {
/// Parameters used for simulating this frame.
pub parameters: Parameters,
Expand Down Expand Up @@ -510,6 +513,114 @@ impl Editor {
}
}

fn prepare_hltas_for_sending(&mut self) -> HLTAS {
let len = self.prefix.lines.len();
self.prefix.lines.extend(self.hltas.lines.iter().cloned());

// Replace the TAS editor / TAS optim commands with the start sending frames command.
match &mut self.prefix.lines[len] {
Line::FrameBulk(frame_bulk) => {
frame_bulk.console_command =
Some("_bxt_tas_optim_simulation_start_recording_frames".to_owned());
}
_ => unreachable!(),
}

// Add a toggleconsole command in the end.
self.prefix.lines.push(Line::FrameBulk(
FrameBulk::with_frame_time("0.001".to_owned()).tap_mut(|x| {
x.console_command = Some("_bxt_tas_optim_simulation_done;toggleconsole".to_owned())
}),
));

let hltas = self.prefix.clone();
self.prefix.lines.truncate(len);
hltas
}

pub fn maybe_simulate_all_in_remote_client(&mut self) {
if self.frames.len() > 1 {
// Already simulated.
return;
}

if let Some(mut frames) = remote::simulate(self.prepare_hltas_for_sending()) {
frames.insert(0, mem::take(&mut self.frames).into_iter().next().unwrap());
self.frames = frames;
}
}

// Yes I know this is not the best structured code at the moment...
#[allow(clippy::too_many_arguments)]
pub fn optimize_with_remote_clients(
&mut self,
frames: usize,
random_frames_to_change: usize,
change_single_frames: bool,
goal: &OptimizationGoal,
constraint: Option<&Constraint>,
mut on_improvement: impl FnMut(&str),
) {
self.maybe_simulate_all_in_remote_client();

if self.frames.len() == 1 {
// Haven't finished the initial simulation yet...
return;
}

let mut high = self.frames.len() - 1;
if frames > 0 {
high = high.min(frames);
}

let between = Uniform::from(0..high);
let mut rng = rand::thread_rng();

remote::receive_simulation_result_from_clients(|mut hltas, mut frames| {
frames.insert(0, self.frames[0].clone());
self.last_mutation_frames = Some(frames.clone());

if constraint.map(|c| c.is_valid(&frames)).unwrap_or(true)
&& goal.is_better(&frames, &self.frames)
{
self.hltas.lines = hltas
.lines
.drain(self.prefix.lines.len()..hltas.lines.len() - 1)
.collect();

// Remove the start sending frames command.
match &mut self.hltas.lines[0] {
Line::FrameBulk(frame_bulk) => frame_bulk.console_command = None,
_ => unreachable!(),
};

self.frames = frames;
on_improvement(&goal.to_string(&self.frames));
}
});

remote::simulate_in_available_clients(|| {
let temp = self.hltas.clone();

// Change several frames.
for _ in 0..random_frames_to_change {
if change_single_frames {
let frame = between.sample(&mut rng);
let frame_bulk = self.hltas.split_single_at_frame(frame).unwrap();
mutate_frame_bulk(&mut rng, frame_bulk);
} else {
mutate_single_frame_bulk(&mut self.hltas, &mut rng);
}
}

let hltas = self.prepare_hltas_for_sending();

self.hltas = temp;

hltas
});
}

fn mutate_frame<R: Rng>(&mut self, rng: &mut R, frame: usize) {
if frame > 0 {
let l = self.hltas.line_and_repeat_at_frame(frame).unwrap().0;
Expand Down
Loading

0 comments on commit 530b3b9

Please sign in to comment.