Source Code Generation
Rishenerator acts as both a Source Generator and a Source Analyzer, drastically improving code safety and developer velocity.
It enforces critical patterns to prevent runtime errors and be performant:
- Type Safety: Enforces
[RishValueType]on Props and State. - Memory Safety: Prevents the creation of managed types (like
Element,Children,RishList) outside of aManagedContext. - Context Safety: Prevents the misuse of the
ManagedContextlifecycle.
Beyond safety, it eliminates boilerplate by auto-generating Factory Methods, Comparers, State Setters and more. The following sections provide examples of user code alongside auto-generated code.
This page is intended to better understand the system internals. It should not be used as a reference to avoid using Rishenerator.
Factory Methods (Create)
Rishenerator scans your Props struct and generates static Create methods for your Elements. This provides a strongly-typed API for instantiating elements.
Every field in the Props struct becomes an optional parameter in the Create method.
// ↓↓↓ User Code ↓↓↓
public partial class Foo : RishElement<FooProps>
{
protected override Element Render() => Element.Null;
}
[RishValueType]
public struct FooProps {
public bool var1;
public float var2;
public int var3;
public Action callback;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
public partial class Foo
{
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create() => Create(default(ulong), default(FooProps));
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(ulong key) => Create(key, default(FooProps));
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(FooProps props) => Create(default(ulong), props);
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(System.Boolean var1 = default, System.Single var2 = default, System.Int32 var3 = default, System.Action callback = default) => Create(default(ulong), new FooProps { var1 = var1, var2 = var2, var3 = var3, callback = callback });
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(ulong key, FooProps props) => RishUI.Rish.Create<Foo, FooProps>(key, props);
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(ulong key, System.Boolean var1 = default, System.Single var2 = default, System.Int32 var3 = default, System.Action callback = default) => Create(key, new FooProps { var1 = var1, var2 = var2, var3 = var3, callback = callback });
}
// ↑↑↑ Autogenerated ↑↑↑If your struct defines a [Default] property or has any fields with [DefaultValue] or [DefaultCode] attributes, Rishenerator injects logic to respect those values when arguments are omitted.
// ↓↓↓ User Code ↓↓↓
public partial class Foo : RishElement<FooProps>
{
protected override Element Render() => Element.Null;
}
[RishValueType]
public struct FooProps {
public bool var1;
public int var2;
[Default]
private static FooProps Default => new() { var2 = 3 };
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
public partial class Foo
{
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create() => Create(default(ulong), RishUI.Defaults.GetValue<FooProps>());
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(ulong key) => Create(key, RishUI.Defaults.GetValue<FooProps>());
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(FooProps props) => Create(default(ulong), props);
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(RishUI.Overridable<System.Boolean> var1 = default, RishUI.Overridable<System.Int32> var2 = default) { var defaultValue = RishUI.Defaults.GetValue<FooProps>(); return Create(default(ulong), new FooProps { var1 = var1.GetValue(defaultValue.var1), var2 = var2.GetValue(defaultValue.var2) }); }
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(ulong key, FooProps props) => RishUI.Rish.Create<Foo, FooProps>(key, props);
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(ulong key, RishUI.Overridable<System.Boolean> var1 = default, RishUI.Overridable<System.Int32> var2 = default) { var defaultValue = RishUI.Defaults.GetValue<FooProps>(); return Create(key, new FooProps { var1 = var1.GetValue(defaultValue.var1), var2 = var2.GetValue(defaultValue.var2) }); }
}
// ↑↑↑ Autogenerated ↑↑↑For Props using the [Expand] attribute in VisualAttributes fields, the generator “flattens” them into name, className, and style arguments.
// ↓↓↓ User Code ↓↓↓
public partial class Foo : RishElement<FooProps>
{
protected override Element Render() => Element.Null;
}
[RishValueType]
public struct FooProps {
[Expand]
public VisualAttributes attributes;
[Expand]
public VisualAttributes secondAttributes;
public bool var1;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
public partial class Foo
{
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create() => Create(default(ulong), default(FooProps));
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(ulong key) => Create(key, default(FooProps));
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(FooProps props) => Create(default(ulong), props);
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(RishUI.Name name = default, RishUI.ClassName className = default, RishUI.Style style = default, RishUI.Name secondName = default, RishUI.ClassName secondClassName = default, RishUI.Style secondStyle = default, System.Boolean var1 = default) => Create(default(ulong), new FooProps { visualAttributes = new RishUI.VisualAttributes {name = name, className = className, style = style }, secondVisualAttributes = new RishUI.VisualAttributes {name = secondName, className = secondClassName, style = secondStyle }, var1 = var1 });
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(ulong key, FooProps props) => RishUI.Rish.Create<Foo, FooProps>(key, props);
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(ulong key, RishUI.Name name = default, RishUI.ClassName className = default, RishUI.Style style = default, RishUI.Name secondName = default, RishUI.ClassName secondClassName = default, RishUI.Style secondStyle = default, System.Boolean var1 = default) => Create(key, new FooProps { visualAttributes = new RishUI.VisualAttributes {name = name, className = className, style = style }, secondVisualAttributes = new RishUI.VisualAttributes {name = secondName, className = secondClassName, style = secondStyle }, var1 = var1 });
}
// ↑↑↑ Autogenerated ↑↑↑For VisualElements, it adds styling and children arguments.
// ↓↓↓ User Code ↓↓↓
public partial class Foo : VisualElement, IVisualElement<FooProps>
{
// ...
public void Setup(FooProps props)
{
// ...
}
}
[RishValueType]
public struct FooProps {
public bool var1;
public float var2;
public int var3;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
public partial class Foo
{
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create() => Create(default(ulong), default(RishUI.VisualAttributes), default(FooProps), default(RishUI.Children));
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(ulong key) => Create(key, default(RishUI.VisualAttributes), default(FooProps), default(RishUI.Children));
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(RishUI.VisualAttributes visualAttributes) => Create(default(ulong), visualAttributes, default(FooProps), default(RishUI.Children));
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(RishUI.Name name) => Create(default(ulong), new RishUI.VisualAttributes { name = name }, default(FooProps), default(RishUI.Children));
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(RishUI.ClassName className) => Create(default(ulong), new RishUI.VisualAttributes { className = className }, default(FooProps), default(RishUI.Children));
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(RishUI.Style style) => Create(default(ulong), new RishUI.VisualAttributes { style = style }, default(FooProps), default(RishUI.Children));
// A lot more methods
[RishUI.MemoryManagement.RequiresManagedContext]
public static RishUI.Element Create(ulong key, RishUI.Name name, RishUI.ClassName className, RishUI.Style style, RishUI.Children children, System.Boolean var1 = default, System.Single var2 = default, System.Int32 var3 = default) => Create(key, new RishUI.VisualAttributes { name = name, className = className, style = style }, new FooProps { var1 = var1, var2 = var2, var3 = var3 }, children);
}
// ↑↑↑ Autogenerated ↑↑↑Reserved Keywords
key: Always reserved for Element Keys.name,classNameandstyle: Reserved in VisualElements and RishElements with expandedVisualAttributess.children: Reserved in VisualElements.
Comparers
Rish relies on fast equality checks to determine if an element is Dirty. Rishenerator generates optimized Comparers based on your attributes.
If a struct contains only simple value types, Rishenerator skips generation and Rish will use a raw binary memory comparison (MemCmp) for maximum speed.
// ↓↓↓ User Code ↓↓↓
[RishValueType]
public struct FooProps {
public bool var1;
public float var2;
public int var3;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
// No code generated because we can simply use a binary memory comparison to compare FooProps
// ↑↑↑ Autogenerated ↑↑↑If attributes are present, it generates specific logic.
// ↓↓↓ User Code ↓↓↓
[RishValueType]
public struct FooProps {
public bool var1;
[EpsilonComparison]
public float var2;
[IgnoreComparison]
public int var3;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
[Comparer]
private static bool Equals(FooProps a, FooProps b)
{
return RishUtils.MemCmp(ref a.var1, ref b.var1) && UnityEngine.Mathf.Approximately(a.var2, b.var2);
}
// ↑↑↑ Autogenerated ↑↑↑By default, it compares reference types by reference and ignores delegate types.
// ↓↓↓ User Code ↓↓↓
[RishValueType]
public struct FooProps {
public bool var1;
public string var2;
public Action callback;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
[Comparer]
private static bool Equals(FooProps a, FooProps b)
{
return RishUtils.MemCmp(ref a.var1, ref b.var1) && System.Object.ReferenceEquals(a.var2, b.var2);
}
// ↑↑↑ Autogenerated ↑↑↑If any field is of a type that has a Comparer defined, it will use it.
// ↓↓↓ User Code ↓↓↓
[RishValueType]
public struct FooProps1 {
public bool var1;
public Element var2;
}
[RishValueType]
public struct FooProps2 {
public int var1;
public FooProps1 var2;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
[Comparer]
private static bool Equals(FooProps1 a, FooProps1 b)
{
return RishUtils.MemCmp(ref a.var1, ref b.var1) && RishUtils.Compare(a.var2, b.var2);
}
[Comparer]
private static bool Equals(FooProps2 a, FooProps2 b)
{
return RishUtils.MemCmp(ref a.var1, ref b.var1) && RishUtils.MemCmp(ref a.var2.var1, ref b.var2.var1) && RishUtils.Compare(a.var2.var2, b.var2.var2);
}
// ↑↑↑ Autogenerated ↑↑↑Memory Management
Rish internally manages references to Element, Children, ClassName and RishList using Managed Contexts. Rishenerator automates the complex “claiming” process required to keep these references alive while they are needed.
// ↓↓↓ User Code ↓↓↓
public partial class Foo : RishElement<FooProps, FooState>
{
protected override Element Render() => Element.Null;
}
[RishValueType]
public struct FooProps {
public bool var1;
public Element var2;
}
[RishValueType]
public struct FooState {
public bool var1;
public RishList<int> var2;
public Children var3;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
public partial class Foo : RishUI.MemoryManagement.IManaged<FooProps>
{
void RishUI.MemoryManagement.IManaged<FooProps>.ClaimReferences(FooProps props)
{
var ctx2 = RishUI.Rish.GetOwnerContext<RishUI.Element, RishUI.ManagedElement>(props.var2);
ClaimContext(-2, ctx2);
}
}
public partial class Foo : RishUI.MemoryManagement.IManaged<FooState>
{
void RishUI.MemoryManagement.IManaged<FooState>.ClaimReferences(FooState state)
{
var ctx2147483647 = RishUI.Rish.GetOwnerContext<RishUI.RishList<System.Int32>, RishUI.ManagedRishList<System.Int32>>(state.var2);
ClaimContext(-2147483647, ctx2147483647);
var ctx2147483646 = RishUI.Rish.GetOwnerContext<RishUI.Children, RishUI.ManagedChildren>(state.var3);
ClaimContext(-2147483646, ctx2147483646);
}
}
[RishUI.MemoryManagement.Dependency]
private static void AddDependency(RishUI.MemoryManagement.ManagedContext ctx, FooProps value)
{
ctx.AddDependency(RishUI.Rish.GetOwnerContext<RishUI.Element, RishUI.ManagedElement>(value.var2));
}
[RishUI.MemoryManagement.Dependency]
private static void AddDependency(RishUI.MemoryManagement.ManagedContext ctx, FooState value)
{
ctx.AddDependency(RishUI.Rish.GetOwnerContext<RishUI.RishList<System.Int32>, RishUI.ManagedRishList<System.Int32>>(value.var2));
ctx.AddDependency(RishUI.Rish.GetOwnerContext<RishUI.Children, RishUI.ManagedChildren>(value.var3));
}
// ↑↑↑ Autogenerated ↑↑↑Props Methods
Rishenerator generates member callback methods for all delegate types in Props.
// ↓↓↓ User Code ↓↓↓
public partial class Foo : RishElement<FooProps>
{
protected override Element Render() => Element.Null;
}
[RishValueType]
public struct FooProps {
public bool var1;
public float var2;
public int var3;
public Action callback1;
public Action<bool> callback2;
public Func<float, int> callback3;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
public partial class Foo
{
private void Callback1() => Props.callback1?.Invoke();
private void Callback2(System.Boolean obj) => Props.callback2?.Invoke(obj);
private System.Int32 Callback3(System.Single arg) => Props.callback3?.Invoke(arg) ?? default;
}
// ↑↑↑ Autogenerated ↑↑↑If you define a method that conflicts with a generated method (e.g., you define OnChange), Rishenerator will prefix the generated method with Rish (e.g., RishOnChange).
// ↓↓↓ User Code ↓↓↓
public partial class Foo : RishElement<FooProps>
{
protected override Element Render() => Element.Null;
public void OnChange(float v) {
// Some other logic
RishOnChange(v);
}
}
[RishValueType]
public struct FooProps {
public float value;
public Action<float> onChange;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
public partial class Foo
{
private void RishOnChange(float v) => Props.onChange?.Invoke(v);
}
// ↑↑↑ Autogenerated ↑↑↑State Setters
Rishenerator generates optimized Setter methods for every field in your State struct. These setters handle:
- Mount Check: Ensures the element is mounted.
- Comparison: Prevents updates if the value hasn’t changed.
- Context Claiming: Automatically claims memory contexts for new values.
- Dirty Flag: Marks the element for re-render.
// ↓↓↓ User Code ↓↓↓
public partial class Foo : RishElement<NoProps, FooState>
{
protected override Element Render() => Element.Null;
}
[RishValueType]
public struct FooState {
public bool var1;
public RishList<int> var2;
public Children var3;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
public partial class Foo : RishUI.MemoryManagement.IManaged<FooState>
{
void RishUI.MemoryManagement.IManaged<FooState>.ClaimReferences(FooState state)
{
var ctx2147483647 = RishUI.Rish.GetOwnerContext<RishUI.RishList<System.Int32>, RishUI.ManagedRishList<System.Int32>>(state.var2);
ClaimContext(-2147483647, ctx2147483647);
var ctx2147483646 = RishUI.Rish.GetOwnerContext<RishUI.Children, RishUI.ManagedChildren>(state.var3);
ClaimContext(-2147483646, ctx2147483646);
}
private void SetVar1(System.Boolean v)
{
if(!IsMounted) return;
var state = State;
if(RishUI.RishUtils.MemCmp(ref v, ref state.var1)) return;
state.var1 = v;
SetState(state, false);
Dirty();
}
private void SetVar2(RishUI.RishList<System.Int32> v)
{
if(!IsMounted) return;
var state = State;
if(RishUI.RishUtils.SmartCompare(v, state.var2)) return;
state.var2 = v;
SetState(state, false);
var ctx2147483645 = RishUI.Rish.GetOwnerContext<RishUI.RishList<System.Int32>, RishUI.ManagedRishList<System.Int32>>(v);
ClaimContext(-2147483645, ctx2147483645);
Dirty();
}
private void SetVar3(RishUI.Children v)
{
if(!IsMounted) return;
var state = State;
if(RishUI.RishUtils.SmartCompare(v, state.var3)) return;
state.var3 = v;
SetState(state, false);
var ctx2147483644 = RishUI.Rish.GetOwnerContext<RishUI.Children, RishUI.ManagedChildren>(v);
ClaimContext(-2147483644, ctx2147483644);
Dirty();
}
}
// ↑↑↑ Autogenerated ↑↑↑If you define a method that conflicts with a generated setter (e.g., you define SetList), Rishenerator will prefix the generated method with Rish (e.g., RishSetList).
// ↓↓↓ User Code ↓↓↓
public partial class Foo : RishElement<NoProps, FooState>
{
protected override Element Render() => Element.Null;
private void SetList(RishList<int> value) {
RishSetList(value);
SetSize(value.count);
}
}
[RishValueType]
public struct FooState {
public RishList<int> list;
public int size;
}
// ↑↑↑ User Code ↑↑↑
// ↓↓↓ Autogenerated ↓↓↓
public partial class Foo : RishUI.MemoryManagement.IManaged<FooState>
{
private void RishSetList(RishUI.RishList<System.Int32> v)
{
if(!IsMounted) return;
var state = State;
if(RishUI.RishUtils.SmartCompare(v, state.list)) return;
state.list = v;
SetState(state, false);
var ctx2147483645 = RishUI.Rish.GetOwnerContext<RishUI.RishList<System.Int32>, RishUI.ManagedRishList<System.Int32>>(v);
ClaimContext(-2147483645, ctx2147483645);
Dirty();
}
private void SetSize(System.Int32 v)
{
if(!IsMounted) return;
var state = State;
if(RishUI.RishUtils.MemCmp(ref v, ref state.size)) return;
state.var1 = v;
SetState(state, false);
Dirty();
}
}
// ↑↑↑ Autogenerated ↑↑↑