cz.xprees.variables 1.0.20 → 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,29 +2,23 @@
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/cz.xprees.variables)](https://www.npmjs.com/package/cz.xprees.variables)
4
4
 
5
- This package contains a set of variables based on the ScriptableObjects,
6
- which make it great for storing and simply sharing state without tight coupling.
7
- Moreover, it provides a way to share variables (state) across multiple scenes making it powerful for multi-scene Unity project.
5
+ ScriptableObject-based variable architecture for decoupled state management, event observation, and multi-scene data sharing in Unity.
8
6
 
9
7
  ## Features
10
8
 
11
- - **ScriptableObject Variables** - A set of variables that can be used to store and share state across scenes.
12
- - Simply extend the [`VariableBaseSO<T>`](Runtime/Base/VariableBaseSO.cs) class to create your own variable types, see examples in
13
- the [BoolVariable](Runtime/Primitive/BoolVariable.cs) folder.
14
- - Watchable Variables - You can subscribe to `onValueChanged` event to get notified when the value changes.
15
- - **References** - A way to reference variables or inlined values in the Project (changeable in the Inspector).
16
- - You can directly set the value in the Inspector or reference a variable.
17
- - This makes flexible go to choice how to use the variables or inlined values in your scripts.
18
- - **Variable Modifiers** - Wrapper classes that allow you to modify the value of the variable in a specific way.
19
- - For example, you can use the [`BoolModifier`](Runtime/Modifiers/BoolModifier.cs)
20
- - **Variable Aggregations** - A set of classes that allow you to aggregate multiple variables into one.
21
- - For example, you can use the [`BoolAggregation`](Runtime/Aggregations/BoolAggregation.cs) to aggregate multiple boolean variables into one.
9
+ - **ScriptableObject Variables** - Store and share state without coupling systems together.
10
+ - Extend [`VariableBaseSO<T>`](Runtime/Base/VariableBaseSO.cs) to author custom typed variables.
11
+ - Observe runtime changes via the `onValueChanged` event.
12
+ - **Standalone Default State** - Every variable manages its own authored `defaultValue` and deep-clones it into `currentValue` on `OnEnable()` and
13
+ `ResetToDefault()`.
14
+ - **PlayMode Transition Resets** - Declarative `[ResetOnPlayMode]` attribute controls automatic reset when entering or exiting Play Mode.
15
+ - **State Lifetime Control** - Inherits `DescriptionBaseSO.lifetime` (`Scenario`, `Session`, `Persistent`) for boundary-aware resets.
16
+ - **References** - Inspector-switchable references (`ReferenceBase<T>`) supporting both inlined values and shared variable assets.
17
+ - **Variable Modifiers & Aggregations** - Compose and transform variable streams dynamically.
22
18
 
23
19
  ## Installation
24
20
 
25
- Install the package using npm scoped registry in `Project Settings > Package Manager > Scoped Registries`
26
-
27
- [Unity Docs - Install a UPM package from a Git URL](https://docs.unity3d.com/6000.1/Documentation/Manual/upm-ui-giturl.html)
21
+ Install the package using npm scoped registry in `Project Settings > Package Manager > Scoped Registries`:
28
22
 
29
23
  ```json
30
24
  {
@@ -35,7 +29,58 @@ Install the package using npm scoped registry in `Project Settings > Package Man
35
29
  "com.dbrizov.naughtyattributes"
36
30
  ]
37
31
  }
