Add Layout trait

This commit is contained in:
hal8174 2025-01-30 23:22:37 +01:00
parent 00eda50872
commit 295490858b
12 changed files with 407 additions and 41 deletions

94
Cargo.lock generated
View file

@ -259,9 +259,9 @@ checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b"
[[package]]
name = "bumpalo"
version = "3.16.0"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "bytemuck"
@ -327,9 +327,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.26"
version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783"
checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796"
dependencies = [
"clap_builder",
"clap_derive",
@ -337,9 +337,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.26"
version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121"
checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
dependencies = [
"anstream",
"anstyle",
@ -449,9 +449,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
[[package]]
name = "csv"
@ -552,7 +552,7 @@ version = "0.1.0"
dependencies = [
"base64 0.22.1",
"bon",
"clap 4.5.26",
"clap 4.5.27",
"factorio-core",
"flate2",
"serde",
@ -563,7 +563,7 @@ dependencies = [
name = "factorio-blueprint-generator"
version = "0.1.0"
dependencies = [
"clap 4.5.26",
"clap 4.5.27",
"factorio-blueprint",
"factorio-core",
"factorio-pathfinding",
@ -574,7 +574,7 @@ dependencies = [
name = "factorio-core"
version = "0.1.0"
dependencies = [
"clap 4.5.26",
"clap 4.5.27",
"criterion",
"image",
"proptest",
@ -588,7 +588,7 @@ dependencies = [
name = "factorio-layout"
version = "0.1.0"
dependencies = [
"clap 4.5.26",
"clap 4.5.27",
"factorio-core",
"factorio-pathfinding",
"image",
@ -605,7 +605,7 @@ version = "0.1.0"
dependencies = [
"base64 0.21.7",
"bon",
"clap 4.5.26",
"clap 4.5.27",
"criterion",
"factorio-blueprint",
"factorio-core",
@ -659,7 +659,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi 0.13.3+wasi-0.2.2",
"windows-targets",
]
[[package]]
@ -762,9 +774,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
[[package]]
name = "indexmap"
version = "2.7.0"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
"hashbrown",
@ -862,9 +874,9 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libfuzzer-sys"
version = "0.4.8"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa"
checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75"
dependencies = [
"arbitrary",
"cc",
@ -1241,7 +1253,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
"getrandom 0.2.15",
]
[[package]]
@ -1366,9 +1378,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustix"
version = "0.38.43"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags 2.8.0",
"errno",
@ -1397,9 +1409,9 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.18"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
[[package]]
name = "same-file"
@ -1442,9 +1454,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.135"
version = "1.0.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
dependencies = [
"itoa",
"memchr",
@ -1560,13 +1572,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tempfile"
version = "3.15.0"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
dependencies = [
"cfg-if",
"fastrand",
"getrandom",
"getrandom 0.3.1",
"once_cell",
"rustix",
"windows-sys",
@ -1693,9 +1705,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicode-ident"
version = "1.0.14"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "unicode-linebreak"
@ -1763,6 +1775,15 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
@ -1943,13 +1964,22 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.24"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags 2.8.0",
]
[[package]]
name = "zerocopy"
version = "0.7.35"

View file

@ -1,6 +1,7 @@
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Serialize, Deserialize)]
pub enum Beltspeed {
Normal,
Fast,

View file

@ -0,0 +1,49 @@
size:
x: 15
y: 15
blocks:
- size:
x: 3
y: 2
input:
- offset:
x: 1
y: 1
dir: Up
output:
- offset:
x: 1
y: 0
dir: Up
- size:
x: 5
y: 2
input:
output:
- offset:
x: 1
y: 1
dir: Down
- size:
x: 5
y: 7
input:
- offset:
x: 0
y: 1
dir: Right
output:
connections:
- startblock: 1
startpoint: 0
endblock: 0
endpoint: 0
lanes: 1
beltspeed: Normal
- startblock: 0
startpoint: 0
endblock: 2
endpoint: 0
lanes: 1
beltspeed: Normal

View file

@ -0,0 +1,42 @@
use std::path::PathBuf;
use clap::Parser;
use factorio_core::prelude::Position;
use factorio_layout::{Layouter, valid_layout::ValidLayout};
use factorio_pathfinding::belt_finding::ConflictAvoidance;
use rand::{SeedableRng, rngs::SmallRng};
#[derive(Debug, Parser)]
struct Args {
#[clap(short, long, default_value_t = 0)]
seed: u64,
path: PathBuf,
// #[command(subcommand)]
// subcommand: Commands,
}
fn main() {
let args = Args::parse();
let file = std::fs::File::open(args.path).unwrap();
let problem = serde_yaml::from_reader(file).unwrap();
let l = ValidLayout {
max_tries: 100,
retries: 10,
start_size: Position::new(10, 10),
growth: Position::new(2, 2),
};
let p = ConflictAvoidance {
timeout: Some(std::time::Duration::from_millis(100)),
};
let mut rng = SmallRng::seed_from_u64(args.seed);
let r = l.layout(&problem, &p, &mut rng);
dbg!(r);
}

View file

@ -0,0 +1,21 @@
use factorio_pathfinding::Pathfinder;
use rand::Rng;
use crate::Layouter;
pub struct GeneticAlgorithmV2 {
pub new_layouts: usize,
pub mutation_timeout: usize,
pub max_mutations: usize,
}
impl Layouter for GeneticAlgorithmV2 {
fn layout<R: Rng, P: Pathfinder>(
&self,
input: &crate::LayoutInput,
pathfinder: &P,
rng: &mut R,
) -> Option<crate::LayoutResult> {
todo!()
}
}

View file

@ -1 +1,55 @@
use factorio_core::{beltoptions::Beltspeed, pathfield::PathField, prelude::*};
use factorio_pathfinding::Pathfinder;
use rand::Rng;
use serde::{Deserialize, Serialize};
pub mod block;
pub mod genetic_algorithm_v1;
pub mod genetic_algorithm_v2;
pub mod layout;
pub mod misc;
pub mod valid_layout;
#[derive(Debug, Serialize, Deserialize)]
pub struct MacroBlock {
pub size: Position,
pub input: Vec<Interface>,
pub output: Vec<Interface>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Interface {
pub offset: Position,
pub dir: Direction,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Connection {
pub startblock: usize,
pub startpoint: usize,
pub endblock: usize,
pub endpoint: usize,
pub lanes: usize,
pub beltspeed: Beltspeed,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LayoutInput {
pub blocks: Vec<MacroBlock>,
pub connections: Vec<Connection>,
}
#[derive(Debug)]
pub struct LayoutResult {
pub positions: Vec<Block>,
pub path_result: Vec<Vec<PathField>>,
}
pub trait Layouter {
fn layout<R: Rng, P: Pathfinder>(
&self,
input: &LayoutInput,
pathfinder: &P,
rng: &mut R,
) -> Option<LayoutResult>;
}

119
factorio-layout/src/misc.rs Normal file
View file

@ -0,0 +1,119 @@
use crate::LayoutInput;
use factorio_core::prelude::*;
use factorio_pathfinding::{Connection, PathInput, examples::HashMapMap};
use rand::Rng;
pub fn initally_set_blocks(
input: &LayoutInput,
size: Position,
retries: usize,
rng: &mut impl Rng,
) -> Option<Vec<Block>> {
let mut blocks = Vec::new();
let mut aabbs = Vec::new();
if place_block(input, size, retries, &mut blocks, &mut aabbs, rng) {
Some(blocks)
} else {
None
}
}
fn place_block(
input: &LayoutInput,
size: Position,
retries: usize,
blocks: &mut Vec<Block>,
aabbs: &mut Vec<AABB>,
rng: &mut impl Rng,
) -> bool {
if input.blocks.len() == blocks.len() {
return true;
}
let b = &input.blocks[blocks.len()];
for _ in 0..retries {
let dir = rng.r#gen::<Direction>();
let pos = match dir {
Direction::Up => Position::new(
rng.gen_range(0..=(size.x - b.size.x)),
rng.gen_range(0..=(size.y - b.size.y)),
),
Direction::Right => Position::new(
rng.gen_range((b.size.y - 1)..size.x),
rng.gen_range(0..=(size.y - b.size.x)),
),
Direction::Down => Position::new(
rng.gen_range((b.size.x - 1)..size.x),
rng.gen_range((b.size.y - 1)..size.y),
),
Direction::Left => Position::new(
rng.gen_range(0..=(size.x - b.size.y)),
rng.gen_range((b.size.x - 1)..size.y),
),
};
let current = Block::new(pos, dir, b.size);
let current_aabb = current.get_aabb();
if aabbs.iter().all(|&b| !AABB::collision(b, current_aabb)) {
blocks.push(current);
aabbs.push(current_aabb);
if place_block(input, size, retries, blocks, aabbs, rng) {
return true;
}
blocks.pop();
aabbs.pop();
}
}
false
}
pub fn path_input_from_blocks_positions(
input: &LayoutInput,
size: Position,
block_positions: &[Block],
) -> (Vec<Connection>, HashMapMap) {
let mut map = HashMapMap::new(size);
let mut connections = Vec::new();
for b in block_positions {
let aabb = b.get_aabb();
map.set_blocked_range(aabb.min(), aabb.max(), true);
}
for c in &input.connections {
let start_transform = block_positions[c.startblock].block_to_world();
let start_pos = input.blocks[c.startblock].output[c.startpoint]
.offset
.transform(start_transform);
let start_dir = input.blocks[c.startblock].output[c.startpoint]
.dir
.transform(start_transform);
let end_transform = block_positions[c.endblock].block_to_world();
let end_pos = input.blocks[c.endblock].input[c.endpoint]
.offset
.transform(end_transform);
let end_dir = input.blocks[c.endblock].input[c.endpoint]
.dir
.transform(end_transform);
connections.push(Connection {
start_pos,
start_dir,
end_pos,
end_dir,
beltspeed: c.beltspeed,
lanes: c.lanes,
});
}
(connections, map)
}

View file

@ -0,0 +1,44 @@
use factorio_core::prelude::{Position, PositionType};
use factorio_pathfinding::Pathfinder;
use rand::Rng;
use crate::{
LayoutResult, Layouter,
misc::{initally_set_blocks, path_input_from_blocks_positions},
};
pub struct ValidLayout {
pub max_tries: usize,
pub retries: usize,
pub start_size: Position,
pub growth: Position,
}
impl Layouter for ValidLayout {
fn layout<R: Rng, P: Pathfinder>(
&self,
input: &crate::LayoutInput,
pathfinder: &P,
rng: &mut R,
) -> Option<LayoutResult> {
for i in 0..self.max_tries {
let size = self.start_size + i as PositionType * self.growth;
if let Some(blocks) = initally_set_blocks(input, size, self.retries, rng) {
let (connections, map) = path_input_from_blocks_positions(input, size, &blocks);
if let Some(paths) = pathfinder.find_paths(factorio_pathfinding::PathInput {
connections: &connections,
map,
}) {
return Some(LayoutResult {
positions: blocks,
path_result: paths,
});
}
}
}
None
}
}

View file

@ -14,13 +14,13 @@ pub mod brute_force;
pub mod conflict_avoidance;
pub struct ConflictAvoidance {
timeout: Option<std::time::Duration>,
pub timeout: Option<std::time::Duration>,
}
impl SinglePathfinder for ConflictAvoidance {
fn find_paths<M: crate::Map>(
&self,
input: SinglePathInput<'_, M>,
input: SinglePathInput<M>,
) -> Option<Vec<Vec<factorio_core::pathfield::PathField>>> {
let (pos, size) = input.map.area();
let mut p = Problem::new(size.x as usize, size.y as usize);
@ -34,11 +34,17 @@ impl SinglePathfinder for ConflictAvoidance {
}
for i in input.connections {
p.add_connection((i.start_pos, i.start_dir), (i.end_pos, i.end_dir));
p.add_connection(
(i.start_pos, i.start_dir),
(i.end_pos.in_direction(&i.end_dir, -1), i.end_dir),
);
}
p.print_visualization();
if p.find_path() {
let mut c = conflict_avoidance::ConflictAvoidance::new(&p);
c.print_visualization();
if c.remove_all_conflicts(self.timeout) {
Some(c.get_paths().to_vec())

View file

@ -12,14 +12,14 @@ pub struct HashMapMap {
}
impl HashMapMap {
fn new(size: impl Into<Position>) -> Self {
pub fn new(size: impl Into<Position>) -> Self {
Self {
map: HashSet::new(),
size: size.into(),
}
}
fn set_blocked_range(
pub fn set_blocked_range(
&mut self,
min_pos: impl Into<Position>,
max_pos: impl Into<Position>,
@ -35,7 +35,7 @@ impl HashMapMap {
}
}
fn set_blocked(&mut self, pos: impl Into<Position>, block: bool) {
pub fn set_blocked(&mut self, pos: impl Into<Position>, block: bool) {
let pos = pos.into();
if block {
self.map.insert(pos);

View file

@ -7,8 +7,8 @@ pub mod misc;
pub mod priority_queue;
pub struct PathInput<'c, M> {
connections: &'c [Connection],
map: M,
pub connections: &'c [Connection],
pub map: M,
}
pub struct Connection {