Skip to content

Commit 4bc2572

Browse files
committed
Updated readme
Also added some more helpers for DI
1 parent f566be7 commit 4bc2572

22 files changed

+204
-59
lines changed

docs/architecture/entity-collections.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ So taking the above example again, if you were to partition your collections so
1818
1919
## Where do they live?
2020

21-
So there is an `EntityCollectionManager` which acts as the container for all the entity collections
21+
So there is an `EntityDatabase` which acts as the container for all the entity collections

docs/breaking-changes.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Breaking Changes
22

3+
## 3.9.0 -> 3.10.0
4+
5+
- IEntityCollectionManager no longer contains EntityCollections its now within `IEntityDatabase`, which is within there
6+
37
## 3.8.0 -> 3.9.0
48

59
- `IObservableScheduler` is now known as `IUpdateScheduler` and uses an `ElapsedTime` object not `TimeSpan`

docs/framework/blueprints.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ how they wish to implement blueprints, as you can add as much configurable prope
66

77
Here is an example of a blueprint:
88

9-
```
9+
```csharp
1010
public class PlayerBlueprint : IBlueprint
1111
{
1212
public string Name {get;set;}
@@ -27,7 +27,7 @@ public class PlayerBlueprint : IBlueprint
2727
Pools are aware of blueprints and you can create an entity with a blueprint to save you the time of having to create the entity
2828
then manually applying all the components, which would look like:
2929

30-
```
30+
```csharp
3131
var hanSoloEntity = somePool.createEntity(new PlayerBlueprint{
3232
Name = "Han Solo",
3333
Class = "Smuggler",
@@ -40,7 +40,7 @@ You have 2 options of applying blueprints to entities, one would be to just new
4040
`Apply` method passing in the entity, or you could use the available extension methods to apply directly from the entity,
4141
this is also chainable so you are able to apply multiple blueprints to the same entity if you wanted, like so:
4242

43-
```
43+
```csharp
4444
entity.ApplyBlueprint(new DefaultActorBlueprint())
4545
.ApplyBlueprint(new DefaultEquipmentBlueprint())
4646
.ApplyBlueprint(new SetupNetworkingBlueprint());

docs/framework/entities.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,17 @@ As mentioned entities are created within collections and you can have many entit
1212

1313
### From `IEntityCollectionManager`
1414

15-
- `myCollectionManager.CreateObservableGroup(myGroup, optionalPoolName)`
15+
- `myCollectionManager.GetObservableGroup(myGroup, idsForCollectionsToCheck)`
1616

1717
This is probably the most common approach, you get your `IEntityCollectionManager` instance (usually injected in to your class) and you call `CreateObservableGroup`, this will create or return an existing observable group for you which internally contains the `Entities` that match the group for you to query on further. This is often a better approach than accessing entities directly. (read more on this in querying/filtration docs)
1818

19-
- `myCollectionManager.GetEntitiesFor(myGroup, optionalPoolName)`
19+
- `myCollectionManager.EntityDatabase.*`
20+
21+
The entity collection manager exposes the entity database which can be queried for more info
22+
23+
### From `IEntityDatabase`
24+
25+
- `entityDatabase.GetEntitiesFor(myGroup, idsForCollectionsToCheck)`
2026

2127
This is not used often but is there for convenience, it allows you to just get back an `IEnumerable<IEntity>` collection which contains all entities which match the group, so it you can query on the matching entities further however you want.
2228

docs/framework/groups.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ There are a few different ways to create a group, here are some of the common wa
1818

1919
There is a `Group` class which implements `IGroup`, this can be instantiated and passed any of the components you want to target, like so:
2020

21-
```c#
21+
```csharp
2222
var group = new Group(typeof(SomeComponent));
2323
```
2424

2525
There are also some helper methods here so you can add component types if needed via extension methods, like so:
2626

27-
```c#
27+
```csharp
2828
var group = new Group()
2929
.WithComponent<SomeComponent>()
3030
.WithoutComponent<SomeOtherComponent();
@@ -36,7 +36,7 @@ This is a halfway house between the builder approach and the instantiation appro
3636

3737
So there is also a `GroupBuilder` class which can simplify making complex groups, it is easy to use and allows you to express complex group setups in a fluent manner, like so:
3838

39-
```c#
39+
```csharp
4040
var group = new GroupBuilder()
4141
.WithComponent<SomeComponent>()
4242
.WithComponent<SomeOtherComponent>()
@@ -49,7 +49,7 @@ So if you are going to be using the same groupings a lot, it would probably make
4949

5050
It is quite simple to make your own group, you just need to implement the 2 getters:
5151

52-
```c#
52+
```csharp
5353
public class MyGroup : IGroup
5454
{
5555
public IEnumerable<Type> RequiredComponents {get;} = return new[] { typeof(SomeComponent), typeof(SomeOtherComponent) };

docs/framework/observable-groups.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ So now you know what entities are and how you can get hold of them, its worth go
55
## Filtration Flow
66

77
```
8-
IEntityCollectionManager <- This contains all collections, which in turn contains ALL entities
8+
IEntityCollectionManager <- This contains the database which contains all collections, which in turn contains ALL entities
99
|
1010
|
1111
IObservableGroup <- This filters all entities down to only ones which are within the group
@@ -15,7 +15,7 @@ IComputedGroup <- This acts as another layer of filtration on an IObse
1515
i.e Top 5 entities with PlayerComponent sorted by Score
1616
```
1717

18-
## IEntityCollectionManager
18+
## IEntityCollectionManager || IEntityDatabase
1919

2020
The entity collection manager is the root most point where all entity queries should originate from.
2121

docs/framework/systems.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ This is a niche system for when you want to carry out some logic outside the sco
1616
more fine grained control over how you deal with the entities matched.
1717

1818
Rather than the `SystemExecutor` doing most of the work for you and managing the subscriptions and entity interactions
19-
this just provides you the `GroupAccessor` for the entities targetted and its up to you to control how they are
19+
this just provides you the `GroupAccessor` for the entities targeted and its up to you to control how they are
2020
dealt with.
2121

2222
The `StartSystem` method will be triggered when the system has been added to the executor, and the `StopSystem`
@@ -30,4 +30,4 @@ So by default (with the default implementation of `ISystemExecutor`) systems wil
3030
2. Implementations of `IReactToEntitySystem`
3131
3. Other Systems
3232

33-
However within those groupings it will load the systems in whatever order Zenject (assuming you are using it) provides them, however there is a way to enforce some level of priority by applying the `[Priority(1)]` attribute, this allows you to specify the priority of how systems should be loaded. The ordering will be from lowest to highest so if you have a priority of 1 it will load before a system with a priority of 10.
33+
However within those groupings it will load the systems in whatever order Zenject/Extenject (assuming you are using it) provides them, however there is a way to enforce some level of priority by applying the `[Priority(1)]` attribute, this allows you to specify the priority of how systems should be loaded. The ordering will be from lowest to highest so if you have a priority of 1 it will load before a system with a priority of 10.

docs/infrastructure/dependency-injection-abstraction.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ If you want to configure how a binding should work, you can pass into the `Bind`
3333
### AsSingleton: `bool`
3434

3535
Setting this to `true` will mean that only one instance of this binding should exist, so if you were to do:
36-
```c#
36+
```csharp
3737
Bind<IEventSystem, EventSystem>(new BindingConfiguration{AsSingleton = true});
3838
```
3939
Then resolve `IEventSystem` in multiple places you will always get back the same instance, which is extremely handy for infrastructure style objects which should act as singletons. If you provide `false` as the value it will return a new instance for every resolve request.
@@ -46,7 +46,7 @@ This will allow you to give the binding a name for resolving via name.
4646

4747
This allows you to bind to an actual instance of an object rather than a type, which is useful if you need to manually setup something yourself.
4848

49-
```c#
49+
```csharp
5050
var someInstance = new Something(foo, bar);
5151
Bind<ISomething>(new BindingConfiguration{ToInstance = someInstance});
5252
```
@@ -55,7 +55,7 @@ Bind<ISomething>(new BindingConfiguration{ToInstance = someInstance});
5555

5656
This allows you to lazy bind something to a method rather than an instance/type, which is useful if you want to setup something in a custom way once all DI configuration has been processed.
5757

58-
```c#
58+
```csharp
5959
var bindingConfiguration = new BindingConfiguration({
6060
ToMethod: container =>
6161
{
@@ -85,7 +85,7 @@ The configuration object is simple but can be unsightly for larger configuration
8585

8686
There is a builder pattern helper which lets you setup your binding config via a builder rather than an instance of `BindingConfiguration`, this can be used by just creating a lambda within the bind method like so:
8787

88-
```c#
88+
```csharp
8989
Bind<ISomething>(config => config
9090
.AsSingleton()
9191
.WithName("something-1")
@@ -95,7 +95,7 @@ Bind<ISomething>(config => config
9595

9696
This lets you setup the configuration in a nice way, it also has type safety so you can setup instances and methods using it like so:
9797

98-
```c#
98+
```csharp
9999
// With instance
100100
Bind<ISomething>(config => config
101101
.ToInstance(new InstanceOfISomething())
@@ -120,7 +120,7 @@ Bind<ISomething>(config => config
120120

121121
There is also another helper which allows you to get an `IObservableGroup` directly from the container, this internally gets the instance of the `IEntityCollectionManager` and requests an observable group of a given type like so:
122122

123-
```c#
123+
```csharp
124124
// By group
125125
var observableGroup = container.ResolveObservableGroup(new MyGroup());
126126
// By required components

docs/introduction/setup.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,18 @@ public abstract class EcsRxApplication
6767
// For creating entities, collections, observable groups and managing Ids
6868
var entityFactory = new DefaultEntityFactory(new IdPool(), componentRepository);
6969
var entityCollectionFactory = new DefaultEntityCollectionFactory(entityFactory);
70+
var entityDatabase = new EntityDatabase(entityFactory);
7071
var observableGroupFactory = new DefaultObservableObservableGroupFactory();
71-
EntityCollectionManager = new EntityCollectionManager(entityCollectionFactory, observableGroupFactory, componentLookup);
72+
EntityCollectionManager = new EntityCollectionManager(observableGroupFactory, entityDatabase, componentLookup);
7273

7374
// All system handlers for the system types you want to support
7475
var manualSystemHandler = new ManualSystemHandler(EntityCollectionManager);
76+
var basicSystemHandler = new BasicSystemHandler(EntityCollectionManager);
7577

7678
var conventionalSystems = new List<IConventionalSystemHandler>
7779
{
78-
manualSystemHandler
80+
manualSystemHandler,
81+
basicSystemHandler
7982
};
8083

8184
// The main executor which manages how systems are given information

docs/performance/component-type-lookups.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ So if you are happy to provide a type index/id with your call it will bypass the
88

99
So this change means **YOU** have to tell EcsRx ahead of time what indexes/ids to use for your components and provide that data to the rest of your codebase, its easiest to do this by making a class with static int properties like so:
1010

11-
```c#
11+
```csharp
1212
public static class ComponentLookupTypes
1313
{
1414
public static int NameComponentId = 0;
@@ -19,7 +19,7 @@ public static class ComponentLookupTypes
1919

2020
Then you can just reference these types anywhere which satisfies the telling of the entity the index, and you then just need to make sure when the application is created it uses these explicit lookups rather than auto generating them, generally done by making your own module and loading it like so:
2121

22-
```c#
22+
```csharp
2323
public class CustomComponentLookupsModule : IDependencyModule
2424
{
2525
public void Setup(IDependencyContainer container)

docs/performance/struct-components.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Out the box it assumes you will be using classes for components, i.e:
44

5-
```c#
5+
```csharp
66
public class MyComponent : IComponent
77
{
88
//...

docs/performance/system-affinity.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Out the box `ObservableGroups` will just listen to changes across all collections, but you can give them an affinity so they will only listen to changes on certain collections, providing a performance boost as they dont need to listen to changes on entities they will never interact with, however most of the time you are not creating observable groups as they are requested per system. So we need to be able to tell the system what affinity they have so you can have a better suited observable group.
44

5-
```c#
5+
```csharp
66
// Tell this system that it should only interact with collections with id 1,5,6
77
[CollectionAffinity(1,5,6)]
88
public class SomeSystemWithAffinity : ISetupSystem

docs/plugins/batched-plugin.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Just load the plugin and then extend the required batched system, most of the he
1010

1111
### Using struct based components
1212

13-
```c#
13+
```csharp
1414
public class BatchedMovementSystem : BatchedSystem<PositionComponent, MovementSpeedComponent>
1515
{
1616
public BatchedMovementSystem(IComponentDatabase componentDatabase, IComponentTypeLookup componentTypeLookup, IBatchBuilderFactory batchBuilderFactory, IThreadHandler threadHandler) : base(componentDatabase, componentTypeLookup, batchBuilderFactory, threadHandler)
@@ -32,7 +32,7 @@ As you can see we extend the `BatchedSystem` class and provide it the component
3232

3333
The setup is almost identical to the struct based one but instead of using `BatchedSystem` you use `ReferenceBatchedSystem` as shown below:
3434

35-
```c#
35+
```csharp
3636
public class BatchedMovementSystem : ReferencedBatchedSystem<PositionComponent, MovementSpeedComponent>
3737
{
3838
public BatchedMovementSystem(IComponentDatabase componentDatabase, IComponentTypeLookup componentTypeLookup, IReferenceBatchBuilderFactory batchBuilderFactory, IThreadHandler threadHandler) : base(componentDatabase, componentTypeLookup, batchBuilderFactory, threadHandler)

docs/plugins/computed-plugin.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ All of these classes are provided as `abstract` classes so you should inherit fr
2626

2727
### `ComputedGroup`
2828

29-
```c#
29+
```csharp
3030
var myGroup = new MyComputedGroup(someObservableGroup); // inherits from ComputedGroup
3131
foreach(IEntity entity in myGroup)
3232
{
@@ -38,7 +38,7 @@ This implements `IComputedGroup` and takes in an `IObservableGroup` instance in
3838

3939
### `ComputedCollectionFromGroup`
4040

41-
```c#
41+
```csharp
4242
var scoresForEntities = new ComputedScores(someObservableGroupWithScores); // inherits from ComputedCollectionFromGroup<int>
4343
foreach(int someScore in scoresForEntities)
4444
{
@@ -50,7 +50,7 @@ This provides a way to create a computed collecton based off an observable group
5050

5151
### `ComputedFromGroup`
5252

53-
```c#
53+
```csharp
5454
var partyRating = new ComputedPartyRating(observableGroupOfPartyMemebers); // inherits from ComputedFromGroup<float>
5555
5656
GroupHud.PartyRating.Text = partyRating.Value.ToString();
@@ -60,7 +60,7 @@ This can be useful for taking a group and computing a singular value based upon
6060

6161
### `ComputedFromData`
6262

63-
```c#
63+
```csharp
6464
var firstPlaceRacer = new ComputedFirstPlace(collectionOfRacers); // inherits from ComputedFromData<Racer, IEnumerable<Racer>>
6565
6666
RacerHud.CurrentWinner.Text = firstPlaceRacer.Value.Name;

docs/plugins/persistence-plugin.md

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Persistence Plugin
2+
3+
The persistence plugin provides some helpers and conventions around saving/loading entity databases.
4+
5+
As part of this it also adds support for building your own pipelines to send data to various endpoints, which is all built on top of Persistity/LazyData.
6+
7+
- [Persistity](https://github.com/grofit/persistity)
8+
- [LazyData](https://github.com/grofit/LazyData)
9+
10+
## A bit of background
11+
12+
Historically this functionality all used to be within EcsRx when it was purely a Unity project, as this was an attempt to try and serialize entity/component data from the scene. However Unity is a complex beast and due to various other reasons the serialization stuff got sidelined.
13+
14+
Anyway fast-forward a bit and now EcsRx is cross platform and Unity is just one of the supported frameworks, so now seemed a good time to re-introduce the serialization aspect of it all.
15+
16+
Given the actual functionality developed was pretty self contained it made sense to split them out into their own libraries that could be used outside of EcsRx, which is why they are their own separate repos, but just wanted to let you know that these libraries were specifically developed originally as part of EcsRx to assist with data push/pulling to various places.
17+
18+
## `EcsRxPersistedApplication`
19+
20+
As part of this there is a helper application class, which extends the default `EcsRxApplication` and by default will look for an existing entity database file and load it when the app starts.
21+
22+
If you do not want it to load automatically and want to handle the load yourself you can override `LoadOnStart` or `SaveOnStop` if you dont want to override the entity database. Also as part of this there is some helper methods for saving and loading the entity database.
23+
24+
There is an example which shows how to use this, as well as how to override the default binary file and use a JSON file instead.
25+
26+
## Pipelines In General
27+
28+
For more info on pipelines look at [Persistity](https://github.com/grofit/persistity), but the gist of it is that they are basic ETL (Extract Transform Load) processes, where you specify how the pipeline should operate. Rather than simple serialization where you just take a model and turn it into a format, a pipeline is a level above that, so you express the entire process/pipeline for taking a raw object and converting it into a given format. Out the box you have support for Binary, Xml, Json, Bson, Yaml.
29+
30+
### Example pipeline
31+
For example if you wanted to have a pipeline to make an encrypted save game file it may look like:
32+
33+
```csharp
34+
// Manually create an encryption/decryption processor
35+
var encryptor = new AesEncryptor("some-pass-phrase");
36+
var encryptionProcessor = new EncryptDataProcessor(encryptor);
37+
var decryptionProcessor = new DecryptDataProcessor(encryptor);
38+
39+
// Tell it where to store our stuff
40+
var fileEndpoint = new FileEndpoint("savegame.sav");
41+
42+
// Create the pipeline to save the data
43+
container.BuildPipeline("SaveGame", x => x.SerializeWith<IBinarySerializer>()
44+
.ProcessWith(encryptionProcessor)
45+
.SendTo(fileEndpoint));
46+
47+
// Create the pipeline to load the data
48+
container.BuildPipeline("LoadGame", x => x.ReceiveFrom(fileEndpoint)
49+
.ProcessWith(decryptionProcessor)
50+
.DeserializeWith<IBinaryDeerializer>());
51+
52+
// Then to use it you would do
53+
var mySaveGameData = //...
54+
var savePipeline = container.ResolveSendPipeline("SaveGame");
55+
savePipeline.Execute(mySaveGameData); // Now encrypted and saved in savegame.sav
56+
57+
// Then to load it
58+
var loadPipeline = container.ResolveReceivePipeline("LoadGame");
59+
var mySaveGameData = loadPipeline.Execute();
60+
```
61+
62+
As you can see you can setup complex pipelines which can process data/transform it and send it to files, http endpoints, databases or even store it in memory or raise an event etc.
63+
64+
The above example uses some of the helper functionality which act as extensions on the DI container, but you can also inject `EcsRxPipelineBuilder` manually and use that directly to make a pipeline without injecting it in.
65+
66+
## Some things to know
67+
68+
When using pipelines you need to have objects which have a parameterless constructor and have everything get/settable. If you are using 3rd party objects which have complex constructors you are advised to make a proxy type which you transform to and from in your pipeline. For an example of this approach look at how we have `EntityDatabaseData` which acts as a proxy type for `EntityDatabase` which has a complex constructor.
69+
70+
You may want to have 2 different types of the same pipeline, for example in development you may want to have an item database as a json file, but when you go to production you want it as binary. The only difference between the 2 pipelines would be the transport format, so you can re-use a lot of the same steps but just change the send/receive format, you can even use LazyData directly and just convert from Json -> Binary (As shown with SuperLazy helpers in the LazyData repo).

0 commit comments

Comments
 (0)