Skip to content

Commit

Permalink
Fixed incorrect data in multicellular cell indexes (#4641)
Browse files Browse the repository at this point in the history
which caused a crash when regrowing and then despawning due to incorrect parent indexes for colony cells
  • Loading branch information
hhyyrylainen committed Dec 12, 2023
1 parent cdebee6 commit b731c4d
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,16 @@ public struct EarlyMulticellularSpeciesMember
// /// </summary>
// public bool SpeciesApplied;

public EarlyMulticellularSpeciesMember(EarlyMulticellularSpecies species, CellType cellType)
public EarlyMulticellularSpeciesMember(EarlyMulticellularSpecies species, CellType cellType,
int cellBodyPlanIndex)
{
if (cellBodyPlanIndex < 0 || cellBodyPlanIndex >= species.Cells.Count)
throw new ArgumentException("Bad body plan index given");

Species = species;
MulticellularCellType = cellType;

MulticellularBodyPlanPartIndex = species.CellTypes.FindIndex(c => c == cellType);

if (MulticellularBodyPlanPartIndex == -1)
{
MulticellularBodyPlanPartIndex = 0;

#if DEBUG
throw new ArgumentException("Multicellular growth given invalid first cell type");
#endif
}
MulticellularBodyPlanPartIndex = cellBodyPlanIndex;
}
}
}
11 changes: 9 additions & 2 deletions src/early_multicellular_stage/components/MulticellularGrowth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ public static class MulticellularGrowthHelpers
// Remove the starting compounds as this is a growth cell which shouldn't give free resources to the
// colony it joins
DelayedColonyOperationSystem.CreateDelayAttachedMicrobe(ref colonyPosition, entity,
multicellularGrowth.NextBodyPlanCellToGrowIndex,
cellTemplate, species, worldSimulation, recorder, notifySpawnTo, false);
multicellularGrowth.NextBodyPlanCellToGrowIndex, cellTemplate, species, worldSimulation, recorder,
notifySpawnTo, false);

++multicellularGrowth.NextBodyPlanCellToGrowIndex;
multicellularGrowth.CompoundsNeededForNextCell = null;
Expand Down Expand Up @@ -145,6 +145,13 @@ public static class MulticellularGrowthHelpers

var lostPartIndex = lostCell.Get<EarlyMulticellularSpeciesMember>().MulticellularBodyPlanPartIndex;

// If the lost index is the first cell, then it should be disbanding the colony. We don't need to keep
// track of when that will regrow as entirely new colonies will be created for the surviving members.
// This shouldn't really matter anyway as this growth object should be getting deleted anyway shortly along
// with the removed cell.
if (lostPartIndex == 0)
return;

// We need to reset our growth towards the next cell and instead replace the cell we just lost
multicellularGrowth.LostPartsOfBodyPlan ??= new List<int>();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Systems
{
using System;
using System.Linq;
using Components;
using DefaultEcs;
Expand Down Expand Up @@ -27,9 +28,23 @@ public sealed class DelayedColonyOperationSystem : AEntitySetSystem<float>
}

public static void CreateDelayAttachedMicrobe(ref WorldPosition colonyPosition, in Entity colonyEntity,
int colonyTargetIndex, CellTemplate cellTemplate, Species species, IWorldSimulation worldSimulation,
int colonyTargetIndex, CellTemplate cellTemplate, EarlyMulticellularSpecies species,
IWorldSimulation worldSimulation,
EntityCommandRecorder recorder, ISpawnSystem notifySpawnTo, bool giveStartingCompounds)
{
if (colonyTargetIndex == 0)
throw new ArgumentException("Cannot delay add the root colony cell");

int bodyPlanIndex = colonyTargetIndex;

if (bodyPlanIndex < 0 || bodyPlanIndex >= species.Cells.Count)
{
GD.PrintErr(
$"Correcting incorrect body plan index for delay attached cell from {bodyPlanIndex} to " +
"a valid value");
bodyPlanIndex = Mathf.Clamp(bodyPlanIndex, 0, species.Cells.Count - 1);
}

var attachPosition = new AttachedToEntity
{
AttachedTo = colonyEntity,
Expand All @@ -41,7 +56,8 @@ public sealed class DelayedColonyOperationSystem : AEntitySetSystem<float>

var weight = SpawnHelpers.SpawnMicrobeWithoutFinalizing(worldSimulation, species,
colonyPosition.Position + colonyPosition.Rotation.Xform(attachPosition.RelativePosition), true,
cellTemplate.CellType, recorder, out var member, MulticellularSpawnState.Bud, giveStartingCompounds);
(cellTemplate.CellType, bodyPlanIndex), recorder, out var member, MulticellularSpawnState.Bud,
giveStartingCompounds);

// Register with the spawn system to allow this entity to despawn if it gets cut off from the colony later
// or attaching fails
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,15 @@ protected override void Update(float delta, in Entity entity)

// Grow from the first cell to grow back, in the body plan grow order
multicellularGrowth.NextBodyPlanCellToGrowIndex = multicellularGrowth.LostPartsOfBodyPlan.Min();

if (multicellularGrowth.NextBodyPlanCellToGrowIndex <= 0)
throw new InvalidOperationException("Loaded bad next body plan index from regrow lost");

multicellularGrowth.LostPartsOfBodyPlan.Remove(multicellularGrowth.NextBodyPlanCellToGrowIndex);

// TODO: should this skip regrowing cells that already exist for some reason in the body?
// That can happen due to a problem elsewhere but then this will cause a duplicate cell to appear
// which will get reported by anyone seeing it
}
else if (multicellularGrowth.ResumeBodyPlanAfterReplacingLost != null)
{
Expand Down
7 changes: 4 additions & 3 deletions src/microbe_stage/MicrobeStage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,8 @@ public void MoveToMulticellular()
// Direct component setting is safe as we verified above we aren't running during a simulation update
microbe.Remove<MicrobeSpeciesMember>();
microbe.Set(new SpeciesMember(multicellularSpecies));
microbe.Set(new EarlyMulticellularSpeciesMember(multicellularSpecies, multicellularSpecies.CellTypes[0]));
microbe.Set(
new EarlyMulticellularSpeciesMember(multicellularSpecies, multicellularSpecies.CellTypes[0], 0));

microbe.Set(new MulticellularGrowth(multicellularSpecies));

Expand Down Expand Up @@ -776,7 +777,7 @@ protected override void SpawnPlayer()
}

