This repository has been archived on 2023-02-02. You can view files and clone it, but cannot push or open issues or pull requests.
mfgames-nitride-cil/src/Nitride/Entities/README.md

189 lines
6.5 KiB
Markdown
Raw Normal View History

2021-09-07 05:15:45 +00:00
# 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).