I'd be interested to hear how an ECS system works for a roguelike project, as I'm making this combo myself and I've tailored it quite a bit to work with the turn-based nature of the game. I had a quick look at the project, and your naming of things is not helpful ( Nuts: the abstract base class for all data objects using this library. ) Would you be interested to provide an overview?
Sure I'll try a brief overview here. As far as the naming, I'm sure I could've chosen better, but working with libraries like MonoGames (Xna), there's too much naming confusion to suit me with the standard names. Anyhow, to begin, here is a list of my names vs 'more or less' standard names.
Bag => Entity (combines holding Entity Id with being a collection of Nut subtypes (Components)).
Nut => Component (actually abstract base class for components).
Process => System (IProcess interface and ProcessBaseAbc useful abstract base class for Processes/Systems)
Vendor => EntityManager in some systems, EntityGroup in others, a collection of Bag instances, primary interface to Bags and root object for serialization of game state (using Json).
Recipe => Assemblage
http://t-machine.org/index.php/2009/10/26/entity-systems-are-the-future-of-mmos-part-5/ in Adam Martin's parlance, a blueprint for Bag instances.
RecipeBook => A collection of Recipes, and primary interface to them. RecipeBook's are only created via loading from Json, using the prototype concept and general format (adapted for ECS) shown at the bottom of
http://gameprogrammingpatterns.com/prototype.html.
Mix => A custom Bitset used to implement membership testing using the 'key fits lock' concept
http://gamedev.stackexchange.com/questions/31473/role-of-systems-in-entity-systems-architecture.
Overall the architecture of Peanuts is designed to support Mick West's "OBJECT AS A PURE AGGREGATION" model as shown in the section of that title in the article:
http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/. The implementation of Peanut's API is influenced by ideas such as Martin Fowler's Minimal Interface
http://martinfowler.com/bliki/MinimalInterface.html, so expect the public API to remain small.
Getting down to brass tacks, the actual implementation of a component using Peanuts would look much like:
public sealed class ExampleNut : ShallowNutAbc
{
public string AStringProperty { get; set; }
public int AnIntegerProperty { get; set; }
// etc - nothing but simple properties except for perhaps temporary data
}
Creating an instance of our ExampleNut component is only done in the context of creating a Bag (Entity) which is done using the Vendor interface method MakeBag. It could look something like*:
Bag myEntity = vendor.MakeBag(new ExampleNut { AStringProperty = "Hello", AnIntegerProperty = 5});
Of course, that is the exception rather than the rule. Normally you would create a Bag as a collection of components all at one time using a Recipe. That looks more like:
Bag myEntity = vendor.MakeBag(recipeBook.Get("MyRecipeForOrcs"));
There's a number of things being done behind the scenes in the above, the Bag's Id property is set to a contextually unique integer value when created, and the mix of component types making up the entity is represented by a Mix (bitset) object created at the same time and stored inside the Bag. Also, all systems registered with the vendor will be notified of the new Bag instance if, and only if, their registered key (a Mix instance) is a subset of the Bag's lock (another Mix instance).
*Note that I currently have too many ways to create entities, once I use this library in my current roguelike, I expect to eliminate at least one of the less useful variants.
Speaking of Processes (Systems or Procedures or whatever in ECS literature), with Peanuts a Process must implement the IProcess interface. Its a simple interface, only three methods, two of which can be handled entirely by the abstract base class ProcessBase, the other method: Update(long gameTicks, object context =null) is the one called to get anything done. One of the things ProcessBase automates for you is the maintenance of a valid entity id list. In code, a Process would look something like:
public class MyProcess : ProcessBase
{
// skip constructor which just passes everything to ProcessBase and gets and caches the Nut subtype integer id's
public override void Update(long gameTick, object context = null)
{
// do setup here
foreach(var eid in MatchingBagIds())
{
var entity = BagVendor.Get(eid);
var componentA = entity.Get(cachedComponentATypeId);
var componentB = entity.Get(cachedComponentBTypeId);
// process component data
}
}
}
That is pretty much it in a nutshell
Of course, there are a few things I didn't go into, like the need to make a call to Nut.Initialize() after loading any plugins that define Nut subtypes and prior to creating instances of Vendor/Process/Bag/etc. Also skipped over the constructor call for Processes that ProcessBase handles, where the Process registers itself with the current Vendor instance and tells it what Nut subtypes (component types) it needs to do any processing.
I also didn't go into too much detail on the creation of Nut subclasses themselves, so let me point out a few things where that is concerned. Due to the ability to use any entity as a prototype for creating a new entity, all entities need to support the Clone method (abstracted in the Nut base class). I recommend using the ShallowNutAbc class as the base class instead of using Nut directly since it implements that interface using shallow copy which is sufficient for most use cases.
While I recommend using shallow inheritance trees for components (derive from one of the supplied abstract base classes), Peanut does support two or more component classes sharing a single non-abstract base class, however to make this work with the auto registration of components in Nut's static Initialize method, all component classes must be sealed. I also heavily advise against putting any logic in the component classes and any data member object references *must* be marked with [JsonIgnore] attribute if not avoided altogether.
Well, now I'm past the nut shell and into the nut house
Omnivore