Skip to content

Instantly share code, notes, and snippets.

@fversnel
Created June 26, 2015 11:22
Show Gist options
  • Save fversnel/0f20277b395bc302c970 to your computer and use it in GitHub Desktop.
Save fversnel/0f20277b395bc302c970 to your computer and use it in GitHub Desktop.
First version of a functional API for the Entitas framework
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Entitas.Functional {
/// <summary>
/// TODO Naming systems with string instead of Type
/// TODO Maybe use functions as base for Entitas instead of classes, then build OO Api on top. Is probably better for performance
/// </summary>
public static class Example {
public class ExampleComponent1 : IComponent {
public int Something;
}
public class ExampleComponent2 : IComponent {
public string Anything;
}
public static ComponentIdRepository ComponentIdRepository = new ComponentIdRepository();
public static class Components {
public static readonly ComponentDefinition<ExampleComponent1> Example1 =
ComponentIdRepository.RegisterComponent<ExampleComponent1>();
public static readonly ComponentDefinition<ExampleComponent2> Example2 =
ComponentIdRepository.RegisterComponent<ExampleComponent2>();
}
public static class Systems {
public static readonly IReactiveSystem System1 = FunctionalApi.ReactiveSystem(
Components.Example2.Matcher(),
GroupEventType.OnEntityAdded,
entities => {
for (int i = 0; i < entities.Length; i++) {
// On entity added
}
});
public static IExecuteSystem System2(Func<IMatcher, Group> getGroup) {
var group = getGroup(Components.Example1.Matcher());
return FunctionalApi.ExecuteSystem(() => {
var entities = group.GetEntities();
for (int i = 0; i < entities.Length; i++) {
var entity = entities[i];
// Processing entity
}
});
}
}
public static void TestRun() {
var pool = new Pool(ComponentIdRepository.ComponentIds.Count);
// TODO Add ability to name anonymous systems
pool.CreateSystem(Systems.System1);
pool.CreateSystem(Systems.System2(pool.GetGroup));
var entity = pool.CreateEntity();
entity.AddComponent(Components.Example1, c => {
c.Something = 42;
});
entity.AddComponent(Components.Example2, c => {
c.Anything = "fortytwo";
});
entity.ReplaceComponent(Components.Example1, c => {
c.Something += 1;
});
entity.RemoveComponent(Components.Example1);
}
}
public static class FunctionalApi {
#region Systems
public static IStartSystem StartSystem(Action onStart) {
return new AnonymousStartSystem(onStart);
}
public static IReactiveSystem ReactiveSystem(IMatcher matcher, GroupEventType eventType, Action<Entity[]> updateEntities) {
return new AnonymousReactiveSystem(matcher, eventType, updateEntities);
}
public static IExecuteSystem ExecuteSystem(Action execute) {
return new AnonymousExecuteSystem(execute);
}
#endregion
#region Components
public static void AddComponent<TComponent>(
this Entity entity,
ComponentDefinition<TComponent> componentDefinition,
Action<TComponent> fillFields) where TComponent : IComponent, new() {
var component = new TComponent();
fillFields(component);
entity.AddComponent(componentDefinition.Id, component);
}
public static void ReplaceComponent<TComponent>(
this Entity entity,
ComponentDefinition<TComponent> componentDefinition,
Action<TComponent> updateFields) where TComponent : IComponent, new() {
TComponent component;
if (entity.HasComponent(componentDefinition.Id)) {
entity.WillRemoveComponent(componentDefinition.Id);
component = (TComponent) entity.GetComponent(componentDefinition.Id);
} else {
component = new TComponent();
}
updateFields(component);
entity.ReplaceComponent(componentDefinition.Id, component);
}
public static void RemoveComponent<TComponent>(
this Entity entity,
ComponentDefinition<TComponent> componentDefinition) where TComponent : IComponent, new() {
entity.RemoveComponent(componentDefinition.Id);
}
#endregion
#region Matchers
// TODO Memoize this function maybe?
public static IMatcher Matcher<TComponent>(this ComponentDefinition<TComponent> componentDefinition) where TComponent : IComponent, new() {
return new AllOfMatcher(new[] { componentDefinition.Id });
}
public static IMatcher And(this IMatcher firstMatcher, IMatcher secondMatcher) {
return new AnonymousMatcher(
indices: MatcherIndices(firstMatcher, secondMatcher),
matches: entity => firstMatcher.Matches(entity) && secondMatcher.Matches(entity));
}
public static IMatcher Or(this IMatcher firstMatcher, IMatcher secondMatcher) {
return new AnonymousMatcher(
indices: MatcherIndices(firstMatcher, secondMatcher),
matches: entity => firstMatcher.Matches(entity) || secondMatcher.Matches(entity));
}
public static int[] MatcherIndices(params IMatcher[] matchers) {
return matchers
.SelectMany(m => m.indices)
.Distinct()
.ToArray();
}
#endregion
#region Pools
#endregion
}
public class ComponentIdRepository {
private readonly IDictionary<Type, int> _componentIds;
public ComponentIdRepository() {
_componentIds = new Dictionary<Type, int>();
}
public ComponentDefinition<TComponent> RegisterComponent<TComponent>() where TComponent : IComponent, new() {
int componentId;
if (!_componentIds.TryGetValue(typeof (TComponent), out componentId)) {
componentId = _componentIds.Count;
_componentIds.Add(typeof (TComponent), componentId);
}
return new ComponentDefinition<TComponent>(componentId);
}
public IList<int> ComponentIds {
get {
var ids = _componentIds.Values.ToList();
ids.Sort();
return ids;
}
}
}
public class ComponentDefinition<TComponent> where TComponent : IComponent {
private readonly int _id;
public ComponentDefinition(int id) {
_id = id;
}
public int Id {
get { return _id; }
}
public Type ComponentType {
get { return typeof (TComponent); }
}
}
public class AnonymousExecuteSystem : IExecuteSystem {
private readonly Action _execute;
public AnonymousExecuteSystem(Action execute) {
_execute = execute;
}
public void Execute() {
_execute();
}
}
public class AnonymousStartSystem : IStartSystem {
private readonly Action _start;
public AnonymousStartSystem(Action start) {
_start = start;
}
public void Start() {
_start();
}
}
public class AnonymousReactiveSystem : IReactiveSystem {
private readonly IMatcher _matcher;
private readonly GroupEventType _eventType;
private readonly Action<Entity[]> _updateEntities;
public AnonymousReactiveSystem(IMatcher matcher, GroupEventType eventType, Action<Entity[]> updateEntities) {
_matcher = matcher;
_eventType = eventType;
_updateEntities = updateEntities;
}
public IMatcher GetTriggeringMatcher() {
return _matcher;
}
public GroupEventType GetEventType() {
return _eventType;
}
public void Execute(Entity[] entities) {
_updateEntities(entities);
}
}
public class AnonymousMatcher : IMatcher {
private readonly int[] _indices;
private readonly Func<Entity, bool> _matches;
public AnonymousMatcher(int[] indices, Func<Entity, bool> matches) {
_indices = indices;
_matches = matches;
}
public int[] indices {
get { return _indices; }
}
public bool Matches(Entity entity) {
return _matches(entity);
}
}
}
@fversnel
Copy link
Author

The idea behind this API is that I think the essence of an Entity Component architecture is best described using functions instead of classes.

Entitas has a very nice code generator to hide all the boiler plate for you but I think that a functional API can be a quite nice alternative that is perhaps a bit more modder friendly (ids are generated at runtime instead of compile time, for example)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment