|  | # Simple Chord for Tcl | 
|  | # | 
|  | # A "chord" is a method with more than one entrypoint and only one body, such | 
|  | # that the body runs only once all the entrypoints have been called by | 
|  | # different asynchronous tasks. In this implementation, the chord is defined | 
|  | # dynamically for each invocation. A SimpleChord object is created, supplying | 
|  | # body script to be run when the chord is completed, and then one or more notes | 
|  | # are added to the chord. Each note can be called like a proc, and returns | 
|  | # immediately if the chord isn't yet complete. When the last remaining note is | 
|  | # called, the body runs before the note returns. | 
|  | # | 
|  | # The SimpleChord class has a constructor that takes the body script, and a | 
|  | # method add_note that returns a note object. Since the body script does not | 
|  | # run in the context of the procedure that defined it, a mechanism is provided | 
|  | # for injecting variables into the chord for use by the body script. The | 
|  | # activation of a note is idempotent; multiple calls have the same effect as | 
|  | # a simple call. | 
|  | # | 
|  | # If you are invoking asynchronous operations with chord notes as completion | 
|  | # callbacks, and there is a possibility that earlier operations could complete | 
|  | # before later ones are started, it is a good practice to create a "common" | 
|  | # note on the chord that prevents it from being complete until you're certain | 
|  | # you've added all the notes you need. | 
|  | # | 
|  | # Example: | 
|  | # | 
|  | #   # Turn off the UI while running a couple of async operations. | 
|  | #   lock_ui | 
|  | # | 
|  | #   set chord [SimpleChord::new { | 
|  | #     unlock_ui | 
|  | #     # Note: $notice here is not referenced in the calling scope | 
|  | #     if {$notice} { info_popup $notice } | 
|  | #   } | 
|  | # | 
|  | #   # Configure a note to keep the chord from completing until | 
|  | #   # all operations have been initiated. | 
|  | #   set common_note [$chord add_note] | 
|  | # | 
|  | #   # Activate notes in 'after' callbacks to other operations | 
|  | #   set newnote [$chord add_note] | 
|  | #   async_operation $args [list $newnote activate] | 
|  | # | 
|  | #   # Communicate with the chord body | 
|  | #   if {$condition} { | 
|  | #     # This sets $notice in the same context that the chord body runs in. | 
|  | #     $chord eval { set notice "Something interesting" } | 
|  | #   } | 
|  | # | 
|  | #   # Activate the common note, making the chord eligible to complete | 
|  | #   $common_note activate | 
|  | # | 
|  | # At this point, the chord will complete at some unknown point in the future. | 
|  | # The common note might have been the first note activated, or the async | 
|  | # operations might have completed synchronously and the common note is the | 
|  | # last one, completing the chord before this code finishes, or anything in | 
|  | # between. The purpose of the chord is to not have to worry about the order. | 
|  |  | 
|  | # SimpleChord class: | 
|  | #   Represents a procedure that conceptually has multiple entrypoints that must | 
|  | #   all be called before the procedure executes. Each entrypoint is called a | 
|  | #   "note". The chord is only "completed" when all the notes are "activated". | 
|  | class SimpleChord { | 
|  | field notes | 
|  | field body | 
|  | field is_completed | 
|  | field eval_ns | 
|  |  | 
|  | # Constructor: | 
|  | #   set chord [SimpleChord::new {body}] | 
|  | #     Creates a new chord object with the specified body script. The | 
|  | #     body script is evaluated at most once, when a note is activated | 
|  | #     and the chord has no other non-activated notes. | 
|  | constructor new {i_body} { | 
|  | set notes [list] | 
|  | set body $i_body | 
|  | set is_completed 0 | 
|  | set eval_ns "[namespace qualifiers $this]::eval" | 
|  | return $this | 
|  | } | 
|  |  | 
|  | # Method: | 
|  | #   $chord eval {script} | 
|  | #     Runs the specified script in the same context (namespace) in which | 
|  | #     the chord body will be evaluated. This can be used to set variable | 
|  | #     values for the chord body to use. | 
|  | method eval {script} { | 
|  | namespace eval $eval_ns $script | 
|  | } | 
|  |  | 
|  | # Method: | 
|  | #   set note [$chord add_note] | 
|  | #     Adds a new note to the chord, an instance of ChordNote. Raises an | 
|  | #     error if the chord is already completed, otherwise the chord is | 
|  | #     updated so that the new note must also be activated before the | 
|  | #     body is evaluated. | 
|  | method add_note {} { | 
|  | if {$is_completed} { error "Cannot add a note to a completed chord" } | 
|  |  | 
|  | set note [ChordNote::new $this] | 
|  |  | 
|  | lappend notes $note | 
|  |  | 
|  | return $note | 
|  | } | 
|  |  | 
|  | # This method is for internal use only and is intentionally undocumented. | 
|  | method notify_note_activation {} { | 
|  | if {!$is_completed} { | 
|  | foreach note $notes { | 
|  | if {![$note is_activated]} { return } | 
|  | } | 
|  |  | 
|  | set is_completed 1 | 
|  |  | 
|  | namespace eval $eval_ns $body | 
|  | delete_this | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | # ChordNote class: | 
|  | #   Represents a note within a chord, providing a way to activate it. When the | 
|  | #   final note of the chord is activated (this can be any note in the chord, | 
|  | #   with all other notes already previously activated in any order), the chord's | 
|  | #   body is evaluated. | 
|  | class ChordNote { | 
|  | field chord | 
|  | field is_activated | 
|  |  | 
|  | # Constructor: | 
|  | #   Instances of ChordNote are created internally by calling add_note on | 
|  | #   SimpleChord objects. | 
|  | constructor new {c} { | 
|  | set chord $c | 
|  | set is_activated 0 | 
|  | return $this | 
|  | } | 
|  |  | 
|  | # Method: | 
|  | #   [$note is_activated] | 
|  | #     Returns true if this note has already been activated. | 
|  | method is_activated {} { | 
|  | return $is_activated | 
|  | } | 
|  |  | 
|  | # Method: | 
|  | #   $note activate | 
|  | #     Activates the note, if it has not already been activated, and | 
|  | #     completes the chord if there are no other notes awaiting | 
|  | #     activation. Subsequent calls will have no further effect. | 
|  | method activate {} { | 
|  | if {!$is_activated} { | 
|  | set is_activated 1 | 
|  | $chord notify_note_activation | 
|  | } | 
|  | } | 
|  | } |