Skip to content

Smart Values

A guide for programmers. If you only use the Inspector, you never need this page - smart value fields just work.

Smart values are the small structs you see all over Jungle components: SmartFloat, SmartVector3, SmartColor, SmartRigidbody, and friends. They give a field the cost of a plain serialized value with an opt-in upgrade path: flip the mode in the Inspector and the same field reads from any value provider instead.

The three modes

Mode What it means
Local The field holds a plain value, exactly like a normal serialized field. Zero overhead.
Dynamic The field reads from a value provider (IFloatValue, IVector3Value, ...) every time it is accessed.
Copied On Init The provider is read once during Initialize(), the result is stored locally, and the provider reference is dropped. Dynamic setup, local runtime cost.

In code, a smart value converts implicitly to its underlying type:

[SerializeField] private SmartFloat speed;

void Update()
{
    transform.position += direction * speed * Time.deltaTime; // reads speed.Value
}

If your component uses Copied On Init fields, call Initialize() on them once (typically in Awake). Command targets do this for you on their first run in play mode.

Smart fields inside your own values and modifiers

When you write your own value, modifier or pipeline destination, mark the class partial. Jungle then writes the code that starts each smart field when a run starts, so a field set to read once or at each start takes its copy then instead of reading live:

[Serializable]
public partial class Wobble : IFloatValue
{
    [SerializeField] private SmartFloat amount;   // starts with the run, no code needed
    public float Value() => amount.Value * Mathf.Sin(Time.time);
    public bool HasMultipleValues => false;
}

If your class has its own Initialize(), call InitializeFields() first in it. The compiler warns you when a class needs partial (JNG001) or when an Initialize() skips InitializeFields() (JNG002).

Where the struct bodies come from

If you open SmartFloat in your IDE you will find an empty declaration:

[Serializable]
[GenerateSmartValue]
public partial struct SmartFloat : ISmartValue<float, IFloatValue> { }

The body is emitted at compile time by a Roslyn source generator that ships with Jungle Core as Jungle Core/Runtime/RoslynAnalyzers/SmartComponentGenerator.dll. Unity hands the DLL to the C# compiler (that is what the RoslynAnalyzer asset label does), and the generator fills in the serialized fields, the Value accessor, the mode logic, the static factories (Local, Dynamic, CopiedOnInit) and the implicit conversion.

Because the generator is attached to the Jungle.Core assembly, it also runs for every assembly that references Jungle.Core - including yours.

Creating your own smart value

Declare an empty partial struct in any assembly that references Jungle.Core:

[Serializable]
[GenerateSmartValue]
public partial struct SmartMass : ISmartValue<float, IFloatValue> { }

That is the whole implementation. Requirements:

  • The struct must be partial and marked [Serializable].
  • It must implement ISmartValue<T, TSource> where TSource is the value-provider interface for T (for example IFloatValue : IValue<float>). Any IValue<T> interface works, including your own.
  • Your assembly definition must reference Jungle.Core (code in Assembly-CSharp qualifies automatically).

Creating your own smart component

The same DLL contains a second generator for component references. It adds lookup strategies and multi-target resolution on top of the smart value pattern:

[Serializable]
[GenerateSmartComponent]
public partial struct SmartLight : ISmartComponent<Light> { }

The generated body resolves the component from a direct reference, a list, or dynamically from a source GameObject plus a pluggable IComponentLookupStrategy, and exposes Value, ValueOnTarget(...), ResolveAll() and HasMultipleValues.

Browsing the generated code

The generated files exist in memory during compilation, not in your project folder. Two ways to read them:

In the IDE. Use Go to Definition on a generated member - speed.Value, SmartFloat.Local(...) - and Rider or Visual Studio opens the generated SmartFloat.g.cs directly. On the struct name, Go to Definition lands on the empty partial; use Go to Declaration and pick the generated part from the popup, or search for SmartFloat.g.cs once the solution has compiled.

On disk. Ask the compiler to also write generator output to a folder. Create a file named csc.rsp in the root of Assets containing:

-generatedfilesout:Library/SourceGenerated

After the next recompile, every generated file in the project appears under Library/SourceGenerated/<generator>/..., smart value bodies included. The folder is derived output: safe to delete, ignored by version control, refreshed on each compile.