Data¶
Store and retrieve typed data at runtime - numbers, strings, objects - using a simple key-value storage that works in the Inspector.
Jungle.Data gives you two complementary storage systems. The data storage stores arbitrary typed values (int, float, bool, string, Color, Vector3, and more) that any part of your project can read and write. The inventory tracks quantities of identified items with automatic stacking and composable filter rules. Both are fully configurable in the Inspector and integrate with the rest of the Jungle framework.
Assembly: Jungle.Data
Dependencies: Jungle.Core, Jungle.Spatial, Jungle.Graphics
Namespace: Jungle.Data
DataStorage¶
A DataStorage is a flat keyed collection of polymorphic entries. Each entry carries one Key and one typed value. You add entries directly through the Inspector via the type picker; each Key is unique and maps to one typed value. Runtime lookup is O(1) per key.
Key¶
A Key is a ScriptableObject that acts as a named identifier for data storage entries. Create one via Create > Jungle > Data > Key.
Using ScriptableObject keys instead of raw strings avoids typos, enables Inspector drag-and-drop, and lets you rename keys without breaking references.
Entry Types¶
Each entry type wraps a single value type and binds it to a Key. Entries use the value provider system internally, so each entry's value can be a constant, a reference to another component, or any IValue<T> implementation.
Built-in entry types:
| Class | Value Type |
|---|---|
IntEntry |
int |
FloatEntry |
float |
BoolEntry |
bool |
StringEntry |
string |
ColorEntry |
Color |
Vector3Entry |
Vector3 |
BoundsEntry |
Bounds |
GameObjectEntry |
GameObject |
Hosting a DataStorage¶
DataStorage instances are plain [Serializable] classes. To use one in a scene or project, host it with one of these containers:
| Container | Type | Use Case |
|---|---|---|
DataStorageComponent |
MonoBehaviour | Per-GameObject data (player stats, object state) |
DataStorageAsset |
ScriptableObject | Shared project-wide data (global config, level settings) |
Create a DataStorageAsset via Create > Jungle > Data > Data Storage Asset.
Reading and Writing Values¶
From C#, use the typed API on DataStorage:
// Reading
if (dataStorage.TryGetValue<int>(healthKey, out int health))
Debug.Log($"Health: {health}");
// Or with a default fallback
float speed = dataStorage.GetValue<float>(speedKey);
// Writing
dataStorage.SetValue(healthKey, 100);
DataStorage Commands¶
DataStorageCommand is a composable command that batches multiple operations on a single data storage. Add it to any command-driven workflow (event responses, process triggers) and configure a list of IDataStorageOperation entries in the Inspector.
Built-in Operations¶
| Operation | Description |
|---|---|
SetIntAction |
Writes an int value by key |
SetFloatAction |
Writes a float value by key |
SetBoolAction |
Writes a bool value by key |
SetStringAction |
Writes a string value by key |
IncrementIntAction |
Adds to an int entry; supports modulo wrapping |
Each set operation takes a Key and an IValue<T> source, so the written value can come from a constant, another data storage entry, or any value provider.
IncrementIntAction¶
Adds an amount to an existing int entry. Useful for counters, cycling through indices, or decrementing resources. Both Amount and Modulo are smart values, so they can be constants or driven dynamically from another data storage entry or value provider.
Key: scoreKey
Amount: 1 // negative to decrement
Modulo: 0 // > 0 enables wrapping: (current + amount) % modulo
With modulo: 4, values cycle through 0, 1, 2, 3, 0, 1, ... - useful for rotating through selections or animation states.
Custom Operations¶
Implement IDataStorageAction to create your own:
[Serializable]
[JungleClassInfo("Multiply Float", "Multiplies a float entry by a factor.", null, "Data")]
public class MultiplyFloatAction : IDataStorageAction
{
[SerializeField] private Key key;
[SerializeField] private float factor = 2f;
public void Execute(DataStorage storage)
{
if (!key) return;
var current = storage.GetValue<float>(key);
storage.SetValue(key, current * factor);
}
}
Values From DataStorage¶
Data storage entries can feed into any system that consumes value providers. The ValueFromDataStorage<T> base class reads a typed value from a data storage by key, exposing it as an IValue<T>.
Built-in implementations:
| Class | Provides |
|---|---|
IntFromDataStorage |
IIntValue |
FloatFromDataStorage |
IFloatValue |
BoolFromDataStorage |
IBoolValue |
StringFromDataStorage |
IStringValue |
ColorFromDataStorage |
IColorValue |
Vector3FromDataStorage |
IVector3Value |
BoundsFromDataStorage |
IBoundsValue |
GameObjectFromDataStorage |
IGameObjectValue |
Each one references an IDataStorageValue (where the data storage lives) and a Key (which entry to read). These are also ISettableValue<T>, so pipelines can write back into the data storage.
DataStorage Value Sources¶
The IDataStorageValue interface provides the data storage reference itself. Two built-in sources:
| Source | Description |
|---|---|
DataStorageValueFromComponent |
References a DataStorageComponent on a GameObject |
DataStorageValueFromAsset |
References a DataStorageAsset in the project |
Inventory¶
The inventory system tracks quantities of identified items with automatic stacking and composable filter rules.
Core Types¶
IInventory - the interface that all inventory implementations satisfy. Exposes entries, filters, store/retrieve methods, and change events.
public interface IInventory
{
IReadOnlyList<InventoryEntry> Entries { get; }
IReadOnlyList<InventoryFilter> StoreFilters { get; }
IReadOnlyList<InventoryFilter> RetrieveFilters { get; }
int EntryCount { get; }
bool TryStore(Item item, int quantity = 1);
bool TryRetrieve(Item item, int quantity = 1);
int GetQuantity(Item item);
bool Contains(Item item);
event Action<InventoryEntry> OnEntryAdded;
event Action<InventoryEntry> OnEntryRemoved;
event Action<InventoryEntry> OnEntryChanged;
}
Inventory - a MonoBehaviour that implements IInventory. Add it directly to a GameObject to give that object an inventory. Items with the same Item reference stack automatically.
InventoryEntry - a single (Item Item, int Quantity) pair.
Item - a ScriptableObject that gives items a name, description, and an embedded DataStorage for extensible metadata. Create via Create > Jungle > Storage > Item.
// Item carries metadata through its DataStorage
[CreateAssetMenu(menuName = "Jungle/Storage/Item")]
public class Item : ScriptableObject, IDataStorageProvider
{
public string Description;
public DataStorage DataStorage { get; } // query with any Key
}
Any system can query an Item's data storage using its own keys - the inventory reads maxQuantity, an object pool reads prefab, the UI reads icon - without coupling to each other.
Hosting an Inventory¶
| Container | Type | Use Case |
|---|---|---|
Inventory |
MonoBehaviour | Per-GameObject inventory (player backpack, chest) |
InventoryAsset |
ScriptableObject | Shared inventory (party resources, shop stock) |
Inventory is a MonoBehaviour - add it to any GameObject and it is ready to use. For InventoryAsset, call Initialize() manually before first use.
Store and Retrieve¶
// Store 3 of an item
if (inventory.TryStore(healthPotion, 3))
Debug.Log("Stored!");
// Retrieve 1
if (inventory.TryRetrieve(healthPotion, 1))
Debug.Log("Retrieved!");
// Query
int count = inventory.GetQuantity(healthPotion);
bool has = inventory.Contains(healthPotion);
Both TryStore and TryRetrieve run through their respective filter lists before modifying data. If any filter rejects the operation, it returns false and the inventory is unchanged.
Events¶
Subscribe to inventory changes at runtime:
inventory.OnEntryAdded += entry => Debug.Log($"New item: {entry.Item.name}");
inventory.OnEntryRemoved += entry => Debug.Log($"Removed: {entry.Item.name}");
inventory.OnEntryChanged += entry => Debug.Log($"Changed: {entry.Item.name} x{entry.Quantity}");
Inventory Filters¶
Filters gate store and retrieve operations with AND logic. Add multiple filters to an inventory to compose rules.
| Filter | Description |
|---|---|
SlotCapacityFilter |
Limits the total number of distinct entry slots. Set maxSlots to -1 for unlimited. |
PerItemCapacityFilter |
Limits quantity per item type by reading a maxQuantity int from the item's DataStorage (via IDataStorageProvider). |
Custom Filters¶
Extend InventoryFilter:
[Serializable]
[JungleClassInfo("Weight Limit", "Rejects if total weight exceeds a cap.", null, "Inventory")]
public class WeightLimitFilter : InventoryFilter
{
[SerializeField] private Key weightKey;
[SerializeField] private float maxWeight = 100f;
public override bool CanStore(IInventory inventory, Item item, int quantity)
{
// Calculate total weight from all entries...
return totalWeight <= maxWeight;
}
}
InventoryCommand¶
InventoryCommand is a composable command that batches multiple operations on a single inventory. It holds a list of IInventoryOperation entries, following the operation-based command pattern used throughout Jungle. Add it to any command-driven workflow and configure operations in the Inspector.
Identity System¶
IIdentifiable - interface for components that carry a ScriptableObject identifier. Used to bridge scene objects with inventory entries.
IdentifiableComponent - simple MonoBehaviour that assigns an identifier to a GameObject. Add to prefabs that need to be tracked by inventory or other identity-aware systems.
Inventory Value Providers¶
Like data storage, inventories integrate with the value provider system. IInventoryValue extends IValue<IInventory>, so any value provider that resolves to an IInventory can feed into inventory-aware systems.
| Source | Description |
|---|---|
InventoryValue |
Inline inventory stored directly on the owner |
InventoryValueFromComponent |
References an Inventory component on a GameObject |
InventoryValueFromAsset |
References an InventoryAsset in the project |
Component Lookup¶
FromDataStorageStrategy implements IComponentLookupStrategy to find components by data storage key. It looks for a DataStorageComponent on the target GameObject, then retrieves a Component reference stored under the given key.
This is useful when a system needs to resolve a component reference indirectly - for example, finding a specific collider or renderer stored as metadata on an object.
Data Flow¶
flowchart TD
subgraph Storage
K[Key]
E[Entry]
DS[DataStorage]
K --> E --> DS
end
subgraph Containers
DSC[DataStorageComponent]
DSA[DataStorageAsset]
DS --> DSC
DS --> DSA
end
subgraph Commands
CMD[DataStorageCommand]
OP[IDataStorageOperation]
OP --> CMD
CMD -->|writes| DS
end
subgraph "Value Integration"
VFD["ValueFromDataStorage<T>"]
VFD -->|reads| DS
VFD -->|"IValue<T>"| Consumer[Any Consumer]
end
flowchart TD
subgraph Inventory
IT[Item]
IE[InventoryEntry]
IINV[IInventory]
INV[Inventory]
IA[InventoryAsset]
IF[InventoryFilter]
ICMD[InventoryCommand]
IOP[IInventoryOperation]
IT --> IE --> IINV
IF -->|gates| IINV
INV -->|implements| IINV
IA -->|implements| IINV
IOP --> ICMD
ICMD -->|operates on| IINV
end
IT -->|IDataStorageProvider| DS[DataStorage]
Reactive System¶
The reactive system observes data storage values, events, signals, and frame cycles at runtime and dispatches responses automatically. It is built on the Reactor component plus its sibling Setter, Measurer, and Visualizer.
Reactor¶
A Reactor hosts a list of IReactorTarget items. Each target watches one source — a typed observable value, an event, a signal received via Reactor.Receive(), or a frame cycle — and runs its own list of (trigger, body) entries when the source's triggers fire.
Reactor
└─ Targets (IReactorTarget list)
├─ Float target ─── source: IObservableFloatValue
│ └─ Entries (trigger + body)
│ ├─ Threshold trigger (Above 100) → [IProcess body]
│ └─ Range trigger (0..100) → [IProcess body]
└─ Signal target ── source: SignalType (pushed via Receive)
└─ Entries → [IProcess body]
A target watches its source and decides when to fire. A trigger inside the target decides which condition fires. The body is a list of processes that runs when a trigger fires. Targets reuse the same operation/command surface used everywhere else in Jungle — anything that implements the matching IXReactionTarget exposes a SmartX slot that's auto-wired to the observed value.
Typed Targets¶
Each scalar type has its own target shape that exposes the live value to its body:
- Float target (
IObservableFloatValuesource) with triggersOnChangeFloatTrigger,ThresholdFloatTrigger(band:Above/Below/AbsAbove/AbsBelow, with hysteresis),RangeFloatTrigger(band:[min, max], with hysteresis). - Bool / Int / Double / String / Vector3 / GameObject targets with type-matched trigger shapes.
- Event target — source is an
IEvent; trigger fires when the event raises. - Signal target — source is a
SignalType; trigger fires whenReactor.Receive(signal)is called. - Frame cycle target — source is a per-frame cycle; trigger fires on cycle begin / end.
Body ops marked IXReactionTarget expose a SmartX ReactionInput slot. On first dispatch the slot is auto-wired to the observed value so the live reaction value flows in with no manual setup.
Retrigger / interrupt behaviour is per-entry. Edge re-fires while the body is still running follow ProcessRetriggerStrategy; band exits and reactor shutdown follow ReactionInterruptPolicy (ForceComplete snaps in-flight processes to their final state, Cancel leaves them alone).
Observable Values¶
Observable values are computed from data storage or other sources and notify when they change. Built-in implementations include:
| Value | Type | Source |
|---|---|---|
ObservableBoolValueFromKey |
bool |
Data storage bool entry |
ObservableIntValueFromKey |
int |
Data storage int entry |
ObservableFloatValueFromKey |
float |
Data storage float entry |
ObservableGameObjectValueFromKey |
GameObject |
Data storage GameObject entry |
FloatSumFromKeys |
float |
Sum of multiple float entries |
FloatMinFromKeys |
float |
Minimum of multiple float entries |
FloatMaxFromKeys |
float |
Maximum of multiple float entries |
IntSumFromKeys |
int |
Sum of multiple int entries |
TrueFlagsCountInt |
int |
Count of true values among listed bool keys |
IntAtLeastBool |
bool |
True when an int entry ≥ threshold |
FloatInRangeBool |
bool |
True when a float entry is within range |
AllFlagsTrueBool |
bool |
True when all listed bool keys are true |
AnyFlagTrueBool |
bool |
True when any listed bool key is true |
Signal Dispatch¶
Signals are pushed into a Reactor via Reactor.Receive(SignalType). The Reactor fans the signal out to every signal-receiving target in its list. For per-GameObject pub/sub (no Reactor required), use LocalSignalTransmitter — anything that holds a reference to the transmitter can register listeners or raise signals on it.
flowchart LR
A[Caller] -->|"Receive(signal)"| R[Reactor]
R --> T1[Signal target A]
R --> T2[Signal target B]
T1 -->|matches| P1[Body processes]
T2 -->|ignores| X[wrong signal]
Setter¶
A Setter groups value-setting entries under one lifecycle. Each entry assigns values into its own target — a settable value, a data storage, a reference field — and manages its own scheduling. The component starts and stops every entry together. Sibling to Reactor (consumes triggers and dispatches reactions) and Visualizer (drives scene visuals from data).
Add a Setter component via Add Component > Jungle > Data > Setter. For the common "feed one DataStorage with several measurements" case, the dedicated Measurer component flattens that into a single component.
Entry Types¶
Each setter entry implements ISetterEntry and receives lifecycle calls from the host Setter.
DataStorageSetter¶
Targets a DataStorage and runs a list of sub-setters against it. Each sub-setter produces one or more keyed values. Sub-setters sharing the same entry write into the same storage.
| Field | Type | Description |
|---|---|---|
| target | IDataStorageValue |
The data storage to write into (component or asset reference) |
| subSetters | List<IDataStorageSubSetter> |
Sub-setters that produce keyed values |
Sub-setters implement IDataStorageSubSetter and self-schedule — they can subscribe to events, sample on a frequency, or write once on initialize. The parent DataStorageSetter resolves the storage once on begin and passes it to every sub-setter.
Duplicate key detection runs automatically in the editor: if two sub-setters write to the same Key, a warning appears in the console.
ScheduledOperationEntry¶
For "run these write-value ops on a schedule" the current pattern is a ScheduledOperationEntry on a Setter:
- The entry hosts a list of
IOperation(commonlyWriteFloat,WriteInt,WriteBool,WriteVector3,WriteGameObject,WriteShape, …) and anIExecutionScheduler(every frame, every fixed frame, every N frames, every interval). - Each scheduled tick runs the operation list once. Use this to copy values between sources and sinks on whatever cadence you need.
The older standalone FloatValueSetter, IntValueSetter, etc. types are obsolete — they remain for compatibility but should not be used in new content.