-
Notifications
You must be signed in to change notification settings - Fork 499
/
Copy pathVariablesState.cs
269 lines (224 loc) · 9.81 KB
/
VariablesState.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
using System.Collections.Generic;
namespace Ink.Runtime
{
/// <summary>
/// Encompasses all the global variables in an ink Story, and
/// allows binding of a VariableChanged event so that that game
/// code can be notified whenever the global variables change.
/// </summary>
public class VariablesState : IEnumerable<string>
{
internal delegate void VariableChanged(string variableName, Runtime.Object newValue);
internal event VariableChanged variableChangedEvent;
internal bool batchObservingVariableChanges
{
get {
return _batchObservingVariableChanges;
}
set {
_batchObservingVariableChanges = value;
if (value) {
_changedVariables = new HashSet<string> ();
}
// Finished observing variables in a batch - now send
// notifications for changed variables all in one go.
else {
if (_changedVariables != null) {
foreach (var variableName in _changedVariables) {
var currentValue = _globalVariables [variableName];
variableChangedEvent (variableName, currentValue);
}
}
_changedVariables = null;
}
}
}
bool _batchObservingVariableChanges;
/// <summary>
/// Get or set the value of a named global ink variable.
/// The types available are the standard ink types. Certain
/// types will be implicitly casted when setting.
/// For example, doubles to floats, longs to ints, and bools
/// to ints.
/// </summary>
public object this[string variableName]
{
get {
Runtime.Object varContents;
if ( _globalVariables.TryGetValue (variableName, out varContents) )
return (varContents as Runtime.Value).valueObject;
else
return null;
}
set {
var val = Runtime.Value.Create(value);
if (val == null) {
if (value == null) {
throw new StoryException ("Cannot pass null to VariableState");
} else {
throw new StoryException ("Invalid value passed to VariableState: "+value.ToString());
}
}
SetGlobal (variableName, val);
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Enumerator to allow iteration over all global variables by name.
/// </summary>
public IEnumerator<string> GetEnumerator()
{
return _globalVariables.Keys.GetEnumerator();
}
internal VariablesState (CallStack callStack)
{
_globalVariables = new Dictionary<string, Object> ();
_callStack = callStack;
}
internal void CopyFrom(VariablesState varState)
{
_globalVariables = new Dictionary<string, Object> (varState._globalVariables);
variableChangedEvent = varState.variableChangedEvent;
if (varState.batchObservingVariableChanges != batchObservingVariableChanges) {
if (varState.batchObservingVariableChanges) {
_batchObservingVariableChanges = true;
_changedVariables = new HashSet<string> (varState._changedVariables);
} else {
_batchObservingVariableChanges = false;
_changedVariables = null;
}
}
}
internal Dictionary<string, object> jsonToken
{
get {
return Json.DictionaryRuntimeObjsToJObject(_globalVariables);
}
set {
_globalVariables = Json.JObjectToDictionaryRuntimeObjs (value);
}
}
internal Runtime.Object GetVariableWithName(string name)
{
return GetVariableWithName (name, -1);
}
Runtime.Object GetVariableWithName(string name, int contextIndex)
{
Runtime.Object varValue = GetRawVariableWithName (name, contextIndex);
// Get value from pointer?
var varPointer = varValue as VariablePointerValue;
if (varPointer) {
varValue = ValueAtVariablePointer (varPointer);
}
return varValue;
}
Runtime.Object GetRawVariableWithName(string name, int contextIndex)
{
Runtime.Object varValue = null;
// 0 context = global
if (contextIndex == 0 || contextIndex == -1) {
if ( _globalVariables.TryGetValue (name, out varValue) )
return varValue;
}
// Temporary
varValue = _callStack.GetTemporaryVariableWithName (name, contextIndex);
if (varValue == null)
throw new System.Exception ("RUNTIME ERROR: Variable '"+name+"' could not be found in context '"+contextIndex+"'. This shouldn't be possible so is a bug in the ink engine. Please try to construct a minimal story that reproduces the problem and report to inkle, thank you!");
return varValue;
}
internal Runtime.Object ValueAtVariablePointer(VariablePointerValue pointer)
{
return GetVariableWithName (pointer.variableName, pointer.contextIndex);
}
internal void Assign(VariableAssignment varAss, Runtime.Object value)
{
var name = varAss.variableName;
int contextIndex = -1;
// Are we assigning to a global variable?
bool setGlobal = false;
if (varAss.isNewDeclaration) {
setGlobal = varAss.isGlobal;
} else {
setGlobal = _globalVariables.ContainsKey (name);
}
// Constructing new variable pointer reference
if (varAss.isNewDeclaration) {
var varPointer = value as VariablePointerValue;
if (varPointer) {
var fullyResolvedVariablePointer = ResolveVariablePointer (varPointer);
value = fullyResolvedVariablePointer;
}
}
// Assign to existing variable pointer?
// Then assign to the variable that the pointer is pointing to by name.
else {
// De-reference variable reference to point to
VariablePointerValue existingPointer = null;
do {
existingPointer = GetRawVariableWithName (name, contextIndex) as VariablePointerValue;
if (existingPointer) {
name = existingPointer.variableName;
contextIndex = existingPointer.contextIndex;
setGlobal = (contextIndex == 0);
}
} while(existingPointer);
}
if (setGlobal) {
SetGlobal (name, value);
} else {
_callStack.SetTemporaryVariable (name, value, varAss.isNewDeclaration, contextIndex);
}
}
void SetGlobal(string variableName, Runtime.Object value)
{
Runtime.Object oldValue = null;
_globalVariables.TryGetValue (variableName, out oldValue);
_globalVariables [variableName] = value;
if (variableChangedEvent != null && !value.Equals (oldValue)) {
if (batchObservingVariableChanges) {
_changedVariables.Add (variableName);
} else {
variableChangedEvent (variableName, value);
}
}
}
// Given a variable pointer with just the name of the target known, resolve to a variable
// pointer that more specifically points to the exact instance: whether it's global,
// or the exact position of a temporary on the callstack.
VariablePointerValue ResolveVariablePointer(VariablePointerValue varPointer)
{
int contextIndex = varPointer.contextIndex;
if( contextIndex == -1 )
contextIndex = GetContextIndexOfVariableNamed (varPointer.variableName);
var valueOfVariablePointedTo = GetRawVariableWithName (varPointer.variableName, contextIndex);
// Extra layer of indirection:
// When accessing a pointer to a pointer (e.g. when calling nested or
// recursive functions that take a variable references, ensure we don't create
// a chain of indirection by just returning the final target.
var doubleRedirectionPointer = valueOfVariablePointedTo as VariablePointerValue;
if (doubleRedirectionPointer) {
return doubleRedirectionPointer;
}
// Make copy of the variable pointer so we're not using the value direct from
// the runtime. Temporary must be local to the current scope.
else {
return new VariablePointerValue (varPointer.variableName, contextIndex);
}
}
// 0 if named variable is global
// 1+ if named variable is a temporary in a particular call stack element
int GetContextIndexOfVariableNamed(string varName)
{
if (_globalVariables.ContainsKey (varName))
return 0;
return _callStack.currentElementIndex;
}
Dictionary<string, Runtime.Object> _globalVariables;
// Used for accessing temporary variables
CallStack _callStack;
HashSet<string> _changedVariables;
}
}