Skip to content

Commit 13082e7

Browse files
BAPI - mapmanip submap perf improv + mapmanip script (#19634)
changes: - rscadd: "BAPI - mapmanip submap performance improvements." - rscadd: "BAPI - mapmanip script." perf improv in the form of changing the map container from hashmap to a flat grid hashmap was only bad for big maps with lots of submaps - did not affect horizon with its one small submap the script: ![image](https://github.com/user-attachments/assets/136365c3-5cbc-4189-90a0-f163ea836735) --------- Co-authored-by: DreamySkrell <> Co-authored-by: AuroraBuildBot <[email protected]>
1 parent 61ef70f commit 13082e7

13 files changed

+188
-53
lines changed

bapi.dll

45.7 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
author: DreamySkrell
3+
4+
delete-after: True
5+
6+
changes:
7+
- rscadd: "BAPI - mapmanip submap performance improvements."
8+
- rscadd: "BAPI - mapmanip script."

rust/bapi/Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/bapi/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ eyre = "0.6.12"
2222
diff = "0.1"
2323
# general utility lib for iterator operations
2424
itertools = "0.10.5"
25+
# simple flat grid, basically just wrapper over Vec<Vec<T>>
26+
grid = "0.14.0"
2527
# fast hashmap
2628
fxhash = "0.2.1"
2729
# generating random numbers

rust/bapi/src/lib.rs

+41
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod mapmanip;
33

44
use byondapi::prelude::*;
55
use eyre::{Context, ContextCompat};
6+
use itertools::Itertools;
67

78
/// Call stack trace dm method with message.
89
pub(crate) fn dm_call_stack_trace(msg: String) {
@@ -94,3 +95,43 @@ fn read_dmm_file(path: ByondValue) -> eyre::Result<ByondValue> {
9495
// and return it
9596
Ok(ByondValue::new_str(dmm)?)
9697
}
98+
99+
/// To be used by the `tools/bapi/mapmanip.ps1` script.
100+
/// Not to be called from the game server, so bad error-handling is fine.
101+
/// This should run map manipulations on every `.dmm` map that has a `.jsonc` config file,
102+
/// and write it to a `.mapmanipout.dmm` file in the same location.
103+
#[no_mangle]
104+
pub unsafe extern "C" fn all_mapmanip_configs_execute_ffi() {
105+
let mapmanip_configs = walkdir::WalkDir::new("../../maps")
106+
.into_iter()
107+
.map(|d| d.unwrap().path().to_owned())
108+
.filter(|p| p.extension().is_some())
109+
.filter(|p| p.extension().unwrap() == "jsonc")
110+
.collect_vec();
111+
assert_ne!(mapmanip_configs.len(), 0);
112+
113+
for config_path in mapmanip_configs {
114+
let dmm_path = {
115+
let mut p = config_path.clone();
116+
p.set_extension("dmm");
117+
p
118+
};
119+
120+
let path_dir: &std::path::Path = dmm_path.parent().unwrap();
121+
122+
let mut dmm = dmmtools::dmm::Map::from_file(&dmm_path).unwrap();
123+
124+
let config = crate::mapmanip::mapmanip_config_parse(&config_path).unwrap();
125+
126+
dmm = crate::mapmanip::mapmanip(path_dir, dmm, &config).unwrap();
127+
128+
let dmm = crate::mapmanip::core::map_to_string(&dmm).unwrap();
129+
130+
let dmm_out_path = {
131+
let mut p = dmm_path.clone();
132+
p.set_extension("mapmanipout.dmm");
133+
p
134+
};
135+
std::fs::write(dmm_out_path, dmm).unwrap();
136+
}
137+
}

rust/bapi/src/mapmanip/core/mod.rs

+58-2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ pub mod map_to_string;
77
pub use map_to_string::map_to_string;
88

99
use dmmtools::dmm;
10+
use dmmtools::dmm::Coord2;
1011

1112
///
12-
#[derive(Clone, Debug)]
13+
#[derive(Clone, Debug, Default)]
1314
pub struct Tile {
1415
///
1516
pub key_suggestion: dmm::Key,
@@ -53,6 +54,61 @@ impl Tile {
5354
}
5455
}
5556

57+
/// Thin abstraction over `grid::Grid`, to provide a hashmap-like interface,
58+
/// and to translate between dmm coords (start at 1) and grid coords (start at 0).
59+
/// The translation is so that it looks better in logs/errors/etc,
60+
/// where shown coords would correspond to coords seen in game or in strongdmm.
61+
#[derive(Clone, Debug)]
62+
pub struct TileGrid {
63+
pub grid: grid::Grid<crate::mapmanip::core::Tile>,
64+
}
65+
66+
impl TileGrid {
67+
pub fn new(size_x: i32, size_y: i32) -> TileGrid {
68+
Self {
69+
grid: grid::Grid::new(size_x as usize, size_y as usize),
70+
}
71+
}
72+
73+
pub fn len(&self) -> usize {
74+
self.grid.size().0 * self.grid.size().1
75+
}
76+
77+
pub fn iter(&self) -> impl Iterator<Item = (Coord2, &Tile)> {
78+
self.grid
79+
.indexed_iter()
80+
.map(|((x, y), t)| (Coord2::new((x + 1) as i32, (y + 1) as i32), t))
81+
}
82+
83+
pub fn get_mut(&mut self, coord: &Coord2) -> Option<&mut Tile> {
84+
self.grid
85+
.get_mut((coord.x - 1) as usize, (coord.y - 1) as usize)
86+
}
87+
88+
pub fn get(&self, coord: &Coord2) -> Option<&Tile> {
89+
self.grid
90+
.get((coord.x - 1) as usize, (coord.y - 1) as usize)
91+
}
92+
93+
pub fn insert(&mut self, coord: &Coord2, tile: Tile) {
94+
*self.get_mut(coord).unwrap() = tile;
95+
}
96+
97+
pub fn keys(&self) -> impl Iterator<Item = Coord2> + '_ {
98+
self.grid
99+
.indexed_iter()
100+
.map(|((x, y), _t)| Coord2::new((x + 1) as i32, (y + 1) as i32))
101+
}
102+
103+
pub fn values(&self) -> impl Iterator<Item = &Tile> {
104+
self.grid.iter()
105+
}
106+
107+
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Tile> {
108+
self.grid.iter_mut()
109+
}
110+
}
111+
56112
/// This is analogous to `dmmtools::dmm::Map`, but instead of being structured like dmm maps are,
57113
/// where they have a dictionary of keys-to-prefabs and a separate grid of keys,
58114
/// this is only a direct coord-to-prefab grid.
@@ -62,7 +118,7 @@ pub struct GridMap {
62118
///
63119
pub size: dmm::Coord3,
64120
///
65-
pub grid: std::collections::BTreeMap<dmm::Coord2, crate::mapmanip::core::Tile>,
121+
pub grid: crate::mapmanip::core::TileGrid,
66122
}
67123

68124
impl GridMap {

rust/bapi/src/mapmanip/core/to_grid_map.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ fn tuple_to_size(xyz: (usize, usize, usize)) -> Coord3 {
1010
pub fn to_grid_map(dict_map: &dmm::Map) -> GridMap {
1111
let mut grid_map = GridMap {
1212
size: tuple_to_size(dict_map.dim_xyz()),
13-
grid: Default::default(),
13+
grid: crate::mapmanip::core::TileGrid::new(
14+
dict_map.dim_xyz().0 as i32,
15+
dict_map.dim_xyz().1 as i32,
16+
),
1417
};
1518

1619
for x in 1..grid_map.size.x + 1 {
@@ -23,7 +26,7 @@ pub fn to_grid_map(dict_map: &dmm::Map) -> GridMap {
2326
prefabs,
2427
};
2528
let coord = dmm::Coord2::new(coord.x, coord.y);
26-
grid_map.grid.insert(coord, tile);
29+
grid_map.grid.insert(&coord, tile);
2730
}
2831
}
2932

rust/bapi/src/mapmanip/mod.rs

+3-7
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,15 @@ fn mapmanip_submap_extract_insert(
111111
let mut marker_extract_coords = vec![];
112112
for (coord, tile) in submaps_map.grid.iter() {
113113
if tile.prefabs.iter().any(|p| p.path == *marker_extract) {
114-
marker_extract_coords.push(*coord);
114+
marker_extract_coords.push(coord);
115115
}
116116
}
117117

118118
// find all the insert markers
119119
let mut marker_insert_coords = vec![];
120120
for (coord, tile) in map.grid.iter() {
121121
if tile.prefabs.iter().any(|p| p.path == *marker_insert) {
122-
marker_insert_coords.push(*coord);
122+
marker_insert_coords.push(coord);
123123
}
124124
}
125125

@@ -132,17 +132,13 @@ fn mapmanip_submap_extract_insert(
132132
.enumerate()
133133
.choose(&mut rand::thread_rng())
134134
.wrap_err(format!(
135-
"can't pick a submap to extract; no extract markers in the submaps dmm; marker type: {marker_extract}"
135+
"can't pick a submap to extract; no more extract markers in the submaps dmm; marker type: {marker_extract}"
136136
))?;
137137

138138
// if submaps should not be repeating, remove this one from the list
139139
if !submaps_can_repeat {
140140
marker_extract_coords.remove(extract_coord_index);
141141
}
142-
eyre::ensure!(
143-
!marker_extract_coords.is_empty(),
144-
format!("no more submaps left to extract; marker type: {marker_extract}")
145-
);
146142

147143
// extract that submap from the submap dmm
148144
let extracted = tools::extract_submap(&submaps_map, extract_coord, submap_size)

rust/bapi/src/mapmanip/test.rs

+23-37
Original file line numberDiff line numberDiff line change
@@ -22,38 +22,14 @@ fn all_test_dmm() -> Vec<std::path::PathBuf> {
2222
.collect_vec()
2323
}
2424

25-
#[test]
26-
fn grid_check() {
27-
let path = std::path::Path::new("src/mapmanip/test-in/_tiny_test_map.dmm");
28-
println!("path: {}", path.display());
29-
30-
let grid_map = crate::mapmanip::core::GridMap::from_file(&path).unwrap();
31-
assert!(grid_map.grid[&dmm::Coord2::new(2, 1)]
32-
.prefabs
33-
.iter()
34-
.any(|p| p.path == "/obj/random/firstaid"));
35-
assert!(grid_map.grid[&dmm::Coord2::new(1, 2)]
36-
.prefabs
37-
.iter()
38-
.any(|p| p.path == "/obj/random/finances"));
39-
assert!(grid_map.grid[&dmm::Coord2::new(14, 15)]
40-
.prefabs
41-
.iter()
42-
.any(|p| p.path == "/obj/random/handgun"));
43-
assert!(grid_map.grid[&dmm::Coord2::new(15, 14)]
44-
.prefabs
45-
.iter()
46-
.any(|p| p.path == "/obj/random/handgun"));
47-
}
48-
4925
#[test]
5026
fn to_grid_and_back() {
5127
for path in all_test_dmm() {
5228
println!("path: {}", path.display());
5329

5430
let dict_map_original = dmmtools::dmm::Map::from_file(&path).unwrap();
5531
let grid_map = crate::mapmanip::core::to_grid_map(&dict_map_original);
56-
let dict_map_again = crate::mapmanip::core::to_dict_map(&grid_map);
32+
let dict_map_again = crate::mapmanip::core::to_dict_map(&grid_map).unwrap();
5733
let map_str_original = crate::mapmanip::core::map_to_string(&dict_map_original).unwrap();
5834
let map_str_from_grid = crate::mapmanip::core::map_to_string(&dict_map_again).unwrap();
5935

@@ -83,10 +59,11 @@ fn extract() {
8359
&grid_map_src,
8460
Coord2::new(4, 7),
8561
Coord2::new(10, 5),
86-
);
62+
)
63+
.unwrap();
8764
let grid_map_xtr_expected = crate::mapmanip::core::to_grid_map(&dict_map_xtr_expected);
8865

89-
let dict_map_xtr = crate::mapmanip::core::to_dict_map(&grid_map_xtr);
66+
let dict_map_xtr = crate::mapmanip::core::to_dict_map(&grid_map_xtr).unwrap();
9067
dict_map_xtr.to_file(path_xtr_out).unwrap();
9168

9269
assert_eq!(
@@ -95,8 +72,8 @@ fn extract() {
9572
);
9673

9774
for key in grid_map_xtr_expected.grid.keys() {
98-
let tile_xtr_expected = grid_map_xtr_expected.grid.get(key).unwrap();
99-
let tile_xtr = grid_map_xtr.grid.get(key).unwrap();
75+
let tile_xtr_expected = grid_map_xtr_expected.grid.get(&key).unwrap();
76+
let tile_xtr = grid_map_xtr.grid.get(&key).unwrap();
10077
assert_eq!(tile_xtr_expected.prefabs, tile_xtr.prefabs);
10178
}
10279
}
@@ -111,16 +88,17 @@ fn insert() {
11188
crate::mapmanip::core::GridMap::from_file(&path_dst_expected).unwrap();
11289
let grid_map_xtr = crate::mapmanip::core::GridMap::from_file(&path_xtr).unwrap();
11390
let mut grid_map_dst = crate::mapmanip::core::GridMap::from_file(&path_dst).unwrap();
114-
crate::mapmanip::tools::insert_submap(&grid_map_xtr, Coord2::new(6, 4), &mut grid_map_dst);
91+
crate::mapmanip::tools::insert_submap(&grid_map_xtr, Coord2::new(6, 4), &mut grid_map_dst)
92+
.unwrap();
11593

11694
assert_eq!(
11795
grid_map_dst_expected.grid.keys().collect::<Vec<_>>(),
11896
grid_map_dst.grid.keys().collect::<Vec<_>>(),
11997
);
12098

12199
for key in grid_map_dst_expected.grid.keys() {
122-
let tile_dst_expected = grid_map_dst_expected.grid.get(key).unwrap();
123-
let tile_dst = grid_map_dst.grid.get(key).unwrap();
100+
let tile_dst_expected = grid_map_dst_expected.grid.get(&key).unwrap();
101+
let tile_dst = grid_map_dst.grid.get(&key).unwrap();
124102
assert_eq!(tile_dst_expected.prefabs, tile_dst.prefabs);
125103
}
126104
}
@@ -138,12 +116,12 @@ fn keys_deduplicated() {
138116
for tile in grid_map_out.grid.values_mut() {
139117
tile.key_suggestion = dmm::Key::default();
140118
}
141-
let dict_map_out = crate::mapmanip::core::to_dict_map(&grid_map_out);
119+
let dict_map_out = crate::mapmanip::core::to_dict_map(&grid_map_out).unwrap();
142120
let grid_map_out = crate::mapmanip::core::to_grid_map(&dict_map_out);
143121

144122
for key in grid_map_src.grid.keys() {
145-
let tile_src = grid_map_src.grid.get(key).unwrap();
146-
let tile_out = grid_map_out.grid.get(key).unwrap();
123+
let tile_src = grid_map_src.grid.get(&key).unwrap();
124+
let tile_out = grid_map_out.grid.get(&key).unwrap();
147125
assert_eq!(tile_src.prefabs, tile_out.prefabs);
148126
}
149127

@@ -152,14 +130,15 @@ fn keys_deduplicated() {
152130

153131
#[test]
154132
fn mapmanip_configs_parse() {
155-
let foo = vec![crate::mapmanip::MapManipulation::InsertExtract {
133+
let foo = vec![crate::mapmanip::MapManipulation::SubmapExtractInsert {
156134
submap_size_x: 1,
157135
submap_size_y: 2,
158136
submaps_dmm: "a".to_owned(),
159137
marker_extract: "b".to_owned(),
160138
marker_insert: "c".to_owned(),
139+
submaps_can_repeat: true,
161140
}];
162-
dbg!(serde_json::to_string(&foo));
141+
dbg!(serde_json::to_string(&foo).unwrap());
163142

164143
let mapmanip_configs = walkdir::WalkDir::new("../../maps")
165144
.into_iter()
@@ -172,3 +151,10 @@ fn mapmanip_configs_parse() {
172151
let _ = crate::mapmanip::mapmanip_config_parse(&config);
173152
}
174153
}
154+
155+
#[test]
156+
fn mapmanip_configs_execute() {
157+
// this is only "unsafe" cause that function is `extern "C"`
158+
// it does not do anything actually unsafe
159+
unsafe { crate::all_mapmanip_configs_execute_ffi() }
160+
}

rust/bapi/src/mapmanip/tools/extract_submap.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::mapmanip::core::GridMap;
22
use dmmtools::dmm;
33
use dmmtools::dmm::Coord2;
44
use eyre::ContextCompat;
5-
use std::collections::BTreeMap;
65

76
/// Returns part of map of `xtr_size` and at `xtr_coord` from `src_map`.
87
pub fn extract_submap(
@@ -12,7 +11,7 @@ pub fn extract_submap(
1211
) -> eyre::Result<GridMap> {
1312
let mut dst_map = GridMap {
1413
size: xtr_size.z(1),
15-
grid: BTreeMap::new(),
14+
grid: crate::mapmanip::core::TileGrid::new(xtr_size.x, xtr_size.y),
1615
};
1716

1817
for x in 1..(xtr_size.x + 1) {
@@ -27,7 +26,7 @@ pub fn extract_submap(
2726
"cannot extract submap; coords out of bounds; x: {src_x}; y: {src_y};"
2827
))?;
2928

30-
dst_map.grid.insert(Coord2::new(x, y), tile.clone());
29+
dst_map.grid.insert(&Coord2::new(x, y), tile.clone());
3130
}
3231
}
3332

0 commit comments

Comments
 (0)