Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance changes to server physics simulation #1403

Merged
merged 20 commits into from
Oct 12, 2023
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
10 changes: 9 additions & 1 deletion src/main/java/cam72cam/immersiverailroading/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,15 @@ public static class ConfigDebug {
public static boolean defaultAugmentComputer = false;
@Comment("Warn if a physics tick takes more than the given time in ms")
@Range(min = 1, max = 100)
public static long physicsWarnThresholdMs = 20;
public static int physicsWarnThresholdMs = 20;

@Comment("Warn if a physics total iteration takes more than the given time in ms")
@Range(min = 1, max = 100)
public static int physicsWarnTotalThresholdMs = 40;

@Comment("Number of physics steps to cache for future movement / send in packets. DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING")
@Range(min = 10, max = 60)
public static int physicsFutureTicks = 10;
}

public static boolean isFuelRequired(Gauge gauge) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.function.Consumer;
import java.util.function.Function;

import cam72cam.immersiverailroading.entity.physics.Consist;
import cam72cam.immersiverailroading.entity.physics.Simulation;
import cam72cam.immersiverailroading.entity.physics.SimulationState;
import cam72cam.immersiverailroading.library.ModelComponentType;
Expand Down Expand Up @@ -69,21 +70,26 @@ public String toString() {
@TagSync
@TagField(value = "CoupledFront", mapper = StrictTagMapper.class)
private UUID coupledFront = null;
@TagField("lastKnownFront")
private Vec3i lastKnownFront = null;
@TagSync
@TagField("frontCouplerEngaged")
private boolean frontCouplerEngaged = true;

@TagSync
@TagField(value = "CoupledBack", mapper = StrictTagMapper.class)
private UUID coupledBack = null;
@TagField("lastKnownRear")
private Vec3i lastKnownRear= null;
@TagSync
@TagField("backCouplerEngaged")
private boolean backCouplerEngaged = true;


@TagField(value = "consist", mapper = Consist.TagMapper.class)
public Consist consist = new Consist(Collections.emptyList(), Collections.emptyList());

@TagField("lastKnownFront")
public Vec3i lastKnownFront = null;
@TagField("lastKnownRear")
public Vec3i lastKnownRear = null;

@TagSync
@TagField("hasElectricalPower")
private boolean hasElectricalPower;
Expand Down Expand Up @@ -114,8 +120,8 @@ public ClickResult onClick(Player player, Player.Hand hand) {
player.sendMessage(ChatText.COUPLER_DISENGAGED.getMessage(coupler));
}
} else {
if (this.isCoupled(coupler) && this.isCouplerEngaged(coupler)) {
EntityCoupleableRollingStock coupled = this.getCoupled(coupler);
EntityCoupleableRollingStock coupled = this.getCoupled(coupler);
if (this.isCoupled(coupler) && this.isCouplerEngaged(coupler) && coupled != null) {
player.sendMessage(ChatText.COUPLER_STATUS_COUPLED.getMessage(
coupler,
coupled.getDefinition().name(),
Expand Down Expand Up @@ -181,27 +187,41 @@ public void onTick() {

hadElectricalPower = hasElectricalPower();

if (this.getCurrentState() != null && !this.getCurrentState().canBeUnloaded || ConfigDebug.keepStockLoaded) {
if (this.getCurrentState() != null && !this.getCurrentState().atRest || ConfigDebug.keepStockLoaded) {
keepLoaded();
}

SimulationState state = getCurrentState();
if (state != null) {
setCoupledUUID(CouplerType.FRONT, state.interactingFront);
setCoupledUUID(CouplerType.BACK, state.interactingRear);
consist = state.consist;

if (getCoupledUUID(CouplerType.FRONT) != null) {
EntityCoupleableRollingStock front = getCoupled(CouplerType.FRONT);
if (front != null) {
lastKnownFront = front.getBlockPosition();
}
} else {
lastKnownFront = null;
}
if (getCoupledUUID(CouplerType.BACK) != null) {
EntityCoupleableRollingStock rear = getCoupled(CouplerType.BACK);
if (rear != null) {
lastKnownRear = rear.getBlockPosition();
}
} else {
lastKnownRear = null;
}
}
}

public void keepLoaded() {
World world = getWorld();
world.keepLoaded(getBlockPosition());
world.keepLoaded(new Vec3i(this.guessCouplerPosition(CouplerType.FRONT)));
world.keepLoaded(new Vec3i(this.guessCouplerPosition(CouplerType.BACK)));
if (this.lastKnownFront != null) {
world.keepLoaded(this.lastKnownFront);
}
if (this.lastKnownRear != null) {
world.keepLoaded(this.lastKnownRear);
if (getCurrentState() != null && !getCurrentState().atRest) {
world.keepLoaded(new Vec3i(this.guessCouplerPosition(CouplerType.FRONT)));
world.keepLoaded(new Vec3i(this.guessCouplerPosition(CouplerType.BACK)));
}
}

Expand All @@ -220,7 +240,6 @@ public final void setCoupledUUID(CouplerType coupler, UUID id) {
if (Objects.equals(target, id)) {
return;
}

if (target == null && isCouplerEngaged(coupler)) {
// Technically this fires the coupling sound twice (once for each entity)
new SoundPacket(getDefinition().couple_sound,
Expand All @@ -229,24 +248,12 @@ public final void setCoupledUUID(CouplerType coupler, UUID id) {
.sendToObserving(this);
}

EntityCoupleableRollingStock coupled = id != null ? findByUUID(id) : null;

switch (coupler) {
case FRONT:
coupledFront = id;
if (coupledFront == null) {
lastKnownFront = null;
} else if (coupled != null){
lastKnownFront = new Vec3i(coupled.getPosition());
}
break;
case BACK:
coupledBack = id;
if (coupledBack == null) {
lastKnownRear = null;
} else if (coupled != null){
lastKnownFront = new Vec3i(coupled.getPosition());
}
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public abstract class EntityMoveableRollingStock extends EntityRidableRollingSto

public long lastCollision = 0;

public boolean newlyPlaced = false;

@Override
public void load(TagCompound data) {
super.load(data);
Expand Down
174 changes: 122 additions & 52 deletions src/main/java/cam72cam/immersiverailroading/entity/physics/Consist.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import cam72cam.immersiverailroading.util.Speed;
import cam72cam.mod.math.Vec3d;
import cam72cam.mod.math.Vec3i;
import cam72cam.mod.serialization.TagCompound;
import cam72cam.mod.serialization.TagField;

import java.util.*;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -379,7 +381,7 @@ public void correctDistance() {
}
}

public static Map<UUID, SimulationState> iterate(Map<UUID, SimulationState> states, List<Vec3i> blocksAlreadyBroken) {
public static void iterate(Map<UUID, SimulationState> states, Map<UUID, SimulationState> nextStateMap, List<Vec3i> blocksAlreadyBroken) {
debug = false;
// ordered
List<Particle> particles = new ArrayList<>();
Expand Down Expand Up @@ -466,17 +468,90 @@ public static Map<UUID, SimulationState> iterate(Map<UUID, SimulationState> stat
prevParticle = currParticle;
}

// Propagate dirty flag
boolean canBeUnloaded = consist.stream().allMatch(p -> p.state.velocity == 0 && state.forcesNewtons() < state.frictionNewtons());
consist.forEach(p -> p.state.canBeUnloaded = canBeUnloaded);
List<UUID> ids = consist.stream().map(x -> x.state.config.id).collect(Collectors.toList());
consist.forEach(p -> p.state.consist = ids);
// Propogate brake pressure


for (Particle particle : consist) {
if (particle.nextLink != null) {
particle.nextLink.setup();
}
}

List<SimulationState> linked = new ArrayList<>();
for (Particle source : consist) {
linked.add(source.state);

if (source.nextLink == null || !source.nextLink.coupled || !source.state.config.hasPressureBrake) {
// No further linked couplings
// Spread brake pressure

float desiredBrakePressure = (float) linked.stream()
.filter(s -> s.config.desiredBrakePressure != null)
.mapToDouble(s -> s.config.desiredBrakePressure)
.max().orElse(0);

boolean needsBrakeEqualization = linked.stream().anyMatch(s -> s.config.hasPressureBrake && Math.abs(s.brakePressure - desiredBrakePressure) > 0.01);

if (needsBrakeEqualization) {
double brakePressureDelta = 0.1 / linked.stream().filter(s -> s.config.hasPressureBrake).count();
linked.forEach(p -> {
if (p.config.hasPressureBrake) {
if (Config.ImmersionConfig.instantBrakePressure) {
p.brakePressure = desiredBrakePressure;
} else {
if (p.brakePressure > desiredBrakePressure + brakePressureDelta) {
p.brakePressure -= brakePressureDelta;
} else if (p.brakePressure < desiredBrakePressure - brakePressureDelta) {
p.brakePressure += brakePressureDelta;
} else {
p.brakePressure = desiredBrakePressure;
}
}
}
});
}

linked.clear();
}
}




// Propagate dirty flag
boolean dirty = consist.stream().anyMatch(p -> p.state.dirty);
boolean atRest = consist.stream().allMatch(p -> p.state.atRest());
boolean missingNextStates = consist.stream().anyMatch(p -> !nextStateMap.containsKey(p.state.config.id));

if (dirty) {
consist.forEach(p -> p.state.dirty = true);
particles.addAll(consist);
}

if (atRest && !dirty) {
// Copy existing states
for (Particle particle : consist) {
nextStateMap.put(particle.state.config.id, particle.state.next());
}
Simulation.restStates += consist.size();
} else {
if (dirty || missingNextStates) {
particles.addAll(consist);
Simulation.calculatedStates += consist.size();
} else {
Simulation.keptStates += consist.size();
}
}

// Propagate atRest
consist.forEach(p -> p.state.atRest = atRest);

// Store consist info
if (dirty) {
Consist c = new Consist(
consist.stream().map(x -> x.state.config.id).collect(Collectors.toList()),
consist.stream().map(x -> new Vec3i(x.state.position)).collect(Collectors.toList())
);
consist.forEach(p -> p.state.consist = c);
}

// Make sure we can't accidentally hook into any of the processed states from this consist
Expand All @@ -489,50 +564,6 @@ public static Map<UUID, SimulationState> iterate(Map<UUID, SimulationState> stat
double stepsPerTick = 40;
double dt_S = (1 / (ticksPerSecond * stepsPerTick));

for (Particle particle : particles) {
if (particle.nextLink != null) {
particle.nextLink.setup();
}
}

List<SimulationState> linked = new ArrayList<>();
for (Particle source : particles) {
linked.add(source.state);

if (source.nextLink == null || !source.nextLink.coupled || !source.state.config.hasPressureBrake) {
// No further linked couplings
// Spread brake pressure

float desiredBrakePressure = (float) linked.stream()
.filter(s -> s.config.desiredBrakePressure != null)
.mapToDouble(s -> s.config.desiredBrakePressure)
.max().orElse(0);

boolean needsBrakeEqualization = linked.stream().anyMatch(s -> s.config.hasPressureBrake && Math.abs(s.brakePressure - desiredBrakePressure) > 0.01);

if (needsBrakeEqualization) {
double brakePressureDelta = 0.1 / linked.stream().filter(s -> s.config.hasPressureBrake).count();
linked.forEach(p -> {
if (p.config.hasPressureBrake) {
if (Config.ImmersionConfig.instantBrakePressure) {
p.brakePressure = desiredBrakePressure;
} else {
if (p.brakePressure > desiredBrakePressure + brakePressureDelta) {
p.brakePressure -= brakePressureDelta;
} else if (p.brakePressure < desiredBrakePressure - brakePressureDelta) {
p.brakePressure += brakePressureDelta;
} else {
p.brakePressure = desiredBrakePressure;
}
}
}
});
}

linked.clear();
continue;
}
}


// Spread forces
Expand Down Expand Up @@ -570,7 +601,9 @@ public static Map<UUID, SimulationState> iterate(Map<UUID, SimulationState> stat

// Generate new states
try {
return particles.stream().map(particle -> particle.applyToState(blocksAlreadyBroken)).collect(Collectors.toMap(s -> s.config.id, s -> s));
for (Particle particle : particles) {
nextStateMap.put(particle.state.config.id, particle.applyToState(blocksAlreadyBroken));
}
} catch (Exception ex) {
for (SimulationState state : states.values()) {
ImmersiveRailroading.debug("State: %s (%s, %s)", state.config.id, state.interactingFront, state.interactingRear);
Expand All @@ -582,4 +615,41 @@ public static Map<UUID, SimulationState> iterate(Map<UUID, SimulationState> stat
throw ex;
}
}


// For dirty propagation
public List<UUID> ids;
// For chunk loading
public List<Vec3i> positions;

public Consist(List<UUID> consistIDs, List<Vec3i> consistPositions) {
this.ids = consistIDs;
this.positions = consistPositions;
}

public static class TagMapper implements cam72cam.mod.serialization.TagMapper<Consist> {
@Override
public TagAccessor<Consist> apply(Class<Consist> type, String fieldName, TagField tag) {
return new TagAccessor<Consist>(
(d, o) -> {
if (o != null) {
d.set(fieldName, new TagCompound()
.setList("ids", o.ids, u -> new TagCompound().setUUID("id", u))
.setList("pos", o.positions, p -> new TagCompound().setVec3i("p", p))
);
}
},
// TODO we could fallback to lastKnownFront/Rear
d -> d.hasKey(fieldName) ? new Consist(
d.get(fieldName).getList("ids", t -> t.getUUID("id")),
d.get(fieldName).getList("pos", t -> t.getVec3i("p"))
) : new Consist(Collections.emptyList(), Collections.emptyList())
) {
@Override
public boolean applyIfMissing() {
return true;
}
};
}
}
}
Loading
Loading