var (recorder, _) = SpawnHelpers.SpawnMicrobeWithoutFinalizing(WorldSimulation, GameWorld.PlayerSpecies,
spawnLocation, false, null, out var entityRecord);
spawnLocation, false, (null, 0), out var entityRecord);

entityRecord.Set(new MicrobeEventCallbacks
{
Expand Down Expand Up @@ -990,7 +991,7 @@ private void OnSpawnEnemyCheatUsed(object sender, EventArgs e)
var playerPosition = Player.Get<WorldPosition>().Position;

var (recorder, weight) = SpawnHelpers.SpawnMicrobeWithoutFinalizing(WorldSimulation, randomSpecies,
playerPosition + Vector3.Forward * 20, true, null, out var entity);
playerPosition + Vector3.Forward * 20, true, (null, 0), out var entity);

// Make the cell despawn like normal
WorldSimulation.SpawnSystem.NotifyExternalEntitySpawned(entity,
Expand Down
48 changes: 33 additions & 15 deletions src/microbe_stage/Spawners.cs
Original file line number Diff line number Diff line change
Expand Up @@ -384,29 +384,36 @@ public static void SpawnCellBurstEffect(IWorldSimulation worldSimulation, Vector
}

public static void SpawnMicrobe(IWorldSimulation worldSimulation, Species species, Vector3 location,
bool aiControlled, CellType? multicellularCellType = null,
bool aiControlled, MulticellularSpawnState multicellularSpawnState = MulticellularSpawnState.Bud)
{
SpawnMicrobe(worldSimulation, species, location, aiControlled, (null, 0), multicellularSpawnState);
}

public static void SpawnMicrobe(IWorldSimulation worldSimulation, Species species, Vector3 location,
bool aiControlled, (CellType? MulticellularCellType, int CellBodyPlanIndex) multicellularData,
MulticellularSpawnState multicellularSpawnState = MulticellularSpawnState.Bud)
{
var (recorder, _) = SpawnMicrobeWithoutFinalizing(worldSimulation, species, location, aiControlled,
multicellularCellType, out _, multicellularSpawnState);
multicellularData, out _, multicellularSpawnState);

FinalizeEntitySpawn(recorder, worldSimulation);
}

public static (EntityCommandRecorder Recorder, float Weight) SpawnMicrobeWithoutFinalizing(
IWorldSimulation worldSimulation, Species species,
Vector3 location, bool aiControlled, CellType? multicellularCellType, out EntityRecord entity,
Vector3 location, bool aiControlled, (CellType? MulticellularCellType, int CellBodyPlanIndex) multicellularData,
out EntityRecord entity,
MulticellularSpawnState multicellularSpawnState = MulticellularSpawnState.Bud, Random? random = null)
{
var recorder = worldSimulation.StartRecordingEntityCommands();
return (recorder, SpawnMicrobeWithoutFinalizing(worldSimulation, species, location, aiControlled,
multicellularCellType,
recorder, out entity, multicellularSpawnState, true, random));
multicellularData, recorder, out entity, multicellularSpawnState, true, random));
}

