Code/UniformValue.cs
using System;
using System.Collections.Immutable;
using Sandbox;

namespace Goo;

public enum UniformKind : byte
{
    None = 0,
    Color,
    Float,
    Int,
    Bool,
    Vec2,
    Vec3,
    Vec4,
    Texture,
}

public readonly struct UniformValue : IEquatable<UniformValue>
{
    public readonly string Name;
    public readonly UniformKind Kind;
    public readonly Color   ColorValue;
    public readonly Vector4 VecValue;
    public readonly float   FloatValue;
    public readonly int     IntValue;
    public readonly bool    BoolValue;
    public readonly Texture? TextureValue;

    UniformValue(string name, UniformKind kind,
        Color color = default, Vector4 vec = default,
        float f = 0f, int i = 0, bool b = false, Texture? tex = null)
    {
        Name = name;
        Kind = kind;
        ColorValue = color;
        VecValue = vec;
        FloatValue = f;
        IntValue = i;
        BoolValue = b;
        TextureValue = tex;
    }

    public static UniformValue OfColor(string name, Color value)
        => new(name, UniformKind.Color, color: value);

    public static UniformValue OfFloat(string name, float value)
        => new(name, UniformKind.Float, f: value);

    public static UniformValue OfInt(string name, int value)
        => new(name, UniformKind.Int, i: value);

    public static UniformValue OfBool(string name, bool value)
        => new(name, UniformKind.Bool, b: value);

    public static UniformValue OfVec2(string name, Vector2 value)
        => new(name, UniformKind.Vec2, vec: new Vector4(value.x, value.y, 0f, 0f));

    public static UniformValue OfVec3(string name, Vector3 value)
        => new(name, UniformKind.Vec3, vec: new Vector4(value.x, value.y, value.z, 0f));

    public static UniformValue OfVec4(string name, Vector4 value)
        => new(name, UniformKind.Vec4, vec: value);

    public static UniformValue OfTexture(string name, Texture? value)
        => new(name, UniformKind.Texture, tex: value);

    public bool Equals(UniformValue other)
    {
        if (Name != other.Name || Kind != other.Kind) return false;
        return Kind switch
        {
            UniformKind.Color   => ColorValue == other.ColorValue,
            UniformKind.Float   => FloatValue == other.FloatValue,
            UniformKind.Int     => IntValue == other.IntValue,
            UniformKind.Bool    => BoolValue == other.BoolValue,
            UniformKind.Vec2 or UniformKind.Vec3 or UniformKind.Vec4 => VecValue == other.VecValue,
            UniformKind.Texture => ReferenceEquals(TextureValue, other.TextureValue),
            _ => true,
        };
    }

    public override bool Equals(object? obj) => obj is UniformValue v && Equals(v);

    public override int GetHashCode() => HashCode.Combine(Name, Kind);

    public static bool operator ==(UniformValue a, UniformValue b) => a.Equals(b);
    public static bool operator !=(UniformValue a, UniformValue b) => !a.Equals(b);

    internal static bool SequenceEqual(ImmutableArray<UniformValue> a, ImmutableArray<UniformValue> b)
    {
        if (a.IsDefaultOrEmpty && b.IsDefaultOrEmpty) return true;
        if (a.IsDefaultOrEmpty != b.IsDefaultOrEmpty) return false;
        if (a.Length != b.Length) return false;
        for (int i = 0; i < a.Length; i++)
            if (!a[i].Equals(b[i])) return false;
        return true;
    }
}