namespace Ghost.Shader.Concept; /// /// Represents a shader keyword that can toggle shader features. /// Keywords are immutable and interned for fast comparison. /// public readonly struct ShaderKeyword : IEquatable { private readonly int _id; private readonly KeywordScope _scope; public int Id => _id; public KeywordScope Scope => _scope; public bool IsValid => _id >= 0; internal ShaderKeyword(int id, KeywordScope scope) { _id = id; _scope = scope; } public bool Equals(ShaderKeyword other) => _id == other._id && _scope == other._scope; public override bool Equals(object? obj) => obj is ShaderKeyword other && Equals(other); public override int GetHashCode() => HashCode.Combine(_id, _scope); public static bool operator ==(ShaderKeyword left, ShaderKeyword right) => left.Equals(right); public static bool operator !=(ShaderKeyword left, ShaderKeyword right) => !left.Equals(right); } public enum KeywordScope : byte { /// Keywords set globally (e.g., platform, quality settings) Global, /// Keywords set per-material instance Local } /// /// Manages keyword registration and fast lookup. /// Thread-safe for registration, lock-free for lookups. /// public sealed class ShaderKeywordRegistry { private readonly Dictionary _keywords = new(); private readonly Dictionary _idToName = new(); private int _nextId = 0; private readonly object _lock = new(); public static ShaderKeywordRegistry Instance { get; } = new(); private ShaderKeywordRegistry() { } public ShaderKeyword GetOrRegister(string name, KeywordScope scope) { string key = $"{scope}:{name}"; lock (_lock) { if (_keywords.TryGetValue(key, out var existing)) return existing; var keyword = new ShaderKeyword(_nextId++, scope); _keywords[key] = keyword; _idToName[keyword.Id] = name; return keyword; } } public string? GetName(ShaderKeyword keyword) { lock (_lock) { return _idToName.TryGetValue(keyword.Id, out var name) ? name : null; } } }