GGConventions.tioga
Author: Eric Bier on January 5, 1986 4:17:06 pm PST
Last edited by Bier on January 30, 1986 5:23:20 pm PST
This is a centralized place to put lore about Gargoyle. In particular, the history behind certain data structures, the invariants maintained by certain modules, and a list of kludges known to be in force. This is also a good place to keep lists -- e.g. the list of all procedures which have to be rewritten if data structure X changes.
Conventions
The Scene: Our First Opaque Type
Scenes are an opaque type, visible inside GGSceneImpl and defined in GGSceneType.mesa. This guarantees that only GGSceneImpl touches the entity list. Hence, we can change this data structure as we please to make commands like AddOutline run fast.
The Caret: Chairs and Attractors
The original idea between chairs and attractors was to build up a data structure of what was touching what. The caret would act as a kind of soldering iron. When a dragging operation completed, the object that moved with the caret (the chair) and the object that the caret snapped to (the attractor) would be listed as touching. This use has been abandoned. We didn't find enough use for the touching data structure to warrant the effort needed to maintain it. Unfortunately, chairs and attractors have been used for other purposes so we can't just get rid of them. Here are their uses:
Chair
Heuristics: During add, the chair trajectory was automatically hot. The chair should no longer be used for this. The new joint is simply selected and the same heuristics are used as for drag.
Close: The Close command closes the chair trajectory. Now that Add works by selecting a joint, Close could just work on the selected trajectories.
GGEventImplA.DescribeCaretObject describes the chair. It can be removed when chairs are no more.
Adding: The caret to be extended is the trajectory that has the caret sitting on its end. I don't know of a good way to get rid of this use of the chair. When writing a routine that may modify a trajectory that the caret is sitting on, it is safest to put GGCaret.SitOn[caret, nothing] in that routine.
GGEventImplB.DeleteCaretSegment is used to abort Add, and to "BackSpace" over segments. These uses cannot be removed until Add works differently.
Conclusion: Chairs should be used for adding segment to trajectories, only. I know of no reason that the caret should sit on a slice.
Attractor
GGEventImplC.AddControlPoint. The trajectory that most recently attracted the caret gets a new control point added to it. This is ambiguous because of the difficulty of getting the caret to snap to just what you want it to.
GGEventImplC.AddJoint. The trajectory that most recently attracted the caret gets a new joint added to it. This is ambiguous because of the difficulty of getting the caret to snap to just what you want it to.
Conclusion: Attractors aren't being used for much either. We may do well to try to phase them out so we don't have to maintain them everywhere.
Color
Setting the color. Color is part of the Imager state. If it changes unexpectedly, an incorrect picture can result. In Gargoyle, the color is never changed by a call to a GGShapes procedure. It is changed by GGRefresh.DrawOutline and GGRefresh.DrawTraj. Callers of GGShapes procedures (e.g. GGGravityImpl.DrawObjectBagRegardless must set the color before calling these procedures.
Names
Variable names refering to trajectories use the abbreviation "traj". Procedure names spell out Trajectory in full. GGSelect.DeselectTraj and SelectTraj should be fixed.
Slices
A slice is implemented as an object-oriented style class. A new type of slice is specified by writing the class procedures for the new type and writing class-specific routines that are hidden in the class implementation. All objects, including trajectories and outlines, are slice classes as of January, 1988.
Client programs of slice classes should NOT call class procedures directly. There is a veneer interface, GGSliceOps, which defines one public procedure for each type of class procedure. For example, do not execute slice.class.setStrokeWidth[slice, width]; instead execute GGSliceOps.SetStrokeWidth[slice, width], which will immediately call the class proc in the slice. By ensuring that clients only to through GGSliceOps to invoke class operations, we can intercept/monitor/log/breakpoint on all invocations if needed.
Bounding Boxes
Each segment and slice (GGSliceOps.GetBoundBox and GGSliceOps.GetTightBox) has a routine to get a tight boundbox and a bound box. Lazy evaluation is used, so these boxes should only be accessed through these routines.
BoundBoxes are used for refresh optimization. They must be large enough to include the visible geometry of a shape including stroke width.
Tight boxes are used for hit testing. They need not include stroke width but must include all of the positions of the joints and control points.
NEEDED: A convention about who should copy boundBoxes and who can use the originals.
Slices may cache the value of the bound box for their complete parts and return the cached value when GGSliceOps.GetBoundBox[slice, parts] is called. The convention parts=NIL means return the cached value if it is current. This convention will eventually be replaced by a more specific test for complete parts.
Sequences
A sequence is a description of a subset of the joints and segments of a trajectory as it was when the sequence was made.
If the number of segments or joints in a trajectory is changed, all sequences describing that trajectory become obsolete, and should be discarded. The procedure GGSelect.ReselectTraj can be used to update the sequences on selection lists. When sequences are stored on persistent trigger lists by GGAlignImpl, then these sequences will have to be updated as well.
The operations which change a trajectory in this was include: GGObjects.AddSegment, GGObjects.DeleteSegment, and GGEventImplA.Delete (which should be moved to GGObjects). Planned operations which splice in segments are also in this category (e.g. Weld).
Descriptors and Mutability
SliceDescriptors are mostly immutable. However, there is one big exception. When SliceDescriptors are put in trigger bags, they are subject to mutation. GGAlign.RemoveMoving does the evil deed. I think I would probably just have RemoveMoving make a copy when it has to. The disadvantage will be that I will have to build a new list of descriptors rather than bashing the old one. Life is hard.
When this is fixed, GGSelect.FindSelectedOutline and GGSelect.FindSelectedSlice will be able to use the faster forms that are now commented out.
TriggerBags, ObjectBags and mutability
When we start a dragging operation, we would like to save the current bags, make some new ones, use the new ones during dragging, and then return to using the old ones. For efficient the new ones should be made incrementally from the old ones. How is this to be done?
There are four data types that may or may not be mutated: the bag record, the list of featureData, the featureDatas, and the slice descriptors referred to by the featureDatas. The proposal is that the descriptors be immutable, the featureDatas be "mostly" immutable, the lists be mutable, and the bag records be mutable. In other words, you can add things to a bag without thinking of that as creating a new bag. This means that when you copy a bag, you should copy the bag record and the CONS cells in the lists, but you need not copy the featureDatas or descriptors.
Fixed Parts and Moving Parts
There are several questions we routinely ask slices that are about to be dragged. First, we want to know which parts to put on the overlay plane, and which to leave on the background. Second, we want to know: of the parts of you that are in the triggerBag, which should be removed because they are moving.
The first question is answered by GGSliceOps.MovingParts, which returns SliceDescriptors for the parts that are being dragged in entirety, the parts that are rubber-banding, the parts that will be left on the background plane, and the parts that are going to be dragged.
It looks like, we ought to be able to consolidate these two questions into one procedure called partsOnOverlay, that takes as argument a descriptor of the parts that are selected and returns a descriptor for parts being dragged, a descriptor for parts being rubberbanded, and a descriptor for parts that are stationary. In many cases two of these descriptors will be NIL so it shouldn't be a storage allocation problem.
Selections
Selections are the Vietnam of Gargoyle. Fortunately, they have been getting simpler and simpler now that outlines are slices. Here is the current convention:
The selection list is a list of slice descriptors.
No empty descriptors should be on the list; If a descriptor is on the selected list, one or more of its components are selected.
Setting bits. The GGSelect operations IsSelectedInPart and IsSelectedInFull take advantage of a set of flags stored in the objects themselves. In particular, slices, joints, segments, and control points have selectedInFull bits.
Who uses the selection bits? We could get some real performance improvements if we could store a completeDescriptor with each slice as well as an empty descriptor.
Control Points
A segment can be normal selected in one of five ways:
1) In entirety (both joints, the segment, and all control points).
2) One or both endjoints only.
3) One or more control points only.
4) Control points and endjoints.
5) Not at all.
There are 8 selection combinations for joints (J), the segment (S), and the control points (C):
(none), (J), (S), (C), (JS), (JC), (SC), (JSC)
The 3 possibilities in bold are illegal. An easy rule to remember is that S -> C and S -> J. That is, if the segment is selected then both of its joints, and all of its control points must be selected as well. GGSelect should enforce this convention.]
Only one possibility is illegal for hot selections:
(none), (J), (S), (C), (JS), (JC), (SC), (JSC)
The possibility in bold is illegal. An easy rule to remember is that S -> C. That is, if the segment is selected then all of its control points must be selected as well. GGSelect should enforce this convention. Changed by Bier on July 14, 1986, to allow control points to be made cold even when the adjacent segment is hot.
startProc, duringProc, endProc, continueProc and the mouse position.
There are currently 4 interesting events for interactive actions.
1) When the mouse button first goes down, immediate action should be taken to update the screen so the user can see, even before he moves the mouse, that an operation is beginning.
2) When the mouse moves, objects will move.
3) When the operation is terminated by letting go of all buttons. Unfortunately, the mouse position at this time is not likely to be particularly relevant. Many operations stop all motion as soon as any buttons go up. Hence, the mouse position from the last duringProc must be saved. This conflicts with any attempts to allow the SlackProcess to remove all duringProcs, because at least the last duringProc must be done. To get smooth motion anyway, the endProc can do no further motion.
4) When the continueProc is called, a new action is beginning. The mouse position is no longer relevant to the old operation. Again, the mouse position (or the mapPoint, or just the transform) must be saved from the previous duringProc. The new operation then uses the current mouse position.
This is true for adding, and dragging.
The Add Operation
The Add operation is one of the most complicated interactive operations that Gargoyle performs. The following data structures must be updated in a consistent fashion:
1) The Scene
2) The Selections
3) The TriggerBag
4) The Align Bag
5) The Scene Bag
6) The Overlay Plane
These updates are as follows:
1) The Scene
Add a new segment to the caret trajectory. If there is no caret trajectory, create a new trajectory.
2) The Selections
normal. Deselect all. Set the selection to be the new joint.
hot. No change, except make the new joint hot, if the original caret joint is hot.
3) The TriggerBag
Find the descriptor corresponding to the old caret outline. Remove it from the bag. Insert a descriptor for the new outline. This descriptor is just the hot descriptor for the new outline.
4) The Align Bag
Find all alignments that used to mention the caret outline. Remove that mention. Now filter the hot descriptor mentioned above through the usual filters to create new mentions.
5) The Scene Bag
Find the descriptor corresponding to the old caret outline. Remove it from the bag. Insert a descriptor for the new outline. This descriptor is a complete descriptor for the new outline.
6) The Overlay Plane
Remove everything from the overlay plane.
Feedback and Feedback messages
Feedback.mesa is used to communicate with the user via the feedback line and the Garogyle typescript. Feedback provides three kinds of communication: feedback line only, typescript only, both feedback line and typescript. The following conventions tell you which kind of communication to use in various Gargoyle situations:
1. feedback line only: messages during interactive operations (selection, transformation, or caret position, for example), final feedback at end of interactive operations (final selection, final transformation, or final caret position, for example), cycling selection messages. These messages are high frequency and would badly clutter the typescript, although they would be occassionally desireable. Use Feedback.AppendHerald, Feedback.PutFHerald.
2. typescript only: never. Anything sent to the typescript should also go to the feedback line.
3. both feedback line and typescript: all other messages, including error messages and messages output by user request to Show opertions (ShowFitParameters, Show Value(s), for example). Use Feedback.Append, Feedback.PutFHerald.
Any user operations which sets or otherwise changes internal state should cause a visual display change. If no other display change occurs, such as a TwoState change, GraphicsButton change, or cursor change, an appropriate message should be Appended. Various "Set Parameters" usually have a corresponding "Show Parameters" procedure which may be invoked programmatically after a successful Set (SetLineWidth and ShowValues in the Stroke menu, for example). Sometimes a feedback message can augment a visual display such as "All objects made Cold" after a MakeAllCold user operation.
SelectModes
The SelectModes TYPE is currently used for three purposes. The (literal, joint, segment, traj, topLevel) values are used for selection. The "slice" value and others are used to create descriptors. The (controlPoint and segmentRange) are used (?) for extend modes.