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 namesHealth, 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.

fennecs is released under the MIT License. Neofox is released under the CC BY-NC-SA 4.0 License.