# 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(mimeType); Assert.Equal(2, newEntity.Count); ``` The basic operations for entity components are: - `Add(component)`: Adds a component as the given type. If there is already a component there, an exception will be thrown. - `Add(component)`: As `Add(component)` but the `TType` is the same as `component.GetType()`. - `Remove()`: Removes any component of the given type, if exists. If there is no such component, then nothing happens. - `Remove(component)`: Same as `Remove` with the component given determining the `TType`. - `Set(component)`: Adds or updates a component of the given type. - `Set(component)`: Same as `Set` 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()`: Returns a value indicating whether the entity has the given component type. - `TType Get()`: Returns the value of the registered component. If there is no such object, this will throw an exception. - `TType? GetOptional()`: Returns the value of the registered component. If there is no such object, this will return the default value. - `bool TryGet(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 entities1; IEnumerable entities2; IEnumerable 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 entities; var filtered1 = entities.HasComponents(); var filtered2 = entities.HasComponents(); var filtered3 = entities.HasComponents(); ``` ### 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 entities; var filtered = entities .HasComponents() .NotComonents(); ``` ### 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((entity, value) => entity.Set(value + "!")); Assert.Equal( new[] { "value1!", null, "value2!", }, filtered.Select(x => x.GetOptional())); ``` 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 entities; var updated = entities .AddComponents(mimeType) .AddComponents(mimeType) .RemoveComponents() .SetComponents(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 entities; var combined = entities .MergeEntities( (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).