-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Generator: Create Flyweight Pattern
Summary
Add a source generator that produces boilerplate-free, GoF-consistent Flyweight pattern implementations with explicit key semantics, deterministic caching, and configurable eviction.
The generator lives in PatternKit.Generators and emits self-contained C# with no runtime PatternKit dependency.
Primary goals:
- Generate flyweight caches with explicit keys and factory creation.
- Make thread-safety, eviction, and comparer behavior explicit.
- Provide deterministic policies and clear diagnostics.
Motivation / Problem
Flyweight is often re-implemented ad hoc:
- inconsistent keying/comparers
- unclear threading guarantees
- unbounded caches that leak memory
- hard-to-test eviction policies
A generator can emit a correct, consistent flyweight cache once.
Supported Targets (must-have)
The generator must support creating flyweights for:
partial class/partial structpartial record class/partial record struct
Two usage models:
- Factory-on-type: annotate the flyweight value type; generator emits a cache type.
- Cache host: annotate a host type that declares key/value types explicitly.
Proposed User Experience
A) Annotate value type, generate cache
[Flyweight(typeof(string), CacheTypeName = "GlyphCache")]
public partial record struct Glyph(char Value, int Width);
public static partial class Glyph
{
[FlyweightFactory]
private static Glyph Create(string key) => /* ... */;
}Generated (representative shape):
public sealed partial class GlyphCache
{
public GlyphCache(IEqualityComparer<string>? comparer = null);
public Glyph Get(string key);
public bool TryGet(string key, out Glyph value);
public void Clear();
}B) Bounded cache
[Flyweight(typeof(string), Capacity = 10_000, Eviction = FlyweightEviction.Lru)]
public partial record struct Glyph(char Value, int Width);Generated cache uses deterministic eviction.
Attributes / Surface Area
Namespace: PatternKit.Generators.Flyweight
Core
-
[Flyweight]on value type or cache hostType KeyTypestring? CacheTypeName(default:<ValueTypeName>FlyweightCache)int Capacity(default: 0 = unbounded)FlyweightEviction Eviction(default:None)FlyweightThreadingPolicy Threading(default:Locking)bool GenerateTryGet(default: true)
-
[FlyweightFactory]on factory method- required signature:
static TValue Create(TKey key)orstatic ValueTask<TValue> CreateAsync(TKey key, CancellationToken ct = default)
- required signature:
Enums:
FlyweightEviction:None,Lru(v1),Ttl(v2)FlyweightThreadingPolicy:SingleThreadedFast,Locking,Concurrent
Semantics (must-have)
Keying
-
Uses
IEqualityComparer<TKey>. -
If none provided:
- use
EqualityComparer<TKey>.Default.
- use
-
For
string, optional case-insensitive mode can be added later.
Creation
-
On miss, cache calls generated factory.
-
Concurrency semantics must be explicit:
- v1 (recommended): under Locking policy, factory invoked at most once per key.
Threading policies
SingleThreadedFast: no locks; documented not thread-safe.Locking: lock around dictionary operations; ensures single-create per key.Concurrent: usesConcurrentDictionary; creation semantics must be documented (may call factory multiple times unless guarded).
Eviction
V1:
-
NoneandLru. -
If
Capacity > 0:- enforce capacity with deterministic eviction.
Lruimplementation must be O(1) amortized and deterministic.
Allocation expectations
- Executing
Getshould not allocate on hits. - Eviction bookkeeping allocates minimally and predictably.
Diagnostics (must-have)
Stable IDs, actionable:
PKFLY001Type marked[Flyweight]must bepartial.PKFLY002No[FlyweightFactory]method found.PKFLY003Multiple[FlyweightFactory]methods found.PKFLY004Factory method signature invalid.PKFLY005Cache type name conflicts with existing type.PKFLY006Invalid eviction configuration (e.g., Capacity=0 with Lru).
Generated Code Layout
ValueTypeName.Flyweight.g.cs
Determinism:
- stable emission order by fully-qualified symbol name.
Testing Expectations
-
Same key returns same value instance/value (as appropriate for TValue).
-
Factory invoked once per key under Locking.
-
Capacity eviction is deterministic (drop oldest / least recently used).
-
Threading policy smoke tests.
-
Diagnostics:
- missing/duplicate factory
- invalid config
Acceptance Criteria
-
[Flyweight]works for class/struct/record class/record struct. - Generated cache supports
Get+ optionalTryGet. - Threading policy is explicit and tested.
- Eviction supports None and Lru (v1) with deterministic behavior.
- Reflection-free implementation.
- Diagnostics cover invalid usage/config.
- Tests cover cache hits/misses, factory behavior, and eviction.