public static float SpawnMicrobeWithoutFinalizing(IWorldSimulation worldSimulation, Species species,
Vector3 location, bool aiControlled, CellType? multicellularCellType, EntityCommandRecorder recorder,
out EntityRecord entity, MulticellularSpawnState multicellularSpawnState = MulticellularSpawnState.Bud,
Vector3 location, bool aiControlled, (CellType? MulticellularCellType, int CellBodyPlanIndex) multicellularData,
EntityCommandRecorder recorder, out EntityRecord entity,
MulticellularSpawnState multicellularSpawnState = MulticellularSpawnState.Bud,
bool giveInitialCompounds = true, Random? random = null)
{
// If this method is modified it must be ensured that CellPropertiesHelpers.ReApplyCellTypeProperties and
Expand Down Expand Up @@ -465,21 +472,31 @@ public static void SpawnCellBurstEffect(IWorldSimulation worldSimulation, Vector
multicellular = earlyMulticellularSpecies;
CellType resolvedCellType;

if (multicellularCellType != null)
if (multicellularData.MulticellularCellType != null)
{
// Non-first cell in an early multicellular colony
if (multicellularData.CellBodyPlanIndex == 0)
{
throw new ArgumentException(
"Multicellular cell type needs to be accompanied by the body plan index");
}

resolvedCellType = multicellularCellType;
resolvedCellType = multicellularData.MulticellularCellType;

usedCellProperties = multicellularCellType;
var properties = new CellProperties(multicellularCellType);
usedCellProperties = multicellularData.MulticellularCellType;
var properties = new CellProperties(multicellularData.MulticellularCellType);
membraneType = properties.MembraneType;
entity.Set(properties);

// TODO: should this also be given MulticellularGrowth to allow this to grow fully if the colony splits
}
else
{
if (multicellularData.CellBodyPlanIndex != 0)
{
throw new ArgumentException("First Multicellular cell must have body plan index of 0");
}

resolvedCellType = earlyMulticellularSpecies.Cells[0].CellType;

usedCellProperties = resolvedCellType;
Expand All @@ -490,7 +507,8 @@ public static void SpawnCellBurstEffect(IWorldSimulation worldSimulation, Vector
entity.Set(new MulticellularGrowth(earlyMulticellularSpecies));
}

entity.Set(new EarlyMulticellularSpeciesMember(earlyMulticellularSpecies, resolvedCellType));
entity.Set(new EarlyMulticellularSpeciesMember(earlyMulticellularSpecies, resolvedCellType,
multicellularData.CellBodyPlanIndex));
}
else if (species is MicrobeSpecies microbeSpecies)
{
Expand All @@ -504,7 +522,7 @@ public static void SpawnCellBurstEffect(IWorldSimulation worldSimulation, Vector
membraneType = properties.MembraneType;
entity.Set(properties);

if (multicellularCellType != null)
if (multicellularData.MulticellularCellType != null)
GD.PrintErr("Multicellular cell type may not be set when spawning a MicrobeSpecies instance");
}
else
Expand Down Expand Up @@ -728,7 +746,7 @@ public static List<Vector3> CalculateBacteriaSwarmPositions(Vector3 initialLocat
IWorldSimulation worldSimulation, Species species,
Vector3 location, out EntityRecord entity)
{
return SpawnMicrobeWithoutFinalizing(worldSimulation, species, location, true, null, out entity,
return SpawnMicrobeWithoutFinalizing(worldSimulation, species, location, true, (null, 0), out entity,
MulticellularSpawnState.Bud);
}

Expand Down Expand Up @@ -1007,7 +1025,7 @@ public override SpawnQueue Spawn(IWorldSimulation worldSimulation, Vector3 locat
{
// The true here is that this is AI controlled
var (recorder, weight) = SpawnHelpers.SpawnMicrobeWithoutFinalizing(worldSimulation, Species,
location, true, null, out entity, MulticellularSpawnState.ChanceForFullColony);
location, true, (null, 0), out entity, MulticellularSpawnState.ChanceForFullColony);
ModLoader.ModInterface.TriggerOnMicrobeSpawned(entity);
Expand Down
2 changes: 1 addition & 1 deletion src/microbe_stage/components/CellProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ public static bool IsMembraneReady(this ref CellProperties cellProperties)

// Create the one daughter cell.
var (recorder, weight) = SpawnHelpers.SpawnMicrobeWithoutFinalizing(worldSimulation, species, spawnPosition,
true, null, out var copyEntity, multicellularSpawnState);
true, (null, 0), out var copyEntity, multicellularSpawnState);

// Since the daughter spawns right next to the cell, it should face the same way to avoid colliding
// This probably wastes a bit of memory but should be fine to overwrite the WorldPosition component like
Expand Down

0 comments on commit b731c4d

Please sign in to comment.