Class: Fable::VariablesState
- Inherits:
-
Object
- Object
- Fable::VariablesState
- Defined in:
- lib/fable/variables_state.rb
Overview
Encompasses all the global variables in an ink Story, and allows binding of a variable_changed event so that that game code can be notified whenever the global variables change.
Instance Attribute Summary collapse
-
#batch_observing_variable_changes ⇒ Object
Returns the value of attribute batch_observing_variable_changes.
-
#callstack ⇒ Object
Returns the value of attribute callstack.
-
#changed_variables_for_batch_observing ⇒ Object
Returns the value of attribute changed_variables_for_batch_observing.
-
#default_global_variables ⇒ Object
Returns the value of attribute default_global_variables.
-
#dont_save_default_values ⇒ Object
(also: #dont_save_default_values?)
When saving out state, we can skip saving global values that remain equal to the initial values that were declared in ink.
-
#globals ⇒ Object
Returns the value of attribute globals.
-
#list_definitions_origins ⇒ Object
Returns the value of attribute list_definitions_origins.
-
#patch ⇒ Object
Returns the value of attribute patch.
-
#variable_observers ⇒ Object
Returns the value of attribute variable_observers.
Instance Method Summary collapse
- #[](variable_name) ⇒ Object
- #[]=(variable_name, given_value) ⇒ Object
- #add_variable_observer(variable_name, &block) ⇒ Object
- #apply_patch! ⇒ Object
- #assign(variable_assignment, value) ⇒ Object
- #from_hash!(hash_to_use) ⇒ Object
-
#get_context_index_of_variable_named(variable_name) ⇒ Object
0 if named variable is global =! if named variable is a temporary in a particular callstack element.
- #get_default_variable_value(variable_name) ⇒ Object
- #get_raw_variable_with_name(variable_name, context_index) ⇒ Object
- #get_variable_with_name(variable_name) ⇒ Object
- #get_variable_with_name_internal(variable_name, context_index) ⇒ Object
- #global_variable_exists_with_name?(variable_name) ⇒ Boolean
- #has_variable_observers? ⇒ Boolean
-
#initialize(callstack, list_definitions_origins) ⇒ VariablesState
constructor
A new instance of VariablesState.
- #remove_variable_observer(variable_name, &block) ⇒ Object
-
#resolve_variable_pointer!(variable_pointer) ⇒ Object
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.
- #runtime_objects_equal?(object_1, object_2) ⇒ Boolean
- #set_global(variable_name, value) ⇒ Object
- #snapshot_default_globals ⇒ Object
- #to_hash ⇒ Object
- #variable_changed_event(variable_name, current_value) ⇒ Object
Constructor Details
#initialize(callstack, list_definitions_origins) ⇒ VariablesState
Returns a new instance of VariablesState.
88 89 90 91 92 93 94 |
# File 'lib/fable/variables_state.rb', line 88 def initialize(callstack, list_definitions_origins) self.globals = {} self.callstack = callstack self.list_definitions_origins = list_definitions_origins self.dont_save_default_values = true self.variable_observers = {} end |
Instance Attribute Details
#batch_observing_variable_changes ⇒ Object
Returns the value of attribute batch_observing_variable_changes.
6 7 8 |
# File 'lib/fable/variables_state.rb', line 6 def batch_observing_variable_changes @batch_observing_variable_changes end |
#callstack ⇒ Object
Returns the value of attribute callstack.
6 7 8 |
# File 'lib/fable/variables_state.rb', line 6 def callstack @callstack end |
#changed_variables_for_batch_observing ⇒ Object
Returns the value of attribute changed_variables_for_batch_observing.
6 7 8 |
# File 'lib/fable/variables_state.rb', line 6 def changed_variables_for_batch_observing @changed_variables_for_batch_observing end |
#default_global_variables ⇒ Object
Returns the value of attribute default_global_variables.
6 7 8 |
# File 'lib/fable/variables_state.rb', line 6 def default_global_variables @default_global_variables end |
#dont_save_default_values ⇒ Object Also known as: dont_save_default_values?
When saving out state, we can skip saving global values that remain equal to the initial values that were declared in ink. This makes the save object (potentially) much smaller assuming that at least a portion of the globals haven’t changed. However, it can also take marginally longer to save in the case that the majority HAVE changed, since it has to compare all globals. It may also be useful to turn this off for testing worst case save timing.
129 130 131 |
# File 'lib/fable/variables_state.rb', line 129 def dont_save_default_values @dont_save_default_values end |
#globals ⇒ Object
Returns the value of attribute globals.
6 7 8 |
# File 'lib/fable/variables_state.rb', line 6 def globals @globals end |
#list_definitions_origins ⇒ Object
Returns the value of attribute list_definitions_origins.
6 7 8 |
# File 'lib/fable/variables_state.rb', line 6 def list_definitions_origins @list_definitions_origins end |
#patch ⇒ Object
Returns the value of attribute patch.
6 7 8 |
# File 'lib/fable/variables_state.rb', line 6 def patch @patch end |
#variable_observers ⇒ Object
Returns the value of attribute variable_observers.
6 7 8 |
# File 'lib/fable/variables_state.rb', line 6 def variable_observers @variable_observers end |
Instance Method Details
#[](variable_name) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/fable/variables_state.rb', line 55 def [](variable_name) if !patch.nil? && patch.get_global(variable_name) return patch.get_global(variable_name).value_object end # Search main dictionary first # If it's not found, it might be because the story content has # changed, and the original default value hasn't been instantiated variable_value = @globals[variable_name] || @default_global_variables[variable_name] if !variable_value.nil? return variable_value.value_object else return nil end end |
#[]=(variable_name, given_value) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/fable/variables_state.rb', line 71 def []=(variable_name, given_value) if !default_global_variables.has_key?(variable_name) raise Error, "Cannot assign to a variable (#{variable_name}) that hasn't been declared in the story" end value = Value.create(given_value) if value.nil? if given_value.nil? raise Error, "Cannot pass nil to VariableState" else raise Error, "Invalid value passed to VariableState: #{given_value}" end end set_global(variable_name, value) end |
#add_variable_observer(variable_name, &block) ⇒ Object
16 17 18 19 20 |
# File 'lib/fable/variables_state.rb', line 16 def add_variable_observer(variable_name, &block) self.variable_observers[variable_name] ||= [] self.variable_observers[variable_name] << block return true end |
#apply_patch! ⇒ Object
96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/fable/variables_state.rb', line 96 def apply_patch! patch.globals.each do |name, value| self.globals[name] = value end if !changed_variables_for_batch_observing.nil? patch.changed_variables.each do |name| changed_variables_for_batch_observing << name end end patch = nil end |
#assign(variable_assignment, value) ⇒ Object
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 |
# File 'lib/fable/variables_state.rb', line 223 def assign(variable_assignment, value) name = variable_assignment.variable_name context_index = -1 # Are we assigning to a global variable? set_global = false if variable_assignment.new_declaration? set_global = variable_assignment.global? else set_global = global_variable_exists_with_name?(name) end # Constructing new variable pointer reference if variable_assignment.new_declaration? if value.is_a?(VariablePointerValue) fully_resolved_variable_pointer = resolve_variable_pointer!(value) value = fully_resolved_variable_pointer end # 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 existing_pointer = get_raw_variable_with_name(name, context_index) while existing_pointer && existing_pointer.is_a?(VariablePointerValue) name = existing_pointer.variable_name context_index = existing_pointer.context_index set_global = (context_index == 0) existing_pointer = get_raw_variable_with_name(name, context_index) end end if set_global set_global(name, value) else callstack.set_temporary_variable(name, value, variable_assignment.new_declaration?, context_index) end end |
#from_hash!(hash_to_use) ⇒ Object
110 111 112 113 114 115 116 117 118 119 |
# File 'lib/fable/variables_state.rb', line 110 def from_hash!(hash_to_use) @globals = {} @default_global_variables.each do |key, value| if hash_to_use.has_key?(key) @globals[key] = Serializer.convert_to_runtime_object(hash_to_use[key]) else @globals[key] = value end end end |
#get_context_index_of_variable_named(variable_name) ⇒ Object
0 if named variable is global
! if named variable is a temporary in a particular callstack element
319 320 321 322 323 324 325 |
# File 'lib/fable/variables_state.rb', line 319 def get_context_index_of_variable_named(variable_name) if global_variable_exists_with_name?(variable_name) return 0 end return callstack.current_element_index end |
#get_default_variable_value(variable_name) ⇒ Object
173 174 175 |
# File 'lib/fable/variables_state.rb', line 173 def get_default_variable_value(variable_name) return self.default_global_variables[variable_name] end |
#get_raw_variable_with_name(variable_name, context_index) ⇒ Object
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 |
# File 'lib/fable/variables_state.rb', line 192 def get_raw_variable_with_name(variable_name, context_index) variable_value = nil if context_index == 0 || context_index == -1 if !patch.nil? && patch.get_global(variable_name) return patch.get_global(variable_name) end if globals.has_key?(variable_name) return globals[variable_name] end # Getting variables can actually happen during global setup because # you can do VAR x = A_LIST_ITEM # so default_global_variables may be null # WE need to do this check though in case a new global is added, so we need to # revert to the default globals dictionary, since an initial value hasn't been set yet if !default_global_variables.nil? && default_global_variables.has_key?(variable_name) return default_global_variables[variable_name] end list_item_value = list_definitions_origins.find_single_item_list_with_name(variable_name) if list_item_value return list_item_value end end # Temporary return callstack.get_temporary_variable_with_name(variable_name, context_index) end |
#get_variable_with_name(variable_name) ⇒ Object
169 170 171 |
# File 'lib/fable/variables_state.rb', line 169 def get_variable_with_name(variable_name) return get_variable_with_name_internal(variable_name, -1) end |
#get_variable_with_name_internal(variable_name, context_index) ⇒ Object
181 182 183 184 185 186 187 188 189 190 |
# File 'lib/fable/variables_state.rb', line 181 def get_variable_with_name_internal(variable_name, context_index) variable_value = get_raw_variable_with_name(variable_name, context_index) # Get value from pointer? if variable_value.is_a?(VariablePointerValue) variable_value = get_variable_with_name_internal(variable_value.variable_name, variable_value.context_index) end return variable_value end |
#global_variable_exists_with_name?(variable_name) ⇒ Boolean
177 178 179 |
# File 'lib/fable/variables_state.rb', line 177 def global_variable_exists_with_name?(variable_name) globals.has_key?(variable_name) || (!default_global_variables.nil? && default_global_variables.has_key?(variable_name)) end |
#has_variable_observers? ⇒ Boolean
12 13 14 |
# File 'lib/fable/variables_state.rb', line 12 def has_variable_observers? self.variable_observers.all?{|name, observers| !observers.empty? } end |
#remove_variable_observer(variable_name, &block) ⇒ Object
22 23 24 25 26 |
# File 'lib/fable/variables_state.rb', line 22 def remove_variable_observer(variable_name, &block) self.variable_observers[variable_name] ||= [] self.variable_observers[variable_name].delete(block) return true end |
#resolve_variable_pointer!(variable_pointer) ⇒ Object
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.
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/fable/variables_state.rb', line 295 def resolve_variable_pointer!(variable_pointer) context_index = variable_pointer.context_index if context_index == -1 context_index = get_context_index_of_variable_named(variable_pointer.variable_name) end value_of_variable_pointed_to = get_raw_variable_with_name(variable_pointer.variable_name, context_index) # 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. if value_of_variable_pointed_to.is_a?(VariablePointerValue) return value_of_variable_pointed_to # Make a copy of the variable pointer so we're not using the value directly # from the runtime. Temporary must bne local to the current scope else return VariablePointerValue.new(variable_pointer.variable_name, context_index) end end |
#runtime_objects_equal?(object_1, object_2) ⇒ Boolean
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/fable/variables_state.rb', line 152 def runtime_objects_equal?(object_1, object_2) if object_1.class != object_2.class return false end # Perform equality on int/float manually to avoid boxing if object_1.is_a?(IntValue) || object_1.is_a?(FloatValue) return object_1.value == object_2.value end if object_1.is_a?(Value) return object_1.value_object.equal?(object_2.value_object) end raise Error, "FastRoughDefinitelyEquals: Unsupported runtime object type: #{object_1.class}" end |
#set_global(variable_name, value) ⇒ Object
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/fable/variables_state.rb', line 265 def set_global(variable_name, value) old_value = nil if patch.nil? || !patch.get_global(variable_name) old_value = globals[variable_name] end ListValue.retain_list_origins_for_assignment(old_value, value) if !patch.nil? patch.set_global(variable_name, value) else self.globals[variable_name] = value end if has_variable_observers? && !value.equal?(old_value) if batch_observing_variable_changes if !patch.nil? patch.add_changed_variable(variable_name) elsif !changed_variables_for_batch_observing.nil? changed_variables_for_batch_observing << variable_name end else variable_changed_event(variable_name, value) end end end |
#snapshot_default_globals ⇒ Object
261 262 263 |
# File 'lib/fable/variables_state.rb', line 261 def snapshot_default_globals self.default_global_variables = self.globals.dup end |
#to_hash ⇒ Object
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/fable/variables_state.rb', line 132 def to_hash export = {} self.globals.each do |key, value| if dont_save_default_values? # Don't write out values that are the same as the default global # values if default_global_variables.has_key?(key) if runtime_objects_equal?(default_global_variables[key], value) next end end end export[key] = Serializer.convert_from_runtime_object(value) end return export end |
#variable_changed_event(variable_name, current_value) ⇒ Object
28 29 30 31 32 33 34 |
# File 'lib/fable/variables_state.rb', line 28 def variable_changed_event(variable_name, current_value) if variable_observers.has_key?(variable_name) variable_observers[variable_name].each do |block| block.call(variable_name, current_value.value) end end end |