Skip to content
Open
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
4 changes: 4 additions & 0 deletions simulation_parameters/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ public static class Constants
public const float MEMBRANE_WAVE_HEIGHT_MULTIPLIER_CELL_WALL = 0.015f;
public const float MEMBRANE_ENGULF_ANIMATION_DISTANCE = 1.25f;

public const float MEMBRANE_MIDDLE_POINT_OVERREACH = 0.5f;
public const float MEMBRANE_NEIGHBOUR_MAX_DISTANCE_BETWEEN_CENTERS = 750.0f;
public const float MEMBRANE_NEIGHBOUR_MAX_DISTANCE_BETWEEN_VERTICES = 150.0f;

/// <summary>
/// BASE MOVEMENT ATP cost. Cancels out a little bit more then one cytoplasm's glycolysis
/// </summary>
Expand Down
218 changes: 199 additions & 19 deletions src/microbe_stage/IMembraneDataSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,53 @@ public interface IMembraneDataSource
{
public Vector2[] HexPositions { get; }
public int HexPositionCount { get; }
public Vector2[]? MulticellularPositions { get; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding all of this extra data makes this a ton heavier, enough so that I fear for the efficiency of the overall design being able to handle things...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly do you mean by "being able to handle things"? I unfortunatelly need all of this data to make good membrane stretch generation. What would you propose here instead?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way I made the hashing and finding stuff is that I bedgrudginly accepted that the game has to hash the full list of positions, and that works fine now. But this PR increases the amount of hashing effort 5x, and makes membranes unique based on colony position, meaning that the overall caching system for membranes might not even make sense. So by adding a lot of heavy weight to the membrane caching system might hinder it so much that the entire concept needs to be rethought.

I suggested a few things to try to make it lighter (by using a single list, and only verifying some properties), but there might be a fundamental limit that causes problems with doing so much new stuff in the membrane caching and generation.

public Vector2? CellPositionInMulticellular { get; }
public int[]? MulticellularOrientations { get; }
public int? CellOrientation { get; }
public MembraneType Type { get; }
public bool IsPreMulticellularStretch { get; }
}

/// <summary>
/// Struct that holds parameters about a membrane generation request
/// </summary>
public struct MembraneGenerationParameters : IMembraneDataSource
{
public MembraneGenerationParameters(Vector2[] hexPositions, int hexPositionCount, MembraneType type,
Vector2[] multicellularPositions, Vector2 thisCellPosition, int[]? multicellularOrientations,
int? thisCellOrientation, bool isPreMulticellularStretch = false)
: this(hexPositions, hexPositionCount, type)
{
MulticellularPositions = multicellularPositions;
CellPositionInMulticellular = thisCellPosition;
MulticellularOrientations = multicellularOrientations;
CellOrientation = thisCellOrientation;
IsPreMulticellularStretch = isPreMulticellularStretch;
}

public MembraneGenerationParameters(Vector2[] hexPositions, int hexPositionCount, MembraneType type)
{
HexPositions = hexPositions;
HexPositionCount = hexPositionCount;
Type = type;
}

public int[]? MulticellularOrientations { get; }

public int? CellOrientation { get; }

public Vector2[] HexPositions { get; }

public Vector2[]? MulticellularPositions { get; }

public Vector2? CellPositionInMulticellular { get; }

public int HexPositionCount { get; }

public MembraneType Type { get; }

public bool IsPreMulticellularStretch { get; set; }
}

/// <summary>
Expand Down Expand Up @@ -89,7 +117,7 @@ public static MembranePointData GetOrComputeMembraneShape(IReadOnlyList<IPositio

var cache = ProceduralDataCache.Instance;

var hash = ComputeMembraneDataHash(hexes, length, membraneType);
var hash = new MembraneGenerationParameters(hexes, length, membraneType).ComputeMembraneDataHash();

var result = cache.ReadMembraneData(hash);

Expand All @@ -105,63 +133,215 @@ public static MembranePointData GetOrComputeMembraneShape(IReadOnlyList<IPositio
// Need to compute the data now, it doesn't exist in the cache
var generator = MembraneShapeGenerator.GetThreadSpecificGenerator();

result = generator.GenerateShape(hexes, length, membraneType);
result = generator.GenerateMicrobeShape(hexes, length, membraneType);

cache.WriteMembraneData(ref result);
return result;
}

public static long ComputeMembraneDataHash(Vector2[] positions, int count, MembraneType type)
public static long ComputeMembraneDataHash(this IMembraneDataSource dataSource)
{
var nameHash = type.InternalName.GetHashCode();
const long prime1 = 1099511628211L;
const long prime2 = 1409;
const long prime3 = 7793;

var nameHash = dataSource.Type.InternalName.GetHashCode();

unchecked
{
long hash = 1409 + nameHash + ((long)nameHash << 28);
long hash = prime2 + nameHash + ((long)nameHash << 28);

hash ^= (dataSource.HexPositionCount + 1) * prime3;

hash ^= (count + 1) * 7793;
int hashMultiply = 1;
for (int i = 0; i < dataSource.HexPositionCount; ++i)
{
hash = (hash * prime1) ^ BitConverter.SingleToInt32Bits(dataSource.HexPositions[i].X);
hash = (hash * prime1) ^ BitConverter.SingleToInt32Bits(dataSource.HexPositions[i].Y);
}

if (dataSource.CellPositionInMulticellular != null)
{
hash = (hash * prime1) ^ BitConverter.SingleToInt32Bits(dataSource.CellPositionInMulticellular.Value.X);
hash = (hash * prime1) ^ BitConverter.SingleToInt32Bits(dataSource.CellPositionInMulticellular.Value.Y);
}

if (dataSource.MulticellularPositions != null)
{
for (int i = 0; i < dataSource.MulticellularPositions.Length; ++i)
{
hash = (hash * prime1) ^ BitConverter.SingleToInt32Bits(dataSource.MulticellularPositions[i].X);
hash = (hash * prime1) ^ BitConverter.SingleToInt32Bits(dataSource.MulticellularPositions[i].Y);
}
}

if (dataSource.CellOrientation != null)
{
hash = (hash * prime1) ^ dataSource.CellOrientation.Value;
}

for (int i = 0; i < count; ++i)
if (dataSource.MulticellularOrientations != null)
{
var posHash = positions[i].GetHashCode();
for (int i = 0; i < dataSource.MulticellularOrientations.Length; ++i)
{
hash = (hash * prime1) ^ dataSource.MulticellularOrientations[i];
}
}

// TODO: switch to using rotate left here once we can (after Godot 4)
hash ^= (hashMultiply * posHash) ^ ((5081L * hashMultiply * hashMultiply + posHash) << 32);
++hashMultiply;
if (dataSource.IsPreMulticellularStretch)
{
hash = (hash * prime1) ^ 1;
}

return hash;
}
}

public static long ComputeMembraneDataHash(this IMembraneDataSource dataSource)
{
return ComputeMembraneDataHash(dataSource.HexPositions, dataSource.HexPositionCount, dataSource.Type);
}

public static bool MembraneDataFieldsEqual(this IMembraneDataSource dataSource, IMembraneDataSource other)
{
return dataSource.MembraneDataFieldsEqual(other.HexPositions, other.HexPositionCount, other.Type);
return dataSource.MembraneDataFieldsEqual(other.HexPositions, other.HexPositionCount, other.Type,
other.MulticellularPositions, other.CellPositionInMulticellular, other.MulticellularOrientations,
other.CellOrientation);
}

public static bool MembraneDataFieldsEqual(this IMembraneDataSource dataSource, Vector2[] otherPoints,
int otherPointCount, MembraneType otherType)
int otherPointCount, MembraneType otherType, Vector2[]? multicellularPositions = null,
Vector2? cellPositionInMulticellular = null, int[]? multicellularOrientations = null,
int? cellOrientationInMulticellular = null)
{
if (!dataSource.Type.Equals(otherType))
{
GD.PrintErr($"Membrane cache Type mismatch: {dataSource.Type} != {otherType}");
return false;
}

if (dataSource.HexPositionCount != otherPointCount)
{
GD.PrintErr("Membrane cache HexPositionCount mismatch: " +
$"{dataSource.HexPositionCount} != {otherPointCount}");
return false;
}

var count = dataSource.HexPositionCount;

var sourcePoints = dataSource.HexPositions;

if (dataSource.MulticellularPositions != null || multicellularPositions != null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks quite bad in terms of new performance drain, so I would only keep the most important comparison and put all else behind a #if DEBUG condition check to still help catch bugs but not eat up runtime performance for players.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense

{
if (dataSource.MulticellularPositions == null || multicellularPositions == null)
{
GD.PrintErr("Membrane cache MulticellularPositions null mismatch: " +
$"source={dataSource.MulticellularPositions == null} " +
$"other={multicellularPositions == null}");
return false;
}

if (dataSource.MulticellularPositions.Length != multicellularPositions.Length)
{
GD.PrintErr("Membrane cache MulticellularPositions.Length mismatch: " +
$"{dataSource.MulticellularPositions.Length} != " +
$"{multicellularPositions.Length}");
return false;
}

for (int i = 0; i < dataSource.MulticellularPositions.Length; ++i)
{
if (dataSource.MulticellularPositions[i] != multicellularPositions[i])
{
GD.PrintErr($"Membrane cache MulticellularPositions[{i}] mismatch: " +
$"{dataSource.MulticellularPositions[i]} != " +
$"{multicellularPositions[i]}");
return false;
}
}
}

if (dataSource.MulticellularOrientations != null || multicellularOrientations != null)
{
if (dataSource.MulticellularOrientations == null || multicellularOrientations == null)
{
GD.PrintErr("Membrane cache MulticellularOrientations null mismatch: " +
$"source={dataSource.MulticellularOrientations == null} " +
$"other={multicellularOrientations == null}");
return false;
}

if (dataSource.MulticellularOrientations.Length != multicellularOrientations.Length)
{
GD.PrintErr("Membrane cache MulticellularOrientations length mismatch:" +
$" {dataSource.MulticellularOrientations.Length} != " +
$"{multicellularOrientations.Length}");
return false;
}

for (int i = 0; i < dataSource.MulticellularOrientations.Length; ++i)
{
if (dataSource.MulticellularOrientations[i] != multicellularOrientations[i])
{
GD.PrintErr($"Membrane cache MulticellularOrientations[{i}] mismatch: " +
$"{dataSource.MulticellularOrientations[i]} != " +
$"{multicellularOrientations[i]}");
return false;
}
}
}

if (dataSource.CellPositionInMulticellular != null)
{
if (cellPositionInMulticellular == null)
{
GD.PrintErr("Membrane cache CellPositionInMulticellular should not be null");
return false;
}

if (!dataSource.CellPositionInMulticellular.Equals(cellPositionInMulticellular))
{
GD.PrintErr("Membrane cache CellPositionInMulticellular mismatch: " +
$"{dataSource.CellPositionInMulticellular} != " +
$"{cellPositionInMulticellular}");
return false;
}
}
else
{
if (cellPositionInMulticellular != null)
{
GD.PrintErr("Membrane cache CellPositionInMulticellular should be null");
return false;
}
}

if (dataSource.CellOrientation != null)
{
if (cellOrientationInMulticellular == null)
{
GD.PrintErr("Membrane cache CellOrientation should not be null");
return false;
}

if (!dataSource.CellOrientation.Equals(cellOrientationInMulticellular))
{
GD.PrintErr("Membrane cache CellOrientation mismatch: " +
$"{dataSource.CellOrientation} != {cellOrientationInMulticellular}");
return false;
}
}
else
{
if (cellOrientationInMulticellular != null)
{
GD.PrintErr("Membrane cache CellOrientation should be null");
return false;
}
}

for (int i = 0; i < count; ++i)
{
if (sourcePoints[i] != otherPoints[i])
{
GD.PrintErr($"Membrane cache HexPositions[{i}] mismatch: " +
$"{sourcePoints[i]} != {otherPoints[i]}");
return false;
}
}

return true;
Expand Down
Loading