require 'thread' require 'weakref' ## # The StateRegistry is used to track, save, and restore the # states of objects registered into it. # # Session uses this to restore registered objects to the state # they were at when the user first viewed the page they are # attached to. # # For example, in Borges/Component/Counter.rb, the Counter object # is registered with the session when it is initialized. # Every time a new page is rendered, a snapshot of the Counter's # state is created, so that if the user goes back in their # history, any requested actions on the Counter will operate with # the Counter's state from the first rendering of that page. # # Here's a timeline to help explain things better: # # load /borges/counter # - Counter initializes @count to 0 and registers self # - Counter state snapshot taken # - /borges/counter/session/a rendered and sent to user # # load /borges/counter/session/a # - /borges/counter/session/b rendered and sent to user # - user clicks "++" # # load /borges/counter/session/b # - restore Counter snapshot from "a" # - "++" was clicked, increment @count to 1 # - Counter state snapshot taken # - /borges/counter/session/c rendered and sent to user # - user realizes they made a mistake and uses the back button # - user clicks "--" # # load /borges/counter/session/b # - restore Counter snapshot from "a" # - "--" was clicked, decrement @count to -1 # - Counter state snapshot taken # - /borges/counter/session/d rendered and sent to user # # The snapshots allow an object's state to be backtracked easily. # The snapshots created by the registry will drop unreferenced # objects when the Session drops an old page from its cache of # recent pages. # # If the Session's history cache contains 10 pages, and the user # performs five operations in the Counter, then moves on to # another task and spends 10 operations there, the Counter will # no longer be accessible in their Session history, and no longer # restorable. class Borges::StateRegistry ## # Creates a new StateRegistry. def initialize @wrefs = [] end ## # Registers an object in order to keep track of its state. def register(obj) wref = WeakRef.new(obj) @wrefs << wref end ## # Restore the state of all objects in the registry from a # previously recorded snapshot. def restore_snapshot(snap) snap.each do |obj, copy| restore_object_from_snapshot(obj, copy) end end ## # Returns a snapshot of the current state of all registered # objects. def snapshot gc_disabled = nil snapshot = {} Thread.exclusive do gc_disabled = GC.disable end @wrefs.each do |wref| if wref.weakref_alive? then obj = wref.__getobj__ snapshot[obj] = obj.clone end end GC.enable unless gc_disabled @wrefs.delete_if do |wref| not wref.weakref_alive? end return snapshot end private ## # Restore an individual object's state from a copy. def restore_object_from_snapshot(obj, copy) obj.instance_variables.each do |var| obj.instance_variable_set(var, copy.instance_variable_get(var)) end end end