You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
5.3 KiB
158 lines
5.3 KiB
# 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 |
|
} |
|
} |
|
}
|
|
|