32
+ ```
33
+
34
+ Then install `cz.xprees.variables` via the Unity Package Manager.
35
+
36
+ ---
37
+
38
+ ## State Lifecycle & PlayMode Reset (DX)
39
+
40
+ Variables are completely self-contained and integrate with the centralized `StateSnapshotService` without requiring bespoke registration systems.
41
+
42
+ ### 1. Default Behavior
43
+
44
+ By default, `VariableBaseSO<T>`:
45
+
46
+ 1. Clones `defaultValue` into `currentValue` on `OnEnable()`.
47
+ 2. Automatically resets to default on entering Play Mode via `[ResetOnPlayMode(PlayModeResetTiming.EnterPlayMode)]`.
48
+ 3. In Unity Editor, captures its baseline snapshot before the first runtime mutation via `StateSnapshotService.EnsureCaptured(this)`.
49
+ 4. Reverts cleanly to baseline upon exiting Play Mode without dirtying asset files on disk.
38
50
 
51
+ ### 2. Customizing PlayMode Resets via `[ResetOnPlayMode]`
52
+
53
+ Use `[ResetOnPlayMode]` to customize when custom variable classes reset:
54
+
55
+ ```csharp
56
+ using Xprees.Core;
57
+ using Xprees.Variables.Base;
58
+
59
+ // 1. Default: resets on Play Mode entry
60
+ public class AmmoVariable : IntVariable { }
61
+
62
+ // 2. Prevent Play Mode reset (e.g. for persistent editor values or external saves)
63
+ [ResetOnPlayMode(PlayModeResetTiming.None)]
64
+ public class PlayerProfileVariable : StringVariable { }
65
+
66
+ // 3. Reset on both entry and exit
67
+ [ResetOnPlayMode(PlayModeResetTiming.Both)]
68
+ public class TransientDebugFlagVariable : BoolVariable { }
39
69
  ```
40
70
 
41
- Then simply install the package using the Unity Package Manager using the _NPM - xprees_ scope or by the package name `cz.xprees.variables`.
71
+ ### 3. Setting State Lifetime per Variable
72
+
73
+ Every variable ScriptableObject exposes the **Lifetime** dropdown in the Inspector (inherited from `DescriptionBaseSO`):
74
+
75
+ - **`Scenario` (Default)**:
76
+ Resets automatically whenever a scenario starts or restarts via `ScenarioStateRegistrySO` and `StateSnapshotService.RestoreAll()`.
77
+ - **`Session`**:
78
+ Persists across scenarios, resetting only when returning to the Main Menu or quitting.
79
+ - **`Persistent`**:
80
+ **Never reset** by scenario resets or Play Mode entry (e.g. Audio volume, resolution settings).
81
+
82
+ ```csharp
83
+ // You can also enforce class-level lifetime via attribute:
84
+ [StatefulLifetime(StateLifetime.Persistent)]
85
+ public class MasterVolumeVariable : FloatVariable { }
86
+ ```
@@ -3,25 +3,45 @@
3
3
  // ------------------------------------------------------------------------------------------------------
4
4
 
5
5
  using System;
6
+ using UnityEngine;
6
7
  using UnityEngine.Events;
7
8
  using Xprees.Core;
8
9
  using Xprees.Variables.Utils;
9
10
 
10
11
  namespace Xprees.Variables.Base
11
12
  {
12
- /// <summary>
13
13
  /// Base class for all variable references used in the game. It can either use an inlined value or reference a VariableBaseSO Scriptable Object. The Value property abstracts this choice away, so users of ReferenceBase don't have to care about it.
14
- /// </summary>
15
14
  /// <typeparam name="T">Unity Serializable</typeparam>
16
15
  [Serializable]
17
- public class ReferenceBase<T> : IResettable
16
+ public class ReferenceBase<T> : ISerializationCallbackReceiver
18
17
  {
18
+ private bool _hasCapturedBaseline; // Used to check if baseline has been captured
19
19
  private T _defaultInlinedValue; // Used to reset state of inlined value
20
20
 
21
21
  public bool useInlined = true;
22
22
  public T inlinedValue;
23
23
  public VariableBaseSO<T> variable;
24
24
 
25
+ public void OnBeforeSerialize()
26
+ {
27
+ }
28
+
29
+ public void OnAfterDeserialize()
30
+ {
31
+ if (!PlayModeStateTracker.IsPlaying)
32
+ {
33
+ _defaultInlinedValue = CloningTools.Clone(inlinedValue);
34
+ _hasCapturedBaseline = false;
35
+ return;
36
+ }
37
+
38
+ if (!_hasCapturedBaseline)
39
+ {
40
+ _defaultInlinedValue = CloningTools.Clone(inlinedValue);
41
+ _hasCapturedBaseline = true;
42
+ }
43
+ }
44
+
25
45
  // Internal event for inlined value changes, since VariableBaseSO already has its own onValueChanged event.
26
46
  private UnityAction<T> _onInlinedValueChanged;
27
47
 
@@ -31,7 +51,7 @@ namespace Xprees.Variables.Base
31
51
  {
32
52
  add
33
53
  {
34
- var shouldRedirectToVarEvent = !useInlined && variable != null;
54
+ var shouldRedirectToVarEvent = !useInlined && variable;
35
55
  if (shouldRedirectToVarEvent)
36
56
  {
37
57
  variable.onValueChanged += value;
@@ -43,7 +63,7 @@ namespace Xprees.Variables.Base
43
63
 
44
64
  remove
45
65
  {
46
- var shouldRedirectToVarEvent = !useInlined && variable != null;
66
+ var shouldRedirectToVarEvent = !useInlined && variable;
47
67
  if (shouldRedirectToVarEvent)
48
68
  {
49
69
  variable.onValueChanged -= value;
@@ -63,6 +83,7 @@ namespace Xprees.Variables.Base
63
83
  useInlined = true;
64
84
  inlinedValue = value;
65
85
  _defaultInlinedValue = value;
86
+ _hasCapturedBaseline = true;
66
87
  }
67
88
 
68
89
  public T Value
@@ -84,22 +105,14 @@ namespace Xprees.Variables.Base
84
105
 
85
106
  public static implicit operator T(ReferenceBase<T> reference) => reference != null ? reference.Value : default;
86
107
 
87
- public virtual void BackupStartState()
88
- {
89
- if (!useInlined) return; // Variables does that by themselves
90
-
91
- _defaultInlinedValue = CloningTools.Clone(inlinedValue);
92
- }
93
-
94
- public virtual void ResetState()
108
+ /// Resets the inlined value to its baseline value captured at deserialization/initialization.
109
+ /// If this reference points to a VariableBaseSO, it does nothing (the shared variable state is managed by StateSnapshotService).
110
+ public virtual void ResetToDefault()
95
111
  {
96
112
  if (useInlined)
97
113
  {
98
114
  inlinedValue = CloningTools.Clone(_defaultInlinedValue);
99
- return;
100
115
  }
101
-
102
- variable?.ResetState();
103
116
  }
104
117
  }
105
118
  }
@@ -8,12 +8,6 @@ namespace Xprees.Variables.Base
8
8
  [Tooltip("Aggregated variables.")]
9
9
  [SerializeField] protected VariableBaseSO<T>[] variables;
10
10
 
11
- private void OnEnable()
12
- {
13
- hideFlags = HideFlags.DontUnloadUnusedAsset;
14
- ResetState();
15
- }
16
-
17
11
  public override T CurrentValue
18
12
  {
19
13
  get => AggregateValue(variables);
@@ -22,15 +16,14 @@ namespace Xprees.Variables.Base
22
16
 
23
17
  protected abstract T AggregateValue(VariableBaseSO<T>[] variableBases);
24
18
 
25
- public override void ResetState()
19
+ public override void ResetToDefault()
26
20
  {
27
- base.ResetState();
28
- if (variables == null) return;
21
+ // Aggregations do not hold independent state; values are computed dynamically.
22
+ }
29
23
 
30
- foreach (var variable in variables)
31
- {
32
- variable?.ResetState();
33
- }
24
+ public override void ClearTransientState()
25
+ {
26
+ // Aggregations do not hold independent state; values are computed dynamically.
34
27
  }
35
28
  }
36
29
  }
@@ -7,42 +7,46 @@ namespace Xprees.Variables.Base
7
7
  {
8
8
  public abstract class VariableBaseSO : DescriptionBaseSO
9
9
  {
10
- private void OnEnable()
10
+ protected virtual void OnEnable()
11
11
  {
12
12
  hideFlags = HideFlags.DontUnloadUnusedAsset;
13
- ForceResetState();
13
+ ResetToDefault();
14
14
  }
15
15
 
16
- /// Force reset state regardless of any protection settings.
17
- public abstract void ForceResetState();
18
-
19
- public override void ResetState() => ForceResetState();
16
+ /// Resets the variable to its authored default value.
17
+ public abstract void ResetToDefault();
20
18
  }
21
19
 
22
- /// <summary>
23
- /// Base class for all variables ScriptableObjects.
24
- /// Object holds the state on runtime, but reset every time OnEnable to defaultValue.
25
- /// </summary>
20
+ /// Base class for all variable ScriptableObjects.
21
+ /// Manages its own default initialization self-contained, and integrates with StateSnapshotService for scenario boundaries.
22
+ /// By default, resets on EnterPlayMode unless decorated with a custom [ResetOnPlayMode] or marked Persistent.
26
23
  /// <typeparam name="T">Unity Serializable or System.Serializable</typeparam>
27
- public class VariableBaseSO<T> : VariableBaseSO
24
+ [ResetOnPlayMode(PlayModeResetTiming.EnterPlayMode)]
25
+ public class VariableBaseSO<T> : VariableBaseSO, IRuntimeStateOwner
28
26
  {
29
- [Tooltip("Value to which the variable will be reset on OnEnable or ResetState call.")]
27
+ [Tooltip("Authored default value of the variable.")]
30
28
  [SerializeField] protected T defaultValue;
31
29
 
32
- [Tooltip("Current value of variable - Runtime only value.")]
30
+ [Tooltip("Current value of variable - Runtime only value. Replaced with default value on OnEnable/PlayMode start.")]
33
31
  [SerializeField] private T currentValue;
34
32
 
35
- [Header("Settings")]
36
- [Tooltip("If true, the variable will not be reset when calling ResetState()."
37
- + " However, it will still be reset on OnEnable and when ForceResetState() is called. "
38
- + "Useful for game-system variables where runtime value should persist unless explicitly reset.")]
39
- [SerializeField] protected bool protectedDontReset = false;
33
+ #if UNITY_EDITOR
34
+ protected virtual void OnValidate()
35
+ {
36
+ // If not playing, reset current value to default value on validate to ensure that the variable is always in a valid state in Editor.
37
+ if (!Application.isPlaying) currentValue = CloningTools.Clone(defaultValue);
38
+ }
39
+ #endif
40
40
 
41
41
  public virtual T CurrentValue
42
42
  {
43
43
  get => currentValue;
44
44
  set
45
45
  {
46
+ #if UNITY_EDITOR
47
+ // If not already captured in Editor capture before changing the value
48
+ if (PlayModeStateTracker.IsPlaying) StateSnapshotService.EnsureCaptured(this);
49
+ #endif
46
50
  currentValue = value;
47
51
  onValueChanged?.Invoke(value);
48
52
  }
@@ -58,13 +62,18 @@ namespace Xprees.Variables.Base
58
62
  public static implicit operator T(VariableBaseSO<T> variable) =>
59
63
  variable != null ? variable.CurrentValue : default;
60
64
 
61
- public override void ResetState()
65
+ /// Resets the variable's current value to a fresh clone of its authored default value and notifies listeners.
66
+ public override void ResetToDefault()
62
67
  {
63
- if (protectedDontReset) return; // skip reset if protected
64
-
65
- ForceResetState();
68
+ currentValue = CloningTools.Clone(defaultValue);
69
+ onValueChanged?.Invoke(currentValue);
66
70
  }
67
71
 
68
- public override void ForceResetState() => CurrentValue = CloningTools.Clone(defaultValue);
72
+ /// Invoked by StateSnapshotService when restoring state at scenario boundaries.
73
+ /// Notifies runtime listeners that the value has reverted to its snapshot baseline.
74
+ public virtual void ClearTransientState()
75
+ {
76
+ onValueChanged?.Invoke(currentValue);
77
+ }
69
78
  }
70
79
  }
@@ -12,13 +12,6 @@ namespace Xprees.Variables.Base
12
12
  [Space]
13
13
  [SerializeField] private BoolReference disableWrite = new(true);
14
14
 
15
- private void OnEnable()
16
- {
17
- hideFlags = HideFlags.DontUnloadUnusedAsset;
18
- disableWrite?.BackupStartState();
19
- ResetState();
20
- }
21
-
22
15
  public override T CurrentValue
23
16
  {
24
17
  get => ModifyValue(variable.CurrentValue);
@@ -36,11 +29,14 @@ namespace Xprees.Variables.Base
36
29
 
37
30
  protected abstract T ModifyValue(T value);
38
31
 
39
- public override void ResetState()
32
+ public override void ResetToDefault()
33
+ {
34
+ // Modifiers do not hold independent state; state is owned by the wrapped variable.
35
+ }
36
+
37
+ public override void ClearTransientState()
40
38
  {
41
- base.ResetState();
42
- variable?.ResetState();
43
- disableWrite?.ResetState();
39
+ // Modifiers do not hold independent state; state is owned by the wrapped variable.
44
40
  }
45
41
  }
46
42
  }
@@ -1,4 +1,4 @@
1
- using System;
1
+ using System;
2
2
  using System.Collections;
3
3
  using System.Runtime.CompilerServices;
4
4
  using UnityEngine;
@@ -24,6 +24,13 @@ namespace Xprees.Variables.Utils
24
24
  var canReturnAsIs = typeof(T).IsValueType && !RuntimeHelpers.IsReferenceOrContainsReferences<T>();
25
25
  if (canReturnAsIs) return value;
26
26
 
27
+ // Strings are immutable in .NET, so return directly without JsonUtility
28
+ if (typeof(T) == typeof(string)) return value;
29
+
30
+ // UnityEngine.Object references (ScriptableObjects, MonoBehaviours, Assets, GameObjects)
31
+ // are reference-assigned; we must not deep-clone or call CreateInstance during serialization.
32
+ if (typeof(Object).IsAssignableFrom(typeof(T)) || value is Object) return value;
33
+
27
34
  if (value is ICloneable cloneable) return (T) cloneable.Clone();
28
35
 
29
36
  // Handle collections (List<T>, arrays, etc.) - JsonUtility doesn't support them directly
@@ -96,6 +103,9 @@ namespace Xprees.Variables.Utils
96
103
  // Value types are copied by value
97
104
  if (itemType.IsValueType) return item;
98
105
 
106
+ // Strings are immutable
107
+ if (item is string) return item;
108
+
99
109
  // ICloneable
100
110
  if (item is ICloneable cloneable) return cloneable.Clone();
101
111
 
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "cz.xprees.variables",
3
3
  "displayName": "Variables",
4
- "version": "1.0.20",
4
+ "version": "1.0.23",
5
5
  "unity": "2021.3",
6
6
  "description": "This package contains a system for creating variables based on ScriptableObjects in Editor.",
7
7
  "license": "Apache-2.0",
8
8
  "category": "Core",
9
9
  "dependencies": {
10
- "cz.xprees.core": "1.0.32",
10
+ "cz.xprees.core": "1.0.40",
11
11
  "com.dbrizov.naughtyattributes": "2.1.4"
12
12
  },
13
13
  "keywords": [