Skip to content

Instantly share code, notes, and snippets.

@will-hart
Last active June 8, 2018 21:03
Show Gist options
  • Save will-hart/4dce1335d05d34dfbc32d61d32e56b5b to your computer and use it in GitHub Desktop.
Save will-hart/4dce1335d05d34dfbc32d61d32e56b5b to your computer and use it in GitHub Desktop.
Sentry ECS - a simple public domain entity component system
public abstract class AbstractComponent : IComponent
{
[NonSerialized]
protected Entity _owner;
public AbstractComponent(Entity owner)
{
_owner = owner;
ID = Guid.NewGuid().ToString("n");
}
public void SetOwner(Entity newOwner)
{
_owner = owner;
}
public virtual void OnDestroy() { }
public abstract Type ObjectType { get; }
public abstract ComponentTypes ComponentType { get; }
public T Clone<T>() where T : AbstractComponent
{
return (T) MemberwiseClone();
}
public Entity Owner
{
get { return _owner; }
}
public string ID { get; private set; }
}
public abstract class AbstractEditorWindow : EditorWindow
{
private float _nextUpdate = 1;
// refreshes the display every second
protected virtual void Update()
{
_nextUpdate -= Time.deltaTime;
if (_nextUpdate > 0) return;
Repaint();
_nextUpdate = 1;
}
protected void DrawDoubleLabel(string label, string value1, string separator, string value2)
{
GUILayout.BeginHorizontal();
GUILayout.Label(label, EditorStyles.miniBoldLabel);
GUILayout.Box(value1, GUILayout.Width(100));
GUILayout.Label(separator, GUILayout.Width(50));
GUILayout.Box(value2, GUILayout.Width(100));
GUILayout.EndHorizontal();
}
protected void DrawLabel(string label, string value, int width = 200, bool isBox = true)
{
GUILayout.BeginHorizontal();
GUILayout.Label(label, EditorStyles.miniBoldLabel);
if (isBox)
{
GUILayout.Box(value, GUILayout.Width(width));
}
else
{
GUILayout.Label(value, GUILayout.Width(width));
}
GUILayout.EndHorizontal();
}
}
public abstract class AbstractSystem : ISystem
{
public abstract Type ObjectType { get; }
public virtual int ExecutionPriority { get { return 100; } }
public abstract void Init();
public abstract void Update();
}
public static class ComponentFactory
{
public static readonly Dictionary<ComponentTypes, Type> ComponentLookup = new Dictionary<ComponentTypes, Type>
{
{ComponentTypes.SampleComponent, typeof(SampleComponent)}
};
public static IComponent Create(ComponentTypes type, Entity entity)
{
if (!ComponentLookup.ContainsKey(type)) return null;
return (IComponent) Activator.CreateInstance(ComponentLookup[type], entity);
}
}
public class Entity
{
private readonly Dictionary<Type, IComponent> _components = new Dictionary<Type, IComponent>();
protected Entity(IEnumerable<ComponentTypes> componentTypes)
{
foreach (var ctype in componentTypes)
{
AddComponent(ctype);
}
}
public virtual void OnDestroy()
{
}
public bool HasComponent(ComponentTypes type)
{
return _components.ContainsKey(ComponentFactory.ComponentLookup[type]);
}
public T GetComponent<T>() where T : IComponent
{
var t = typeof(T);
if (!_components.ContainsKey(t))
throw new KeyNotFoundException(string.Format("Unable to find component {0} on {1}", t, GetType()));
return (T) _components[t];
}
protected void AddComponent(ComponentTypes ctype)
{
var comp = ComponentFactory.Create(ctype, this);
AddComponent(comp);
}
protected void AddComponent(IComponent component)
{
_components.Add(component.ObjectType, component);
EntityPool.AddComponent(component);
}
public IEnumerable<IComponent> Components
{
get
{
return _components.Values;
}
}
}
public static class EntityPool
{
/// <summary>
/// A list of components sorted by their type, allowing O(1) lookup for components
/// </summary>
private static readonly Dictionary<ComponentTypes, List<IComponent>> ComponentPools =
new Dictionary<ComponentTypes, List<IComponent>>(Enum.GetNames(typeof(ComponentTypes)).Length);
/// <summary>
/// A list of systems on the game
/// </summary>
private static readonly List<ISystem> SystemPools = new List<ISystem>();
/// <summary>
/// Static constructor
/// </summary>
static EntityPool()
{
Reset();
}
/// <summary>
/// Resets the component and system pools to their fresh, empty state
/// </summary>
public static void Reset()
{
foreach (var componentList in ComponentPools.Values)
{
var tempList = new List<IComponent>(componentList);
foreach (var component in tempList)
{
DestroyComponent(component);
}
}
SystemPools.Clear();
ComponentPools.Clear();
var enumVals = Enum.GetValues(typeof(ComponentTypes));
foreach (var ev in enumVals)
{
var ct = (ComponentTypes) ev;
ComponentPools.Add(ct, new List<IComponent>());
}
}
/// <summary>
/// Adds a component to the pool
/// </summary>
/// <param name="component"></param>
public static void AddComponent(IComponent component)
{
ComponentPools[component.ComponentType].Add(component);
}
/// <summary>
/// Adds a system into the pool and sorts by execution priority
/// </summary>
/// <param name="system"></param>
public static void AddSystem(ISystem system)
{
SystemPools.Add(system);
system.Init();
SystemPools.Sort((a, b) => a.ExecutionPriority.CompareTo(b.ExecutionPriority));
}
/// <summary>
/// Removes all the components attached to an entity from the pool
/// </summary>
/// <param name="entity"></param>
public static void DestroyEntity(Entity entity)
{
foreach (var component in entity.Components)
{
DestroyComponent(component);
}
entity.OnDestroy();
}
/// <summary>
/// Removes the given component from the pool
/// </summary>
/// <param name="component"></param>
public static void DestroyComponent(IComponent component)
{
component.OnDestroy();
ComponentPools[component.ComponentType].Remove(component);
}
/// <summary>
/// Runs through all the systems and updates them
/// </summary>
public static void Update()
{
foreach (var system in SystemPools)
{
system.Update();
}
}
/// <summary>
/// Gets the IComponents references of a given type in the system
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<IComponent> Get(ComponentTypes type)
{
return ComponentPools[type];
}
/// <summary>
/// Gets the components of a given type in the system
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<T> Get<T>(ComponentTypes type) where T : IComponent
{
return ComponentPools[type].Cast<T>();
}
/// <summary>
/// Gets a component type by ID
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="type"></param>
/// <param name="id"></param>
/// <returns></returns>
public static T Get<T>(ComponentTypes type, string id) where T : class, IComponent
{
var comps = Get(type);
return comps.FirstOrDefault(o => o.ID == id) as T;
}
}
[Serializable]
public sealed class SampleComponent : AbstractComponent
{
public float AWeirdNumber;
#region AbstractComponent Implementation
private static readonly Type _type = typeof(SampleComponent);
public SampleComponent(Entity owner) : base(owner)
{
}
public override Type ObjectType
{
get { return _type; }
}
public override ComponentTypes ComponentType
{
get
{
return ComponentTypes.SampleComponent;
}
}
#endregion
}
public sealed class SampleEntity : Entity
{
public SampleEntity() : base(new List<ComponentTypes>
{
ComponentTypes.SampleComponent
}) { }
}
public class SampleSystem : AbstractSystem
{
public override void Update()
{
var samples = EntityPool.Get<SampleComponent>(ComponentTypes.SampleComponent);
foreach (var sample in samples)
{
sample.AWeirdNumber = Random.value;
}
}
#region Abstract System Implementation
private static readonly Type _type = typeof(SampleSystem);
public override Type ObjectType
{
get { return _type; }
}
public override void Init()
{
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment