189 lines
6.5 KiB
Markdown
189 lines
6.5 KiB
Markdown
|
# Entities
|
||
|
|
||
|
Nitride is based on an Entity Component System (ECS) in the way it handles the
|
||
|
various input documents, images, feeds, and other parts that make up a website.
|
||
|
Implementing it this way makes it easier to create a distinction between the
|
||
|
different entities (much like Statiq.Web uses the DocumentType)
|
||
|
but allows for adding new components along the way without having the C#
|
||
|
limitations of a sealed enumeration or needing to implement a Javascript-style
|
||
|
enum for identification. Instead, if an entity needs to be identified as being
|
||
|
Markdown, an image, or a database query, it just adds a component to represent
|
||
|
that information.
|
||
|
|
||
|
The basic entity is just a simple object with an internal identifier. These
|
||
|
entities are also immutable. Functions that appear to manipulate actually clone,
|
||
|
make the change, and then return the results.
|
||
|
|
||
|
```c#
|
||
|
var entity = new Entity();
|
||
|
Console.WriteLine("Entity Id: {0}", entity.Id);
|
||
|
```
|
||
|
|
||
|
## Components
|
||
|
|
||
|
By itself, an entity doesn't have any meaning or purpose. These are described by
|
||
|
a generic collection of components that are added to the entity. Each component
|
||
|
has a type and then an instance of that type. These are added to the entity with
|
||
|
the `Add` command. If a type is not given, it is assumed to be the same type as
|
||
|
the parameter, but a base class or interface can be given to allow different
|
||
|
types to be stored in a specific component.
|
||
|
|
||
|
In effect, the type of the component is the key. Having two different types,
|
||
|
even with the same object, would be considered two distinct objects.
|
||
|
|
||
|
```c#
|
||
|
string mimeType = "text/plain";
|
||
|
Entity entity = new Entity();
|
||
|
Assert.Equal(0, entity.Count);
|
||
|
|
||
|
Entity newEntity = entity.Add(mimeType);
|
||
|
Assert.Equal(0, entity.Count);
|
||
|
Assert.Equal(1, newEntity.Count);
|
||
|
Assert.Equal(entity.Id, newEntity.Id);
|
||
|
Assert.Equal(entity, newEntity);
|
||
|
|
||
|
newEntity = newEntity.Add<object>(mimeType);
|
||
|
Assert.Equal(2, newEntity.Count);
|
||
|
```
|
||
|
|
||
|
The basic operations for entity components are:
|
||
|
|
||
|
- `Add<TType>(component)`: Adds a component as the given type. If there is
|
||
|
already a component there, an exception will be thrown.
|
||
|
- `Add(component)`: As `Add<TType>(component)` but the `TType` is the same as
|
||
|
`component.GetType()`.
|
||
|
- `Remove<TType>()`: Removes any component of the given type, if exists. If
|
||
|
there is no such component, then nothing happens.
|
||
|
- `Remove(component)`: Same as `Remove<TType>` with the component given
|
||
|
determining the `TType`.
|
||
|
- `Set<TType>(component)`: Adds or updates a component of the given type.
|
||
|
- `Set(component)`: Same as `Set<TType>` with the component given determining
|
||
|
the `TType`.
|
||
|
- `Copy()`: Creates a copy of the entity and assigns it a new identifier.
|
||
|
- `ExactCopy()`: Creates a copy of the entity with the same identifier.
|
||
|
|
||
|
As above, all of these return a new entity (or the same one if no change is made
|
||
|
to the entity).
|
||
|
|
||
|
### Query Components
|
||
|
|
||
|
- `bool Has<TType>()`: Returns a value indicating whether the entity has the
|
||
|
given component type.
|
||
|
- `TType Get<TType>()`: Returns the value of the registered component. If there
|
||
|
is no such object, this will throw an exception.
|
||
|
- `TType? GetOptional<TType>()`: Returns the value of the registered component.
|
||
|
If there is no such object, this will return the default value.
|
||
|
- `bool TryGet<TType>(out TType component)`: Attempt to get the component. If it
|
||
|
cannot be retrieved, then this will return `false` and `component` is
|
||
|
undefined.
|
||
|
|
||
|
## Collections
|
||
|
|
||
|
To keep with the patterns of C#, working with collection of entities uses normal
|
||
|
LINQ operations. For example, to combine two sets of entities together,
|
||
|
the `Union` LINQ command can be used:
|
||
|
|
||
|
```c#
|
||
|
IEnumerable<Entity> entities1;
|
||
|
IEnumerable<Entity> entities2;
|
||
|
IEnumerable<Entity> all = entities1.Union(entities2);
|
||
|
```
|
||
|
|
||
|
To work with the ECS, additional extension methods have been written that allow
|
||
|
for filtering or working with those entities.
|
||
|
|
||
|
### HasComponents
|
||
|
|
||
|
The `HasComponents` is a set of overrides that checks to see if the given entity
|
||
|
has the requisite components. If they don't, then that entity is filtered out.
|
||
|
|
||
|
```c#
|
||
|
IEnumerable<Entity> entities;
|
||
|
|
||
|
var filtered1 = entities.HasComponents<C1>();
|
||
|
var filtered2 = entities.HasComponents<C1, C2>();
|
||
|
var filtered3 = entities.HasComponents<C1, C2, C3>();
|
||
|
```
|
||
|
|
||
|
### NotComponents
|
||
|
|
||
|
`NotComponents` is effectively the reverse of `HasComponents` in that if the
|
||
|
entity has the given components, they are filtered out. This also allows up to
|
||
|
three different components.
|
||
|
|
||
|
This also allows the developer to ask for an entity that has two components but
|
||
|
not have a different of two with:
|
||
|
|
||
|
```c#
|
||
|
IEnumerable<Entity> entities;
|
||
|
var filtered = entities
|
||
|
.HasComponents<C1, C2>()
|
||
|
.NotComonents<C3, C4>();
|
||
|
```
|
||
|
|
||
|
### ForComponents
|
||
|
|
||
|
`ForComponents` allows for a lambda to be performed on entities that have the
|
||
|
given components while passing all the entities on through the function. This is
|
||
|
much like the `ForEach` combined with `Select` in that the changed or updated
|
||
|
entity will be passed on.
|
||
|
|
||
|
```c#
|
||
|
var entities = new Entities[]
|
||
|
{
|
||
|
new Entity().Add("value1"),
|
||
|
new Entity().Add(2),
|
||
|
new Entity().Add(3).Add("value2"),
|
||
|
};
|
||
|
var filtered = entities
|
||
|
.ForComponents<string>((entity, value) => entity.Set(value + "!"));
|
||
|
|
||
|
Assert.Equal(
|
||
|
new[] {
|
||
|
"value1!",
|
||
|
null,
|
||
|
"value2!",
|
||
|
},
|
||
|
filtered.Select(x => x.GetOptional<string>()));
|
||
|
```
|
||
|
|
||
|
There are also three overloads allowing up to three components to be pulled out
|
||
|
with the lambda.
|
||
|
|
||
|
### SetComponents, AddComponents, RemoveComponents
|
||
|
|
||
|
`SetComponents` (as the corresponding `AddComponents`, and `RemoveComponents`)
|
||
|
basically perform the same operation on the entire list. They also have the
|
||
|
three overloads to allow one to three components be manipulated in a single
|
||
|
call.
|
||
|
|
||
|
```c#
|
||
|
IEnumerable<Entity> entities;
|
||
|
var updated = entities
|
||
|
.AddComponents<object>(mimeType)
|
||
|
.AddComponents(mimeType)
|
||
|
.RemoveComponents<object>()
|
||
|
.SetComponents<object>(mimeType)
|
||
|
.SetComponent(mimeType);
|
||
|
```
|
||
|
|
||
|
### MergeEntities
|
||
|
|
||
|
`MergeComponents` combines multiple entities together if they have the same `Id`
|
||
|
field. The two sides of the comparison are the presence of a specific component.
|
||
|
|
||
|
```c#
|
||
|
IEnumerable<Entity> entities;
|
||
|
var combined = entities
|
||
|
.MergeEntities<C1, C2>(
|
||
|
(entity1, c1, entity2, c2) => entity1.Set(c2));
|
||
|
```
|
||
|
|
||
|
## Files, Paths, and Content
|
||
|
|
||
|
Entities do not have an integral concept of being a file or having contents from
|
||
|
the disk or anywhere else. Much of this is implemented as components from the
|
||
|
Nitride.IO assembly which uses [Zio](https://github.com/xoofx/zio)
|
||
|
for the underlying library, but can be easily replaced with a different IO
|
||
|
layer (or even the straight System.IO).
|