ModifyEnv
is a class in cm.core that stores the original state of one or more Snappers, allowing the Snapper to be restored to that state (zero or more times).public class TheSnapster extends Snapper { public int snappiness; } { TheSnapster z(); // Necessary--ModifyEnv will not accept Snappers that aren't "active" // (i.e., in space and not suspended) mainSpace.undoableInsert(z); ModifyEnv env(); env << z; pln("Field value before change: ", z.snappiness); z.snappiness = 29; pln("Field value after change: ", z.snappiness); // `updateBsp` needs to be true in this case because we didn't say not to // put the snapper into the bsp when we inserted it into space and it's // still in there env.restore(updateBsp=true); pln("Field value after restore: ", z.snappiness); } Output: Field value before change: 0 Field value after change: 29 Field value after restore: 0
The Animation system currently makes heavy use of ModifyEnv with the goal of preventing intermediate steps or possibilities from having lasting effects. The canonical example of this in the realm of Abstract Office is snapping a Worksurface
to PanelFrames
. The default behavior is to automatically resize the Worksurface to fit the width of the panel. If the user moves their cursor away from that panel (rather than going through with connecting the worksurface to it) but the Worksurface is not restored by the ModifyEnv
, the new size will persist.
The Basics
When a Snapper is appended to the ModifyEnv for the first time, the env calls the Snapper's modifyCopy()
and stores the result. The default implementation of modifyCopy()
is to do a shallow copy and return the result.
Change Depth
Besides the "backups", the ModifyEnv also keeps a count for each Snapper, which will be referred to here as the Snapper's "change depth". This is intended to be able to keep track of whether the current state of the Snapper is the same as the backup (which would be a change depth of 0).
- Every time a Snapper is appended/added/"registered" to the ModifyEnv (using the << "append" operator) its change depth is increased (or set to 1 if the Snapper isn't yet in the env)
- If
ModifyEnv.remove(Snapper)
is called, the change depth is decreased for that Snapper (assuming it's actually in the env) ModifyEnv.restore()
will reset change depth to 0 for all Snappers in the envModifyEnv.restore(Snapper)
will reset a given Snapper's change depth to 0 (assuming it's actually in the env)
It is common for there to be some changes that should actually be "remembered" during the lifespan of the ModifyEnv which would otherwise get restored away (such as the user utilizing Properties (Animation-/Core-/Sidebar-/etc.) during an animation after the Snapper being manipulated has already been "backed up" by the Animation's ModifyEnv). If the Snapper's change depth is 0, then it is safe to tell the ModifyEnv to "forget" the backup and make those particular changes so that the next time it is added to the ModifyEnv a new backup will be made.
ModifyEnv.cleanup()
is the facility for doing this "safe forgetting": it will cause the env to remove all Snappers from itself whose change depth is 0.ModifyEnv.restore()
is called.
Snapper Interface
public class Entity extends CoreObject : modify notice, abstract { // ... /** * Produces a copy of `this` to be stored in the ModifyEnv for * possible future restore. * Should be overridden to additionally copy any sub-objects that need * to have changes to them restored. */ extend public Snapper modifyCopy(CopyEnv cEnv) { // Default implementation return this.copy(cEnv, shallow=true).Snapper; } // ... } public class Snapper extends Entity { // ... /** * Hook informing us that we have just had our memory overwritten by * that of `copyOfOriginalInModifyEnv`. */ extend public void restored(Snapper copyOfOriginalInModifyEnv) { } /*********************************************************************** * NOTE: The hooks below are ONLY called when ModifyEnv.restore() is * used (restoring all Snappers in the env), NOT when * ModifyEnv.restore(Snapper) is used (restoring one particular Snapper) ***********************************************************************/ /** * Hook informing a "live" Snapper that will actually be restored (us) * that it is about to have its memory overwritten by that from * `copyOfOriginalInModifyEnv`. */ extend public void beforeRestore(Snapper copyOfOriginalInModifyEnv) { } /** * Hook informing a "live" Snapper that has actually been restored (us) * that it and all applicable others in the env have been restored. */ extend public void afterRestore() { } // ... }
ModifyEnv Interface
public class ModifyEnv { /** * Map of snapper->(<backup, changeDepth>). */ public Object-><Object, int> modified; /** * Total of all change depths. If this is < 1, calls to restore() will * assume that no Snappers need to be restored. * WARNING: Currently (9.5), there are a significant number of ways that * this could potentially get out of sync with the actual change depths * of the entries (see comments below). */ public int invCount; /** * Create with no entries. */ public constructor(); /** * Same as * ModifyEnv env(); * env << z; */ public constructor(Snapper z); /** * "Forget" any inactive Snappers (!isActive()). * Warning: Currently (9.5) `invCount` is not updated by this (probably * an oversight). */ final public void removeInactive(); /** * Append/add/"register" `z`. * Should be called just before `z` undergoes changes that may need to * be reverted. * * If `z` has not already been "registered", a "backup" is created via * its modifyCopy() method and its change depth is set to 1. * * Otherwise, the pre-existing "backup" is kept as it is and `z`'s * change depth is increased. */ extend public ModifyEnv operator<<(Snapper z); /** * Decreases `z`'s change depth (assuming it was previously added to us). * Should be called if `z` was added to us in case of a change, but that * change didn't actually occur (abort/"false alarm"). * * WARNING: This should only be called when a corresponding append of `z` * is still "active" (i.e., the Snapper hasn't been removed from us * somehow, nothing has reset its change depth, etc). */ extend public void remove(Snapper z); /** * "Forget"/remove all entries and reset `invCount` to 0. */ extend public void clear(); /** * "Forget" all Snappers whose change depth is 0. */ extend public void cleanup(); /** * Gather all active (isActive() == true) Snappers whose change depth >0. * * WARNING: Currently (9.5), this has side effects equivalent to calling * removeInactive() (including the possible desync of `invCount`). */ extend public Snapper{} modifiedSnappers(); /** * Restore all Snappers whose change depth > 0, reset their change * depth to 0 and our `invCount` to 0. * * Note: calls removeInactive() before doing anything else. * * WARNING: Requires `invCount` to be accurate--if invCount < 1 nothing * except the removeInactive() call will happen. * * `invalidate` controls whether graphics invalidation is attempted on * affected Snappers. * `updateBsp` controls whether affected Snappers in the BSP are * removed from bsp before restore (and re-inserted after). * NOTE: If any of the Snappers are in the BSP when this * is called, `updateBsp` should be true (and not doing * so actually results in a CantHappen exception in * developMode). */ extend public bool restore(bool invalidate=false, bool updateBsp=false); /** * Restore just one Snapper, subtract its change depth from `invCount` * and reset said change depth to 0. * Does nothing if Snapper's change depth < 1. * * Note: calls removeInactive() before doing anything else (including * check whether `z` should actually be restored). * * WARNING: Requires `invCount` to be accurate--if invCount < 1 nothing * except the removeInactive() call will happen. * * WARNING: The restoration process is significantly different here from * restoring ALL Snappers. */ extend public bool restore(Snapper z); /** * This is only(?) used as one of the conditions determining whether * the protectModify syntax will attempt to revert changes done in its * block. * * For each connectedNeighbor of `z`, each Connector that is connected to * any of `z`'s Connectors are gathered. Then Snapper.snap() is called * for each of THAT Connector's connected Connectors (lose you yet?). * If any return false, so does this method. */ final public bool validateNeighbors(Snapper z); }
Comments
0 comments
Please sign in to leave a comment.