Skip to content

Instantly share code, notes, and snippets.

@Lucus16
Last active June 24, 2024 14:26
Show Gist options
  • Save Lucus16/5c9452277f1ff3d7a6d8b359db5b0582 to your computer and use it in GitHub Desktop.
Save Lucus16/5c9452277f1ff3d7a6d8b359db5b0582 to your computer and use it in GitHub Desktop.
A better fluid model for Factorio.
// Optimal flow speed is when pipes are at half pressure.
const Node = struct {
// All of these must be positive, I'm just using signed types to avoid casting.
height: i32,
width: i32,
contents: i32,
potential_inflow: i32,
potential_outflow: i32,
pub inline fn capacity(node: Node) i32 {
return node.width * node.height;
}
pub inline fn space(node: Node) i32 {
return node.capacity() - node.contents;
}
pub inline fn level(node: Node) i32 {
return @divTrunc(node.contents, node.width);
}
};
const Edge = struct {
node_a: *Node,
node_b: *Node,
// Positive means flow from A to B, negative means flow from B to A.
flow_ab: i32,
node_a_min_level: i32,
node_b_min_level: i32,
// TODO: Allow top-up valves by introducing a max_level or min_space.
};
inline fn muldiv(x: i32, multiplier: i32, divisor: i32) i32 {
return @intCast(@divTrunc(@as(i64, x) * @as(i64, multiplier), @as(i64, divisor)));
}
// Invariant: The flow through a pipe is never larger than the total amount of
// fluid in its input and never larger than the total amount of space in its
// output.
const Model = struct {
nodes: []Node,
edges: []Edge,
fn tick(model: Model) void {
// Clear flow potentials.
for (model.nodes) |*node| {
node.potential_inflow = 0;
node.potential_outflow = 0;
}
// Compute ideal next flow level, based on level difference.
for (model.edges) |*edge| {
const effective_node_a_level = @max(0, edge.node_a.level() - edge.node_a_min_level);
const effective_node_b_level = @max(0, edge.node_b.level() - edge.node_b_min_level);
const force_ab_from_level = effective_node_a_level - effective_node_b_level;
// Increase the flow by a portion of the level difference to model inertia.
// Don't go higher than 1/2.
edge.flow_ab += muldiv(force_ab_from_level, 1, 10);
// If desired, at this point you can add resistance to edges, which
// can reduce the maximum amount of flow based on the number edges
// being traversed.
// For example, to lower the speed exponentially with distance:
// edge.flow_ab = muldiv(edge.flow_ab, 99, 100);
// Or to lower it linearly with distance:
// edge.flow_ab = @max(0, edge.flow_ab - 10);
edge.node_a.potential_inflow += @max(0, -edge.flow_ab);
edge.node_a.potential_outflow += @max(0, edge.flow_ab);
edge.node_b.potential_inflow += @max(0, edge.flow_ab);
edge.node_b.potential_outflow += @max(0, -edge.flow_ab);
}
// Scale down flow based on available input fluid and output space.
for (model.edges) |*edge| {
if (edge.flow_ab > 0) {
// Flow from A to B.
const input_limited_flow = muldiv(edge.flow_ab, edge.node_a.contents, edge.node_a.potential_outflow);
const output_limited_flow = muldiv(edge.flow_ab, edge.node_b.space(), edge.node_b.potential_inflow);
edge.flow_ab = @min(edge.flow_ab, @min(input_limited_flow, output_limited_flow));
} else if (edge.flow_ab < 0) {
// Flow from B to A.
const flow_ba = -edge.flow_ab;
const input_limited_flow = muldiv(flow_ba, edge.node_b.contents, edge.node_b.potential_outflow);
const output_limited_flow = muldiv(flow_ba, edge.node_a.space(), edge.node_a.potential_inflow);
edge.flow_ab = -@min(flow_ba, @min(input_limited_flow, output_limited_flow));
}
}
// Apply flow. This must be a separate step from computing the flow
// since the flow computation is based on the node contents which are
// changed here.
for (model.edges) |edge| {
edge.node_a.contents -= edge.flow_ab;
edge.node_b.contents += edge.flow_ab;
}
}
};
pub fn main() !void {
var model = Model{
.nodes = &.{},
.edges = &.{},
};
model.tick();
}
@Lucus16
Copy link
Author

Lucus16 commented Jun 24, 2024

The only actual if statement can also easily be converted into branchless code due to its symmetry by the way, I just wrote it like this because it's more readable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment