Skip to content

Instantly share code, notes, and snippets.

@Dykam
Last active December 29, 2015 15:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Dykam/cf5be65a14a84caf9191 to your computer and use it in GitHub Desktop.
Save Dykam/cf5be65a14a84caf9191 to your computer and use it in GitHub Desktop.
Comp# - Experimentations with dialecting C#
Why is it so hard to set a title, GitHub?

I love entity/component systems. And static typing can be very cool and fast. Combining the two would be amazing. Oh, and I really like C#, so why not that too. So here are my experiments and musings in adding an entity/component feature to C# as a superset, leaving the existing language as it is.

Concepts

- Entities are bare identity objects, nothing else
- Concepts are interfaces
- Components implement concepts
- Concepts can require other concepts
- Components can require concepts
- Components can implicitly implement concept's data members using the keyword `implicitly`
- A component and a concept can have the same name
- `void` is an absence of an entity
- attach prepends a component to an entity
- augment makes a referential copy and prepends the component. Similar to but different from javascript prototypal inheritance

Mapping C# to Comp#

- interface -> concept
- class -> component
- struct -> value component
    - Can imply other value components
    - entities are value entities if and only if all components are value entities
    - The components on value entities cannot be modified after initialization

concept <Name> {
    requires { <concept1>, <concept2>, <concept*> }
}

component <Name> {
    requires { <concept1>, <concept2>, <concept*> }
    [implicitly] provides { <concept1>, <concept2>, <concept*> }
    implies { <component1>, <component2>, <component*> }
    { <concept> }.<Member> = <implementation>
}

The implicit examples show it becomes very close to C# for basic setups

concept Position {
{ Vector } Position { get; set; }
}
concept Velocity {
{ Vector } Velocity { get; set; }
}
concept PhysicsTick {
void Tick();
}
concept Living {
{ int } Health { get; private set; }
}
component PhysicsObject {
implies { Position, Velocity }
provides { PhysicsTick }
{ PhysicsTick }.Tick() {
{ Position }.Position += { Velocity }.Velocity;
}
}
component Player {
implies { Living, PhysicsObject }
Player() {
{ Living }.Health = 10;
}
}
component Engine {
provides { PhysicsTick, Collection<{PhysicsTick}>} };
implies { List<{PhysicsTick}> }
{ PhysicsTick }.Tick() { // Contribution
foreach (var item in { Collection<{PhysicsTick}> })
{
item{PhysicsTick}.Tick();
}
}
}
// demo
Main() {
ent player = new { Player }; // typeof(player) == { Player, PhysicsObject | Living, PhysicsTick, Position, Velocity }
ent engine = new { Engine }; // typeof(engine) == { Engine, List<{PhysicsTick}> | PhysicsTick, Collection<{PhysicsTick}>} }
engine{ Collection<PhysicsTick> }.Add(player);
while (true)
{
engine{ Engine }.Tick();
}
}
concept Position {
Vector Position { get; set; }
}
concept Velocity {
Vector Velocity { get; set; }
}
concept PhysicsTick {
void Tick();
}
concept Living {
int Health { get; private set; }
}
component PhysicsObject {
implies { Position, Velocity }
provides PhysicsTick;
Tick() {
Position += Velocity;
}
}
component Player {
implicitly provides Living;
implies Living, PhysicsObject;
Player() {
Health = 10;
}
}
component Engine {
provides PhysicsTick;
implies List<{PhysicsTick}>
Tick() { // Contribution
foreach (var item in this)
{
item.Tick();
}
}
}
// demo
Main() {
ent player = new Player; // typeof(player) == { Player, PhysicsObject | Living, PhysicsTick, Position, Velocity }
ent engine = new Engine; // typeof(engine) == { Engine, List<{PhysicsTick}> | PhysicsTick, Collection<{PhysicsTick}>} }
engine.Add(player);
while (true)
{
engine.Tick();
}
}
// Possible implementation for runtime version
// Slower, involves some reflection and a lot of lookups
[Concept]
interface Position {
Vector Position { get; set; }
}
[Concept]
interface Velocity {
Vector Velocity { get; set; }
}
[Concept]
interface PhysicsTick {
void Tick();
}
[Concept]
interface Living {
int Health { get; private set; }
}
[Component]
[Provides(typeof(PhysicsTick)]
[Implies(typeof(Position), typeof(Velocity)]
class PhysicsObject : Component, Velocity {
void PhysicsTick.Tick() {
Get<Position>().Position += Velocity;
}
}
[Component]
[Implies(typeof(Living, PhysicsObject)]
class Player : Component {
public Player() {
Get<Health>().Health = 10;
}
}
[Component]
[Provides(typeof(PhysicsTick)]
[Implies(typeof(List<PhysicsTick>)]
class Engine : Component, PhysicsTick {
public void Tick() { // Contribution
foreach (var item in Get<List<PhysicsTick>>())
{
item.Get<PhysicsTick>().Tick();
}
}
}
// demo
Main() {
var player = Entity.Create<Player>(); // Entity.TypeOf(player) == { Player, PhysicsObject | Living, PhysicsTick, Position, Velocity }
var engine = Entity.Create<Engine>(); // Entity.TypeOf(engine) == { Engine, List<{PhysicsTick}> | PhysicsTick, Collection<{PhysicsTick}>} }
engine.Get<List<PhysicsTick>>().Add(player);
while (true)
{
engine.Get<PhysicsTick>.Tick();
}
}
// Possible implementation for runtime version
// Slower, involves some reflection and a lot of lookups
[Component]
[Provides(typeof(INode<T>))]
class Node<T> : INode<T> { // Why not both
Entity INode<T>.Next { get; set }
T INode<T>.Value { get; set ;}
}
[Concept]
interface INode<T> { // Why not both
Entity Next { get; set }
T Value { get; set ;}
}
[Concept]
interface ToString {
String ToString();
}
[Concept]
interface Enumerable<T> {
Enumerator<T> GetEnumerator();
}
[Concept]
interface Enumerator<T> {
bool MoveNext();
T Current { get; }
}
[Component]
[Requires(typeof(INode<T>))]
[Provides(typeof(Enumerable<T>))]
class NodeEnumerable<T> : Component, Enumerable<T> {
Enumerable<T>.GetEnumerator() => new NodeEnumerator<T>}(Entity);
}
[Component]
[Provides(typeof(Enumerator<T>))]
class NodeEnumerator<T> : Component, Enumerator<T> {
T Enumerator<T>.Current => currentNode.Get<INode<T>>().Value;
Component currentNode;
NodeEnumerator(Entity node) {
currentNode = node;
}
bool Enumerator<T>.MoveNext() {
if(node.Get<INode<T>>().Next == null)
return false;
node = node.Get<INode<T>>().Next;
}
}
[Component]
[Provides(typeof(Add<Vector2<T>>), typeof(Mul<Vector2<T>>))]
class Vector2<T> where T : Component {
T X;
T Y;
Add(Entity a, Entity) {
return Entity.Create<Vector2<T>>(new {
X = a.Entity.Get<Vector2<T>>().X + b.Entity.Get<Vector2<T>>().X,
Y = a.Entity.Get<Vector2<T>>().Y + b.Entity.Get<Vector2<T>>().Y,
});
}
Mul(Entity a, Entity b) {
return Entity.Create<Vector2<T>>({
X = a.Entity.Get<Vector2<T>>().X * b.Entity.Get<Vector2<T>>().X,
Y = a.Entity.Get<Vector2<T>>().Y * b.Entity.Get<Vector2<T>>().Y,
});
}
}
[Component]
[Requires(typeof(Vector<ToString>)]
[Provides(typeof(ToString))]
class VectorToString : Component, ToString {
ToString.ToString() =>
return $"{{ X: {X.Entity.Get<ToString>().ToString()}, Y: {Y.Entity.Get<ToString>().ToString()} }}";
}
Main() {
var list = Entity.Create<Node<Vector2<float>>>(new {
Value = Entity.Create<Vector2<float>>({
X: 1,
Y: 2,
}),
Next = Entity.Create<Node<Vector2<float>>>(new {
Value = Entity.Create<Vector2<float>>({
X: 12,
Y: 3,
}),
}),
});
list.Complete<NodeEnumerator<Vector<float>>>(); // Prepends to the components on the entity `list`
foreach(var node in list.Get<NodeEnumerator<Vector2<float>>>()) {
var augmented = node.Augment<ToString>();
}
}
concept component Node<T> { // Why not both
{ Node<{T}> } Next { get; set }
{ T } Value { get; set ;}
}
concept ToString {
{ String } ToString();
}
concept Enumerable<T> {
{ Enumerator<T> } GetEnumerator();
}
concept Enumerator<T> {
{ bool } MoveNext();
{ T } Current { get; }
}
component NodeEnumerable<T> {
requires { Node<{ T }> }
provides { Enumerable<{ T }> }
{ Enumerable<{ T }> }.GetEnumerator() {
return new { NodeEnumerator<T> } {
{NodeEnumerator<T>}(this);
}
}
}
component NodeEnumerator<T> {
provides { Enumerator<{ T }> }
{ T } Current => currentNode.Value;
{ Node<{ T }> } currentNode;
NodeEnumerator({Node<{T}>} node) {
currentNode = node;
}
{ Enumerable<{ T }> }.MoveNext() {
if(node{Node<{ T }>}.Next == null)
return false;
node = node{Node<{ T }>}.Next;
}
}
component Vector2<T> where T : { Add<{Vector2<{T}>}>, Mul<{Vector2<{T}>}> } {
provides { Add<{Vector2<{T}>}>, Mul<{Vector2<{T}>}> }
{ T } X;
{ T } Y;
{Add<{Vector2<{T}>}>}.Add({ Vector2<T> } a, { Vector2<T> } b) {
return new { Vector2<T> } {
{ Vector2<T> }.X = a{ Vector2<T> }.X + b{ Vector2<T> }.X,
{ Vector2<T> }.Y = a{ Vector2<T> }.Y + b{ Vector2<T> }.Y,
}
}
{Mul<{Vector2<{T}>}>}.Mul({ Vector2<T> } a, { Vector2<T> } b) {
return new { Vector2<T> } {
{ Vector2<T> }.X = a{ Vector2<T> }.X * b{ Vector2<T> }.X,
{ Vector2<T> }.Y = a{ Vector2<T> }.Y * b{ Vector2<T> }.Y,
}
}
}
component VectorToString {
requires { Vector<{ToString}> }
provides { ToString }
{ ToString }.ToString() {
return "{ X: " + X.ToString() + ", Y: " + Y.ToString() + " }";
}
}
Main() {
ent list = new { Node<{Vector2<{float}>}> } {
{Node<{Vector2<{float}>}>}.Value = new { Vector2<{float}> } {
X = 1,
Y = 2,
},
{Node<{Vector2<{float}>}>}.Next = new { Node<{Vector2<{float}>}> } {
{Node<{Vector2<{float}>}>}.Value = new { Vector2<{float}> } {
X = 3,
Y = 4,
},
},
};
complete list with { NodeEnumerator<{Vector<{float}>}> }; // Prepends to the components on the entity `list`
// Without the above, the following would throw: "No component found providing concept Enumerable"
foreach(ent node in list) {
ent augmented = augment node with { ToString }
}
}
concept component Node<T> { // Why not both
Node<T> Next { get; set }
T Value { get; set ;}
}
concept ToString {
String ToString();
}
concept Enumerable<T> {
Enumerator<T> GetEnumerator();
}
concept Enumerator<T> {
bool MoveNext();
T Current { get; }
}
component NodeEnumerable<T> {
requires Node<T>;
provides Enumerable<T>;
GetEnumerator() => new NodeEnumerator<T>}(this);
}
component NodeEnumerator<T> {
provides Enumerator<T>;
T Current => currentNode.Value;
Node<T> currentNode;
NodeEnumerator(Node<T> node) {
currentNode = node;
}
MoveNext() {
if(node.Next == null)
return false;
node = node.Next;
}
}
component Vector2<T> where T : { Add<Vector2<T>>, Mul<Vector2<T>> } {
provides { Add<Vector2<T>>, Mul<Vector2<T>> }
T X;
T Y;
Add(Vector2<T> a, Vector2<T> b) {
return new Vector2<T> {
X = a.X + b.X,
Y = a.Y + b.Y,
}
}
Mul(Vector2<T> a, Vector2<T> b) {
return new Vector2<T> {
X = a.X * b.X,
Y = a.Y * b.Y,
}
}
}
component VectorToString {
requires Vector<ToString>;
provides ToString;
ToString() => return "{ X: " + X.ToString() + ", Y: " + Y.ToString() + " }";
}
Main() {
ent list = new Node<Vector2<float>> } {
Value = new Vector2<float> {
X = 1,
Y = 2,
},
Next = new Node<Vector2<float>> } {
Value = new Vector2<float> {
X = 2,
Y = 3,
},
},
};
complete list with NodeEnumerator<Vector<float>>; // Prepends to the components on the entity `list`
// Without the above, the following would throw: "No component found providing concept Enumerable"
foreach(ent node in list) {
ent augmented = augment node with ToString;
}
}
/*
Monkey patching is the practise of modifying parts of a system in ways which can affect the full system.
E.g. adding to the prototype of Javascript objects.
In Comp# you can make existing entities with existing frameworks act like other ones by writing your own patch components.
In the following example there will be two frameworks with two vector concepts, where you augment an existing entity so it
uses the data from an existing component to provide for the other concept.
*/
#region Library A
[Concept]
interface component Vec2D {
float X;
float Y;
}
#endregion
#region Library B
[Component]
[Provides(typeof(IVectorF))]
class VectorF : IVectorF {
float X { get; set; }
float Y { get; set; }
}
[Concept]
interface IVectorF {
float X { get; }
float Y { get; }
}
[Concept]
interface Engine {
Process(Entity vectors);
}
#endregion
// The monkey patch
[Component]
[Requires(typeof(Vec2D)]
[Provides(typeof(VectorF)]
class Vec2DToVectorF : VectorF {
X => Entity.Get<Vec2D>().X;
Y => Entity.Get<Vec2D>().Y;
}
Main() {
var engine = // retrieve engine
var vectors = // retrieve Enumerable<Vec2D>
foreach(var vec in vectors.Get<Enumerable<Vec2D>>) {
vec.Complete<Vec2DToVectorF>();
}
// or: vectors = vectors.Get<Enumerable<Vec2D>>()
// .Select(vec => vec.Augment<Vec2DToVectorF>());
while(true) {
engine.Get<Engine>().Process(vectors);
}
}
/*
Monkey patching is the practise of modifying parts of a system in ways which can affect the full system.
E.g. adding to the prototype of Javascript objects.
In Comp# you can make existing entities with existing frameworks act like other ones by writing your own patch components.
In the following example there will be two frameworks with two vector concepts, where you augment an existing entity so it
uses the data from an existing component to provide for the other concept.
*/
#region Library A
concept component Vec2D {
float X;
float Y;
}
#endregion
#region Library B
concept component VectorF {
float X { get; }
float Y { get; }
}
concept Engine {
Process(Enumerable<VectorF> vectors);
}
#endregion
// The monkey patch
component Vec2DToVectorF {
requires Vec2D;
provides VectorF;
X => {Vec2D}.X;
Y => {Vec2D}.Y;
}
Main() {
ent engine = // retrieve engine
ent vectors = // retrieve Enumerable<Vec2D>
foreach(var vec in vectors) {
complete vec with Vec2DToVectorF;
}
// or: vectors = vectors.Select(vec => augment vec with Vec2DToVectorF);
while(true) {
engine.Process(vectors);
}
}
interface Entity {
GUID ID { get; }
T Get<T>();
Entity Complete<T>(object parameters);
Entity Augment();
}
abstract class Component {
private Entity { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
public class ComponentAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
public class ConceptAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = true)]
public class ProvidesAttribute : Attribute
{
public Types { get; set; }
public ProvidesAttribute(params Type[] types) {
Types = types;
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = true)]
public class ImpliesAttribute : Attribute
{
public Types { get; set; }
public ProvidImpliesAttributeesAttribute(params Type[] types) {
Types = types;
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = true)]
public class RequiresAttribute : Attribute
{
public Types { get; set; }
public RequiresAttribute(params Type[] types) {
Types = types;
}
}
static class EntityExtensions {
public static Entity Augment<T>(this Entity entity, object parameters) =>
entity.Augment().Complete<T>(parameters);
public static Entity Augment<T1, T2>(this Entity entity, object parameters1, object parameters2) =>
entity.Augment().Complete<T1, T2>(parameters1, parameters2);
public static Entity Complete<T1, T2>(this Entity entity, object parameters1, object parameters2) =>
entity.Complete<T1>(parameters1).Complete<T2>(parameters2);
}

Transpiling is very possible, however fairly complicated as C#'s full type system gets involved.

concept

Concepts become interfaces

component

Value components become structs, normal components become classes

{ Component, Component }

Component types are instances of Component with explicit implementations of all components

Transpile example

Comp#

concept component Vector {
    float X { get; set; }
    float Y { get; set; }
    Vector(float x, float y) {
        X = x;
        Y = y;
    }
}
component VectorAdder {
    provides Add<Vector>;
    requires Vector;
    Add(Vector other) {
        return new Vector {
            X = X + other.X,
            Y = Y + other.Y,
        }
    }
}

Main() {
    ent vec1 = new { VectorAdder, Vector } { Vector(1, 2) };
    ent vec2 = new Vector(1, 2);
    ent vec3 = vec1 + vec2;
}

C# TODO

interface IVector {
    float X { get; set; }
    float Y { get; set; }
}
class Vector : IVector {
    float X { get; set; }
    float Y { get; set; }
    Vector(float x, float y) {
        X = x;
        Y = y;
    }
}
// TODO
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment