Skip to content

Fox Typing Neofox: hug_duck_heart ​

Neofox: thumbsup Designing Great Components

How do we make components as useful as possible while keeping them simple? This guide covers best practices for component design in fennecs!

The Challenge ​

When building an ECS, you want components that are:

  • Type-safe – Easy to identify and impossible to mix up
  • Readable – Self-documenting in delegate code
  • Distinct – No clashes between similar data (e.g., multiple Vector3 uses)
  • Efficient – Fast to store and process

Record Structs: The Sweet Spot ​

C# 10's record struct is perfect for fennecs components:

cs
public record struct Position(Vector3 Value);
public record struct Velocity(Vector3 Value);
public record struct Acceleration(Vector3 Value);

Why Record Structs? ​

FeatureBenefit
Value semanticsPredictable equality comparison
Minimal boilerplateOne line per component
Nice ToString()Great for debugging
Tightly packedExcellent cache performance
with expressionsEasy partial updates

Using with Expressions ​

Records support partial reinstantiation:

cs
public record struct Transform(Vector3 Position, Quaternion Rotation);

var tr = new Transform(Vector3.Zero, Quaternion.Identity);

// Create new instances with some fields changed
var tr2 = tr with { Position = new Vector3(1, 2, 3) };
var tr3 = tr with { Rotation = Quaternion.CreateFromYawPitchRoll(0.1f, 0.2f, 0.3f) };

Type Wrapping: Avoiding Confusion Neofox: think ​

Without proper typing, you might end up with ambiguous components:

cs
// ❌ Confusing: What do these floats represent?
entity.Add<float>(10.0f);  // Is this speed? health? what?
entity.Add<float>(100.0f); // Can't even have two floats!

With fox typing:

cs
// ✅ Clear: Each type is self-documenting
public record struct Speed(float Value);
public record struct Health(float Value);

entity.Add(new Speed(10.0f));
entity.Add(new Health(100.0f));

Practical Examples ​

Game Stats ​

cs
public record struct Health(int Current, int Max);
public record struct Mana(int Current, int Max);
public record struct Stamina(float Value);
public record struct Experience(int Value);
public record struct Level(int Value);

Physics ​

cs
public record struct Position(Vector3 Value);
public record struct Velocity(Vector3 Value);
public record struct Acceleration(Vector3 Value);
public record struct Mass(float Kg);
public record struct Radius(float Value);

Implicit Conversions ​

For ergonomic use, add conversion operators:

cs
public record struct Health(int Value)
{
    public static implicit operator Health(int value) => new(value);
    public static implicit operator int(Health health) => health.Value;
}

// Now you can do:
entity.Add<Health>(100);  // Implicit conversion from int
int hp = entity.Ref<Health>(); // Implicit conversion to int

Quick Reference ​

PatternExampleUse Case
Simple wrapperrecord struct Speed(float Value)Single value
Multi-fieldrecord struct Health(int Current, int Max)Related values
With operatorsAdd implicit operatorErgonomic API
Plain structstruct Enemy;Zero-size tags

Best Practices Neofox: comfy ​

  1. One purpose per type – Don't reuse types for different meanings
  2. Descriptive names – Health, not HP or H
  3. Use record structs – Unless you need sharing or special behavior
  4. Consider operators – For frequently used numeric types
  5. Group related data – Better to have Transform(Position, Rotation) than separate components if they're always used together

Neofox: science Future SIMD Support

Future versions of fennecs may introduce Fox128<T> and Fox256<T> interfaces for SIMD-optimized operations on 128-bit and 256-bit aligned types.