Skip to content

Instantly share code, notes, and snippets.

@karlgluck
Last active May 17, 2018 00:59
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karlgluck/a43a4dbb31382ebba834bd050f18e770 to your computer and use it in GitHub Desktop.
Save karlgluck/a43a4dbb31382ebba834bd050f18e770 to your computer and use it in GitHub Desktop.
My quick and incomplete version of Overwatch's Entity-Component System from the GDC17 presentation. Mostly written to think about how this would be implemented in a language with reflection.
public class ECSEntity
{
public ArrayList Components;
public static ECSEntity Acquire ()
{
throw new System.NotImplementedException();
ECSEntity retval = null;
return retval;
}
public static void Release (ECSEntity entity)
{
for (int i = 0; i < entity.Components.Count; ++i)
{
ECSComponent.Release ((ECSComponent)entity.Components[i]);
}
}
public ECSComponent Attach (Type componentType)
{
return ECSComponent.Acquire (this, componentType);
}
}
public class ECSComponent
{
private bool living;
public ECSEntity Entity { get; private set; }
private static ArrayList PoolLive = new ArrayList();
private static ArrayList PoolDead = new ArrayList();
private static int ReleasedThisFrame = 0;
private static int AcquiredThisFrame = 0;
private static ArrayList AllocatedThisFrame = new ArrayList();
public static void Frame ()
{
for (int i = PoolLive.Count - 1; ReleasedThisFrame > 0 && i >= 0; --i, --ReleasedThisFrame)
{
ECSComponent component = (ECSComponent)PoolLive[i];
if (component.living)
{
continue;
}
PoolLive.RemoveAt (i);
Insert(PoolDead, component);
}
for (int i = PoolDead.Count - 1; AcquiredThisFrame > 0 && i >= 0; --i, --AcquiredThisFrame)
{
ECSComponent component = (ECSComponent)PoolDead[i];
if (!component.living)
{
continue;
}
PoolDead.RemoveAt (i);
Insert(PoolLive, component);
}
for (int i = 0; i < AllocatedThisFrame.Count; ++i)
{
Insert(PoolLive, (ECSComponent)AllocatedThisFrame[i]);
}
AllocatedThisFrame.Clear ();
}
public static ECSComponent Acquire(ECSEntity entity, Type componentType)
{
int min, max;
if (!typeof(ECSComponent).IsAssignableFrom(componentType))
{
throw new System.InvalidOperationException("Type '"+componentType.ToString()+"' must extend ECSComponent");
}
FindBounds (PoolDead, componentType, out min, out max);
ECSComponent retval = null;
for (int i = min; i < max; ++i)
{
var component = (ECSComponent)PoolDead[i];
if (component.living)
{
continue;
}
retval = component;
++AcquiredThisFrame;
break;
}
if (retval == null)
{
retval = (ECSComponent)Activator.CreateInstance(componentType);
AllocatedThisFrame.Add(retval);
}
entity.Components.Add (retval);
retval.Entity = entity;
retval.living = true;
return retval;
}
public static void Release(ECSComponent component)
{
var type = component.GetType ();
int min, max;
FindBounds (PoolLive, type, out min, out max);
for (int i = min; i < max; ++i)
{
ECSComponent poolComponent = (ECSComponent)PoolLive[i];
if (!object.ReferenceEquals (poolComponent, component))
{
continue;
}
if (component.living)
{
++ReleasedThisFrame;
component.living = false;
return;
}
else
{
throw new System.InvalidOperationException("ECSComponent.Release({already released component})");
}
}
throw new System.InvalidOperationException("ECSComponent.Release({invalid component})");
}
private static void Insert (ArrayList pool, ECSComponent component)
{
throw new System.NotImplementedException ();
}
private static void FindBounds (ArrayList pool, Type type, out int min, out int max)
{
throw new System.NotImplementedException ();
}
public static void ForEach<T0> (Action<T0> callback)
where T0 : ECSComponent
{
int min, max;
FindBounds (PoolLive, typeof(T0), out min, out max);
for (int i0 = min; i0 < max; ++i0)
{
callback ((T0)PoolLive[i0]);
}
}
public static void ForEach<T0,T1> (Action<T0,T1> callback)
where T0 : ECSComponent
where T1 : ECSComponent
{
int min0, max0,
min1, max1;
FindBounds (PoolLive, typeof(T0), out min0, out max0);
FindBounds (PoolLive, typeof(T1), out min1, out max1);
for (int i0 = min0; i0 < max0; ++i0)
{
T0 c0 = (T0)PoolLive[i0];
ECSEntity entity = c0.Entity;
for (int i1 = min1; i1 < max1; ++i1)
{
T1 c1 = (T1)PoolLive[i1];
if (!object.ReferenceEquals (entity, c1.Entity))
{
continue;
}
callback (c0, c1);
}
}
}
public static void ForEach<T0,T1,T2> (Action<T0,T1,T2> callback)
where T0 : ECSComponent
where T1 : ECSComponent
where T2 : ECSComponent
{
int min0, max0,
min1, max1,
min2, max2;
FindBounds (PoolLive, typeof(T0), out min0, out max0);
FindBounds (PoolLive, typeof(T1), out min1, out max1);
FindBounds (PoolLive, typeof(T2), out min2, out max2);
for (int i = min0; i < max0; ++i)
{
T0 c0 = (T0)PoolLive[i];
ECSEntity entity = c0.Entity;
for (int i1 = min1; i1 < max1; ++i1)
{
T1 c1 = (T1)PoolLive[i1];
if (!object.ReferenceEquals (entity, c1.Entity))
{
continue;
}
for (int i2 = min2; i2 < max2; ++i2)
{
T2 c2 = (T2)PoolLive[i2];
if (!object.ReferenceEquals (entity, c2.Entity))
{
continue;
}
callback (c0, c1, c2);
}
}
}
}
public static void ForEach<T0,T1,T2,T3> (Action<T0,T1,T2,T3> callback)
where T0 : ECSComponent
where T1 : ECSComponent
where T2 : ECSComponent
where T3 : ECSComponent
{
int min0, max0,
min1, max1,
min2, max2,
min3, max3;
FindBounds (PoolLive, typeof(T0), out min0, out max0);
FindBounds (PoolLive, typeof(T1), out min1, out max1);
FindBounds (PoolLive, typeof(T2), out min2, out max2);
FindBounds (PoolLive, typeof(T3), out min3, out max3);
for (int i = min0; i < max0; ++i)
{
T0 c0 = (T0)PoolLive[i];
ECSEntity entity = c0.Entity;
for (int i1 = min1; i1 < max1; ++i1)
{
T1 c1 = (T1)PoolLive[i1];
if (!object.ReferenceEquals (entity, c1.Entity))
{
continue;
}
for (int i2 = min2; i2 < max2; ++i2)
{
T2 c2 = (T2)PoolLive[i2];
if (!object.ReferenceEquals (entity, c2.Entity))
{
continue;
}
for (int i3 = min3; i3 < max3; ++i3)
{
T3 c3 = (T3)PoolLive[i3];
if (!object.ReferenceEquals (entity, c3.Entity))
{
continue;
}
callback(c0,c1,c2,c3);
}
}
}
}
}
}
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment