Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# cargo-features = ["codegen-backend"]
[package]
name = "factory"
version = "0.2.0"
version = "0.2.1"
edition = "2024"
rust-version = "1.85"
build = "build.rs"
Expand Down
50 changes: 12 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,18 @@
# What is this?
This project is an academic recreation of the factory game [Factorio](https://www.factorio.com/) taking additional ideas from [Dyson Sphere Program](https://store.steampowered.com/app/1366540/Dyson_Sphere_Program/).
This project is an academic recreation of the factory game [Factorio](https://www.factorio.com/).

I created it as an exercise to see how far I could optimize the basic concepts and algorithms of the genre in terms of performance, while allowing myself minor changes to the games' rules.
I created it as an exercise to see how far I could optimize the basic mechanics and algorithms of the genre in terms of performance.
Another goal that emerged along the way, was learning about the way modern CPUs actually work.

# Roadmap
Currently I adding beacons and thinking about how to efficiently add logistics bots. Then I want to build a comprehensive suit of benchmark test to show if/by how much I was able to improve performance.

# Why did you start?
I was playing the above games and started being unable to expand due to performance issues. So in my hubris I declared: "How hard can it be?".
I was playing Factorio and started being unable to expand due to performance issues. So in my hubris I declared: "How hard can it be?".

# Current State
Most logic for power grids, belts, splitters, assemblers, labs, inserters, mining drills, solar panels and accumulators is working. This allowed me to recreate a Factorio base, giving me a point for performance comparison.
I was able to run a base comprised of 40 copies of [this](https://factoriobox.1au.us/map/view/2824bc1566bd95b5825baf3bd2eb8fa32de8397526464f5a0327bcb82d64ebf8/#1/nauvis/15/2942/1158/0/447) Factorio Megabase by Smurphy (which Factorio runs at ~40 UPS) at 60 UPS on my machine.

# Running it
It should run on Linux, Windows and MacOS. Assuming you have [rust and cargo](https://rust-lang.org), just `cargo run --release`. On NixOS the included `shell.nix` contains all you need.

## TODOS
- ~~Place Power Production~~
- ~~Blueprints so I can actually do perf tests~~
- ~~Permanently running replay system, so I can easily recreate crashes~~
- ~~Test harness for replays, to ensure they do not crash~~
- ~~Automatic insertion limit~~
- ~~Assembler Module Support~~
- ~~World listener support (i.e. update whenever something changes in the world, for power, beacons and inserters)~~
- Lazy Terrain Generation
- ~~Assembler Module Frontend~~
- ~~Assembler Power Consumption Modifier Support~~
- ~~Beacons~~
- ~~FIX Beacon Flicker due to lowering power consumption when beacons are unpowered~~
- ~~Storage Storage Inserters~~
- ~~Science Consumption in Labs~~
- ~~Inserter connections to labs~~
- ~~Debug inserters~~
- ~~Production Graphs~~
- ~~Liquids~~
- ~~Map View~~
- ~~Technology~~
- Mining Drills
- ~~Underground belts~~
- Fix Underground Pipe connection breaking/overlap
- Place Steam Turbines
- ~~Splitters~~
- Allow Belts of different types to connect to one another
- Decide if I want beacons to match factorio behaviour or keep the hard switch on/off
- ~~Ore Generation~~
- Add tile requirements for buildings/recipes (for offshore pump)
- Bots
- MAYBE: A canonical version of the simulation that can be used for diff testing (and as some weird documentation of the mechanics I suppose)
# Attributions
All graphics used with the `graphics` feature are from the Factorio Mod [Krastorio 2 Assets](https://codeberg.org/raiguard/Krastorio2Assets).
66 changes: 36 additions & 30 deletions src/app_state.rs

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/assembler/bucketed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -877,4 +877,8 @@ impl<RecipeIdxType: WeakIdxTrait, const NUM_INGS: usize, const NUM_OUTPUTS: usiz
data.11.into(),
)
}

fn num_assemblers(&self) -> usize {
self.hot_data.len() - self.holes.len()
}
}
54 changes: 54 additions & 0 deletions src/assembler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,58 @@ impl<
_ => unreachable!(),
}
}

pub fn num_assemblers(&self) -> usize {
let Self {
assemblers_0_1,
assemblers_1_1,
assemblers_2_1,
assemblers_2_2,
assemblers_2_3,
assemblers_3_1,
assemblers_4_1,
assemblers_5_1,
assemblers_6_1,
recipe: _,
} = self;

assemblers_0_1
.iter()
.map(|store| store.num_assemblers())
.sum::<usize>()
+ assemblers_1_1
.iter()
.map(|store| store.num_assemblers())
.sum::<usize>()
+ assemblers_2_1
.iter()
.map(|store| store.num_assemblers())
.sum::<usize>()
+ assemblers_2_2
.iter()
.map(|store| store.num_assemblers())
.sum::<usize>()
+ assemblers_2_3
.iter()
.map(|store| store.num_assemblers())
.sum::<usize>()
+ assemblers_3_1
.iter()
.map(|store| store.num_assemblers())
.sum::<usize>()
+ assemblers_4_1
.iter()
.map(|store| store.num_assemblers())
.sum::<usize>()
+ assemblers_5_1
.iter()
.map(|store| store.num_assemblers())
.sum::<usize>()
+ assemblers_6_1
.iter()
.map(|store| store.num_assemblers())
.sum::<usize>()
}
}

// FIXME:
Expand Down Expand Up @@ -799,6 +851,8 @@ pub trait MultiAssemblerStore<

ret
}

fn num_assemblers(&self) -> usize;
}

pub mod arrays {
Expand Down
61 changes: 11 additions & 50 deletions src/assembler/simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@
None => Either::Right(self.base_power_consumption.as_chunks().0.into_iter()),
};

let (timers, overlap) = self.timers.as_chunks_mut::<{ SIMDTYPE::LEN }>();

Check failure on line 240 in src/assembler/simd.rs

View workflow job for this annotation

GitHub Actions / Clippy

binding's name is too similar to existing binding

// FIXME:
// assert!(overlap.is_empty());
Expand Down Expand Up @@ -289,7 +289,7 @@
ing_mask_for_two_crafts & ings.simd_ge(our_ings_two_crafts);
}

let timer = SIMDTYPE::from_array(*timer_arr);

Check failure on line 292 in src/assembler/simd.rs

View workflow job for this annotation

GitHub Actions / Clippy

binding's name is too similar to existing binding

let new_timer_output_space = ing_mask.select(timer + increase, timer);
let new_timer_output_full = ing_mask.select(timer.saturating_add(increase), timer);
Expand Down Expand Up @@ -665,6 +665,7 @@
.iter()
.map(|&old_idx| old_idx + self.timers.len()),
);
self.holes.extend(other.len..(other.timers.len()));
self.len = old_len_stored;

let new_ings_max: [Box<[u8]>; NUM_INGS] = self
Expand All @@ -673,13 +674,7 @@
.zip(other.ings_max_insert)
.map(|(s, o)| {
let mut s = s.into_vec();
s.extend(
o.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);
s.extend(o.into_vec().into_iter().enumerate().map(|(_, v)| v));
s.into_boxed_slice()
})
.collect_array()
Expand All @@ -691,13 +686,7 @@
.zip(other.ings)
.map(|(s, o)| {
let mut s = s.into_vec();
s.extend(
o.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);
s.extend(o.into_vec().into_iter().enumerate().map(|(_, v)| v));
s.into_boxed_slice()
})
.collect_array()
Expand All @@ -708,13 +697,7 @@
.zip(other.outputs)
.map(|(s, o)| {
let mut s = s.into_vec();
s.extend(
o.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);
s.extend(o.into_vec().into_iter().enumerate().map(|(_, v)| v));
s.into_boxed_slice()
})
.collect_array()
Expand All @@ -726,7 +709,6 @@
.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);

Expand All @@ -737,7 +719,6 @@
.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);

Expand All @@ -748,7 +729,6 @@
.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);

Expand All @@ -759,7 +739,6 @@
.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);

Expand All @@ -770,7 +749,6 @@
.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);

Expand All @@ -781,7 +759,6 @@
.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);

Expand All @@ -792,7 +769,6 @@
.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);

Expand All @@ -803,7 +779,6 @@
.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);

Expand All @@ -814,7 +789,6 @@
.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);

Expand All @@ -825,39 +799,22 @@
.into_vec()
.into_iter()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);

let mut new_positions = self.positions.into_vec();
new_positions.extend(
other
.positions
.iter()
.copied()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);
new_positions.extend(other.positions.iter().copied().enumerate().map(|(_, v)| v));

let mut new_types = self.types.into_vec();
new_types.extend(
other
.types
.iter()
.copied()
.enumerate()
.take(other.len)
.map(|(_, v)| v),
);
new_types.extend(other.types.iter().copied().enumerate().map(|(_, v)| v));

let updates = IntoIterator::into_iter(other.positions)
.take(other.len)
.zip(other.types)
.enumerate()
.take(other.len)
.filter(move |(i, _)| !other.holes.contains(i))
.enumerate()
.filter(move |(_offs, (i, _))| !other.holes.contains(i))
.map(move |(new_index_offs, (old_index, (pos, ty)))| {
assert!(new_index_offs <= old_index);
IndexUpdateInfo {
Expand Down Expand Up @@ -1158,7 +1115,7 @@
);

take_mut::take(&mut self.bonus_productivity, |bonus_productivity| {
let mut bonus_productivity = bonus_productivity.into_vec();

Check failure on line 1118 in src/assembler/simd.rs

View workflow job for this annotation

GitHub Actions / Clippy

binding's name is too similar to existing binding
bonus_productivity.resize(new_len, 0);
bonus_productivity.into_boxed_slice()
});
Expand All @@ -1179,7 +1136,7 @@
);

take_mut::take(&mut self.raw_bonus_productivity, |bonus_productivity| {
let mut bonus_productivity = bonus_productivity.into_vec();

Check failure on line 1139 in src/assembler/simd.rs

View workflow job for this annotation

GitHub Actions / Clippy

binding's name is too similar to existing binding
bonus_productivity.resize(new_len, 0);
bonus_productivity.into_boxed_slice()
});
Expand Down Expand Up @@ -1342,6 +1299,10 @@
data.11.into(),
)
}

fn num_assemblers(&self) -> usize {
self.len - self.holes.len()
}
}

#[cfg(test)]
Expand Down
Loading
Loading