Skip to content

Instantly share code, notes, and snippets.

@ribbanya
Last active October 24, 2019 16:20
Show Gist options
  • Save ribbanya/14800d5f5ab11ce540d303d8ae58f5f3 to your computer and use it in GitHub Desktop.
Save ribbanya/14800d5f5ab11ce540d303d8ae58f5f3 to your computer and use it in GitHub Desktop.
Entitas "binding" syntax sugar

Provides a pretty-looking way to create and modify entities.

Example usage:

using static GameComponentsLookup;

// public class [...]

var binding = new EntityBinding<GameEntity>(this.gameContext) {
// using a statically imported integer index
  [Name] = "Mr. Entity", 
// anonymous types are correctly mapped to component classes by field name
  [Color] = new { red = 150/255f, green = 123/255f, blue = 182/255f } 
  [Position] = new {x = 100, y = 25},
// literal values map to components with one field
  [FacingDirection] = FacingDirection.Right
};

// you can use the indexer even after initialization
binding[Scale] = new { width = 1.5f, height = 0.5f };
// or access a component by name rather than by integer index
var name = (NameComponent)binding["Name"];

var entity = this.gameContext.GetEntityWithName(name.value);
Debug.Assert(object.ReferenceEquals(binding.entity, entity));

Credit for parts of this source code, and all of Entitas, goes to DesperateDevs.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using DesperateDevs.Utils;
using Entitas;
namespace TeamSalvato.Slashball.Common {
public static class EntitasHelper {
public static void BindComponent(this IEntity @this, int index, object value, bool replace) {
var valueType = value.GetType();
var componentType = @this.contextInfo.componentTypes[index];
var valueInfo = new HashSet<PublicMemberInfo>(GetPublicMemberInfo(valueType, false));
var componentInfo = GetPublicMemberInfo(componentType, true);
var component = (IComponent) Activator.CreateInstance(componentType);
if (valueInfo.Count <= 0 || Type.GetTypeCode(valueType) != TypeCode.Object) {
componentInfo[0].SetValue(component, value);
} else {
foreach (var destination in componentInfo) {
var source = valueInfo.First(src => src.name == destination.name);
valueInfo.Remove(source);
destination.SetValue(component, source.GetValue(value));
}
}
if (replace) @this.ReplaceComponent(index, component);
else @this.AddComponent(index, component);
}
private static List<PublicMemberInfo> GetPublicMemberInfo(Type type, bool isDestination) {
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
var fields = type.GetFields(flags);
var properties = type.GetProperties(flags);
var result = new List<PublicMemberInfo>(fields.Length + properties.Length);
result.AddRange(from field in fields select new PublicMemberInfo(field));
result.AddRange(from property in properties
where property.GetIndexParameters().Length == 0
where isDestination && property.CanWrite || property.CanRead
select new PublicMemberInfo(property));
return result;
}
}
}
using System;
using System.Linq;
using Entitas;
namespace Common {
public class EntityBinding<TEntity> where TEntity : class, IEntity {
public readonly TEntity entity;
public EntityBinding(TEntity entity) {
this.entity = entity;
}
public EntityBinding(IContext<TEntity> context) {
this.entity = context.CreateEntity();
}
public EntityBinding(IContexts contexts) {
this.entity = GetContextFromContextsByEntityType(contexts).CreateEntity();
}
public EntityBinding() : this(Contexts.sharedInstance) { }
public object this[int index] {
get => this.entity.GetComponent(index);
set => this.entity.BindComponent(index, value, true);
}
public object this[string componentName] {
get => this.entity.GetComponent(GetIndexForName(this.entity, componentName));
set => this.entity.BindComponent(GetIndexForName(this.entity, componentName), value, true);
}
private static int GetIndexForName(IEntity entity, string componentName) {
return Array.IndexOf(entity.contextInfo.componentNames, componentName);
}
public static implicit operator TEntity(EntityBinding<TEntity> binding) => binding.entity;
public static implicit operator EntityBinding<TEntity>(TEntity entity) => new EntityBinding<TEntity>(entity);
private static IContext<TEntity> GetContextFromContextsByEntityType(IContexts contexts) {
foreach (var context in contexts.allContexts) {
for (var type = context.GetType(); type != null; type = type.BaseType) {
if (!type.IsGenericType) continue;
if (type.GetGenericArguments().Any(argument => argument == typeof(TEntity))) {
return (IContext<TEntity>) context;
}
}
}
throw new ArgumentException($"Context corresponding to {typeof(TEntity)} in {contexts} was not found.");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment