Skip to content

Commit

Permalink
Merge from main
Browse files Browse the repository at this point in the history
  • Loading branch information
thygrrr committed Sep 27, 2024
2 parents 8f045b1 + 67393e4 commit 6100870
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 8 deletions.
159 changes: 159 additions & 0 deletions fennecs.tests/ArchetypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,165 @@ public void Moved_Entity_Leaves_Archetype()
Assert.Equal(2, queryAll.Count);
Assert.Equal(1, queryInt.Count);
}

// Verifies fix to https://github.com/outfox/fennecs/issues/23
[Fact]
public void Remaining_Entity_Metas_Updated_Upon_Delete()
{
using var world = new World();
Entity e1 = world.Spawn().Add(1);
Entity e2 = world.Spawn().Add(2);
e1.Despawn();
Assert.Equal(2, e2.Ref<int>());

Entity e3 = world.Spawn().Add(3);
e2.Despawn();
bool e3_seen_in_query_alive_and_with_val_3 = false;
bool dead_entity_in_query = false;
world.Query<int>().Stream().For((Entity entity, ref int val) =>
{
if (entity.Alive && val == 3)
{
e3_seen_in_query_alive_and_with_val_3 = true;
}

if (!entity.Alive)
{
dead_entity_in_query = true;
}
});
Assert.True(e3_seen_in_query_alive_and_with_val_3);
Assert.False(dead_entity_in_query);

bool e3_seen_in_world_iteration_alive_and_with_val_3 = false;
bool dead_entity_in_world_iteration = false;
foreach (Entity entity in world)
{
if (entity.Alive && entity.Ref<int>() == 3)
{
e3_seen_in_world_iteration_alive_and_with_val_3 = true;
}

if (!entity.Alive)
{
dead_entity_in_world_iteration = true;
}
}
Assert.True(e3_seen_in_world_iteration_alive_and_with_val_3);
Assert.False(dead_entity_in_world_iteration);
}

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(10)]
[InlineData(69)]
[InlineData(420)]
[InlineData(10_000)]
public void Meta_Integrity_After_Despawn(int count)
{
using var world = new World();

Entity e1 = world.Spawn().Add(1);

var entities = new Entity[count];
for (var i = 0; i < entities.Length; i++)
{
entities[i] = world.Spawn().Add(i);
}

world.Despawn(e1);

for (var i = 0; i < entities.Length; i++)
{
var entity = entities[i];
Assert.True(world.IsAlive(entity));

// Metas patched?
Assert.Equal(entity, world.GetEntityMeta(entity).Identity);
}
}

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(10)]
[InlineData(69)]
[InlineData(420)]
[InlineData(10_000)]
public void Components_Integrity_After_Despawn(int count)
{
using var world = new World();

var e1 = world.Spawn().Add(-1);
var e2 = world.Spawn().Add(-2);

var entities = new List<Entity>(count);
for (var i = 0; i < count; i++)
{
entities.Add(world.Spawn().Add(i));
}

world.Despawn(e1);

for (var i = 0; i < count; i++)
{
var entity = entities[i];
entity.Add((short) i);
}

world.Despawn(e2);

for (var i = 0; i < count; i++)
{
var entity = entities[i];
Assert.True(world.IsAlive(entity));

// Components correct?
Assert.Equal(i, entity.Ref<int>());
Assert.Equal(i, entity.Ref<short>());
}
}

[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(10)]
[InlineData(69)]
[InlineData(420)]
[InlineData(10_000)]
public void Components_Integrity_After_Truncate(int count)
{
using var world = new World();

var entities = new List<Entity>(count);
for (var i = 0; i < count; i++)
{
entities.Add(world.Spawn().Add(i));
}

world.GetEntityMeta(entities[0]).Archetype.Truncate(10);
entities = entities.Take(10).ToList();


for (var i = 0; i < entities.Count; i++)
{
var entity = entities[i];
entity.Add((short) i);
}

for (var i = 0; i < entities.Count; i++)
{
var entity = entities[i];
Assert.True(world.IsAlive(entity));

// Components correct?
Assert.Equal(i, entity.Ref<int>());
Assert.Equal(i, entity.Ref<short>());
}
}

[Fact]
public void IsComparable_Same_As_Signature()
Expand Down
18 changes: 17 additions & 1 deletion fennecs/Archetype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,11 @@ internal bool Matches(Mask mask)

internal bool IsMatchSuperSet(IReadOnlyList<TypeExpression> matchTypes) => MatchSignature.IsSupersetOf(matchTypes);


/// <summary>
/// Remove one or more Entities and all associated Component data from the Archetype.
/// </summary>
/// <param name="entry">index of the first Entity to remove</param>
/// <param name="count">number of Entities to remove</param>
internal void Delete(int entry, int count = 1)
{
Invalidate();
Expand All @@ -150,6 +154,10 @@ internal void Delete(int entry, int count = 1)
{
storage.Delete(entry, count);
}

// The code above may relocate Component data, we must
// update Metas for all Entities following the removed section.
PatchMetas(entry, Math.Min(Count - entry, count));
}

/// <summary>
Expand Down Expand Up @@ -177,6 +185,14 @@ public void Truncate(int maxEntityCount)
IdentityStorage.Delete(Count - excess, excess);
}

/// <summary>
/// Update Entity metas with new Row information. Call to update the Meta Row information
/// after moving Entities/Component data within the Storages. This ensures that the Entity
/// Meta within the World will have the correct Row to access the Entity's associated data
/// within the Archetype.
/// </summary>
/// <param name="entry">If `count` param is nonzero, `entry` must be less than `this.Count`.</param>
/// <param name="count">If &lt;= 0, PatchMetas does nothing.</param>
private void PatchMetas(int entry, int count = 1)
{
for (var i = 0; i < count; i++)
Expand Down
2 changes: 1 addition & 1 deletion fennecs/fennecs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>fennecs</PackageId>
<Version>0.5.10-beta</Version>
<Version>0.5.11-beta</Version>
<Title>fennecs</Title>
<Product>fennecs Entity-Component System</Product>
<Authors>Moritz Voss, Aaron Winter</Authors>
Expand Down
8 changes: 4 additions & 4 deletions www/docs/Streams/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,22 @@ Entities in Queries (including Worlds) and their Components can be read and modi
You can get a Stream View from a Query, or from a World.
::: code-group
```csharp [from new Query (shorthand)]
var query = stream.Query<Position, Velocity>().Not<Boring>().Stream();
var stream = world.Query<Position, Velocity>().Not<Boring>().Stream();
// This is the tried-and-true way of getting a Stream View from a Query,
// similar to how it was in fennecs 0.4.x but modernized by splitting
// the resolution of the Query from the matching from the Stream's
// iteration and execution. Queries compile fast and are cached!
// Stream<>.Query provides you access to the underlying Query just in case.
```
```csharp [from existing Query]
var query = world.Query().Has<Position>().Has<Velocity>().Not<Boring>();
// Query with arbitrarily complexity of Expressions, and any number of Streams
var stream = world.Query().Has<Position>().Has<Velocity>().Not<Boring>().Compile();
// Query with arbitrary complexity of Expressions, and any number of Streams
var positions = query.Stream<Position>();
var velocities = query.Stream<Velocity>();
var both = query.Stream<Position, Velocity>();
var swap = query.Stream<Velocity, Position>();
```
```csharp [from the World (super shorthand)]
```csharp [from World (super shorthand)]
var query = world.Stream<Position, Velocity>();
// The super-foxy minimal boilerplate shorthand version!
// Disadvantage: Only up to 5 simple Has<T> Query Expressions.
Expand Down
11 changes: 9 additions & 2 deletions www/docs/World.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ order: 10

# Worlds

Each World contains Entities and their Components, as well as their structure and Relations.
Worlds represent the universe of Entities and their Components (as well as the component layout, and the Queries that match them).

![A fennec leaning casually on a World](https://fennecs.tech/img/fennec-world.png)

It is possible to have multiple Worlds, each with its own set of Queries and Entities.
- Entities are unique to a World
- Relations can bridge across Worlds (from fennecs 0.6.0+)
- Component Types are shared between Worlds
- (this facilitates moving entities between Worlds) (planned fennecs 0.6.5+)

![World Example: blue circle labeled world filled with fox emojis with many different traits](https://fennecs.tech/img/diagram-world.png)
*"A world, populated by Entities with different traits (Components)"*


## Instantiation
Imagine making a new universe was as easy as saying *"let there be fennecs"* - well, it is!
::: code-group
Expand Down
5 changes: 5 additions & 0 deletions www/misc/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ var found3 = mystream.FirstOrDefault((tuple) => tuple.Item2 > mousePosition).Ite
- `EntitySpanAction` - process a Span of Entities
- `UniformEntitySpanAction<in U>` - process a Span of Entities with a uniform parameter


## Version 0.5.11-beta
- Fixed [Issue #23](https://github.com/outfox/fennecs/issues/23) Data Integrity Issue Following Despawn. Thanks to [Penny](https://github.com/PennyMew) for the Issue and PR to fix it!
- Fixed [Issue #21](https://github.com/outfox/fennecs/issues/21) Streams Documentation [Example](https://fennecs.tech/docs/Streams/) was mixed up.

## Version 0.5.10-beta
- Added `bool Entity.HasVirtual(object)` extension method to `fennecs.reflection`
- Fixed [Issue #17](https://github.com/outfox/fennecs/issues/17) Entities that have self-referencing relations on themselves can now be despawned and bulk-despawned without crashing / potentially undefined behaviour.
Expand Down

0 comments on commit 6100870

Please sign in to comment.