- Introduction
- Vessel Class Diagram (Relational and Canonical)
- Creating Vessels
- Vessels and Animations
- Vessels on CoreObject
- Helpful Operations
- Feature Search Feedback Example
- Generic Vessels and Their Usage
- Visitors
Introduction
It makes dynamic relationships with other core objects (snappers, animations, and other vessels) possible by acting as a communication layer between them. With the aim of being as simple (and as small) as possible, a Vessel resides within a collection on a core object -- providing feedback and modular construction for that object. Vessels are not streamable by default and should be used in the design with a collaborative mentality (diversification of manipulation). In general, they are extremely helpful for dynamic relationships and can simplify/speed up operations by reducing code through re-usability, modularization, and increased extendability for complex manipulations over core objects.
Vessel Class Diagram (Relational and Canonical)
Creating Vessels
The only requirement a Vessel has to be placed on a CoreObject is to have the attribute key: string. This key is unique to the Vessel within that collection. When given an owner upon construction, the Vessel will place itself within that CoreObject's collection of vessels.
When creating vessels, it is recommended to tag them in groups. This is done by adding a set of symbols as tags that additionally identify the vessels and relate them to each other. One can either pass the attribute tags: symbol{} to the Vessel upon construction or build them up afterward. Manipulations over an object's vessels are usually intended for a small subset of them, therefore, many of the operations also take tags: symbol{} as a parameter.
It is also helpful to be aware of the attribute args: str->Object. This is very common in the construction of objects that extend PropObj. Ultimately, this map will end up in the prop data on that object. However, for vessels, there are many interpreted arguments that are default shortcuts for specifically intended behaviors. To find these, go to the Vessel base class and look at the comments referring to the default arguments. When utilizing this, it is also helpful to be aware of the props{} syntax Props. This syntax will result in the formation of a string to object map in a fashion much more suited to the construction of prop data. It is also helpful to know of the "is(key: str, value: Object = null) : bool" method on PropObj. This method is intended to help with interpreted arguments by easily allowing for the testing of prop data for existence. Optionally, one can also test against the value of the prop data entry by passing an object into the value parameter. There are examples of these methodologies below.
Vessels and Animations
When a Vessel is owned by an Animation, it is assumed a composition by default. Each Vessel can be either active or inactive -- meaning that if the Vessel has graphics, they will end up in the animations get3D()
and graph2D()
by default. This makes for very flexible graphical feedback as the entirety of the feedback does not need to be explicitly defined, only added.
Vessels can be used in many ways in conjunction with animations. They can be implemented to provide debugging feedback, candidate feedback for tool animations, circle menu formation/feedback, Snapper insert/manipulate behaviors and feedback, imported alternate feedback (candidates and selection), alternate/added animation properties through PropDefs, containers for complex manipulations/temporary data structures, temporary highlights surrounding other core objects, etc.
Vessels on CoreObject
Although animations make use of Vessel graphics by default, each core object can call vessels3D()
and vessels2D()
. By doing so, the core object will behave similarly to core animations with respect to graphics. However, in most cases, core objects that are not animations will be base objects in which vessels will be transfered from -- making animations that surround that object more responsive in ways specific to that object.
Helpful Operations
dumpVessels
Because vessels can exist differently based upon interaction and context on single objects, knowing where they are and where they have been can be difficult. In CoreObject, dumpVessels()
can be used to show the details of that objects vessels. Which ones are active, an optional history, optional tags filter, or a fixed set of vessels to dump -- a very helpful tool to utilize in knowing a vessel's state.
updateVessels
Vessel has a method update()
which returns a boolean. Calling updateVessels()
on a CoreObject results in update()
being called on each Vessel in the objects collection. If any of the vessels return true on update()
, the vessel updator is marked as invalid. If the CoreObject is a CoreAnimation and 'invalidate=true' was passed as a parameter to updateVessels()
, this invalidation results in invalidate()
being called on the animation.
Beyond this basic usage for updateVessels()
, one can also pass in an optional updator. This is benificial as much more complex interactions can be performed for each Vessel externally. This can ultimately change what update means for the Vessel. It is also very nice to utilize when setting many vessels to active and inactive at once. In the constructor for an updator, one can specify tags that should be made active, tags that should be made inactive, and what should happen to all of the other vessels if needed.
transferVesselsByTag
Vessels can be easily transfered between CoreObjects. With transferVesselsByTag()
, a subset of the calling objects vessels are transfered to a CoreObject destination. This implicitely removes the vessels from the calling object, so care is required when utilizing this operation. When the parameter update is set or an updator is given, both the source and the destination are updated at the end of transfering via updateVessels()
.
copyTransferVesselsByTag
Utilizing copyTransferVesselsByTag()
results in the destination recieving copies of the vessels chosen to be transfered. This does not remove the vessels from the calling object and if update is set, only the destination gets updateVessels()
. Also, a custom tag visitor is not allowed to be passed into this operation to keep the copied vessels to a known minimum. This is a much safer version of transferVesselsByTag()
because of the lack of side effects.
visitAllVessels
This is the main medium for operations over collections of vessels. Performing operations in this way has many advantages for vessels. Allowing for operations to be performed over multiple collections means that they can be easily extended to include other CoreObjects. One example where this is very benificial is Snapper candidates. An operation can be extended to include these candidates by calling visitAllVessels()
on them with the same visitor.
Feature Search Feedback Example
In this example, one has a CoreToolAnimation that is finding candidates via feature search. The goal is to create some feedback that appears only when a feature has been found that lets the user see more clearly where they are pointing. To do this, a Vessel needs to be added to the animation. Below is the complete solution to this problem.
/** * Feature search feedback vessel. */ public class FeatureSearchFeedbackVessel extends Vessel { /** * Color. */ public color c=blue; /** * Initialize. */ public void initialize() { super(); this."candidateActive" = true; } /** * Append prop defs. */ public void appendPropDefs(PropDefs defs) { super(..); defs.put(CoreFieldPropDef(this.class, "c")); } /** * Update. */ public bool update() { bool old = active; super(); if (owner as CoreToolAnimation) if (active) active = owner.featureSearch(); point oldP = pos; if (active) updatePosition(); return old != active or oldP != pos; } /** * Update position. */ extend public void updatePosition() { if (?Animation a = owner) if ((SnapAlternative sa = a.lastAlternativeSelected) and ?FeatureSearch fs = sa."fs") this.pos = fs.pos; } /** * Get2D. */ public GInstance get2D() { GInstance gs(); gs << GCircle(pos.point2D, radius=1inch, fillColor=c); return gs; } /** * Get3D. */ public Primitive3D get3D(FetchEnv3D env) { Sphere3D s(pos, r=1inch); s.setMaterial(ColorMaterial3D(c)); return s; } }
initialize()
This method is called on construction. It is here that the candidateActive
argument is put on the object -- equivalent to putting via the 'args' parameter on construction. This behavioral argument is interpreted on Vessel.update()
to set the Vessel as active when 'owner."candidate"'is not null. When the tool animation is feature searching, it is known when we have found a feature by wether the candidate has been set. Since this is a CoreFieldPropDef, the vessel will iterpret the above to be true -- resulting in its graphics being called by default. The requirement here is that the tool animation is calling updateVessels(invalidate=true)
on its own Animation.update()
call. This will ensure that Vessel.update()
is being called.
appendPropDefs(..)
It is worth mentioning here that the PropDefs indicate which arguments can be passed in construction via the 'args: str->Object' parameter. In this example, to change the color of the feedback Vessel, on construction, one only needs to set args to include 'args=props{c=red}'. This will set the field 'c' on the Vessel to be the color red. If the field did not exist, it would be put into the anonymous data if able.
update(..) : bool
This method is called when the owner calls updateVessels()
. If the owner passes 'invalidate=true' in the call, in this case on animations, the resulting boolean will be taken into consideration. If any of the vessels return true from this method, the animation will be invalidated (see description above). This is also where the Vessel must ensure that it is in the corect state. In this case, it should only be active if the tool animation is feature searching and has a candidate. It should be invalidated if active has changed or if the position of the feature search feedback has changed.
updatePosition()
In this method, since the candidates are being found via SnapAlternative, the Vessel can recall the feature search snap alternative positional result from the last found alternative. It is this position that the feedback needs to exist.
graphics
The get3D()
and get2D()
calls are made when the animation encounters an active Vessel when gathering graphics upon invalidation. In this case, only a sphere and a circle are made to indicate feature search location.
Adding to the animation
// In animation -- default color FeatureSearchFeedbackVessel("featureSearch", owner=this, active=false, tags={#internal}); // Overridden color and non-transferable FeatureSearchFeedbackVessel("featureSearch", owner=this, active=false, tags={#internal}, args=props{c=red, nonTransferable=true});
The Vessel key is "featureSearch", it will place itself in the owners sequence of vessels, and it will start as inactive. The tag #internal
has been given to further identify and group internal vessels. For the overridden vessel creation, the color will be set to red and the Vessel will not be allowed to transfer owners.
Generic Vessels and Their Usage
VesselAroundPos -- Debugging
This Vessel only has a spherical get3D()
and circular get2D()
. It is intended to be a debug tool one can add to animations or snappers to display the pos()
of that core object. One only needs to add it to the object and make sure to update its position.
FeatureSearchFeedbackVessel
See above for implementation. On tool animations, this Vessel is intended to provide default feedback for feature searching.
VesselHighlight
This is a Vessel intended to live in Space. One can easily highlight ANY core object in space by utilizing this Vessel and the Visitor that is written in the same file. By manipulating some arguments, one can change color, material override or not, transparency, etc..
Look at the file for more examples, but here are just a few:
// Highlight activeSpace vessels and snappers in a ClassSubSet with a purple box highlightSpaceSnappersAndVessels(args=props{cs=ClassSubSet, highlight=purple}); // Highlight a single class of Snapper by material override with green and lots of transparency highlightSpaceSnappers(args=props{c=Class, highlight=green, override=true, transparency=0.7}); // Highlight a single class of Space Vessel by material override with given sequence highlightSpaceVessels(args=props{c=Class, highlight=blue, override=true}, vessels=mySeq); // Append additional Visitor for combined manipulations over one iteration // This allows us to visit the seqence once, but do many unrelated manipulations // This will highlight the space vessels in cs with a purple box, but will also visit with customVisitor highlightSpaceVessels(args=props{cs=ClassSubSet, highlight=purple, customVisitor=Visitor()});
OverrideMaterialVessel
Performing material override actions for feedback has become a norm. To do this easily, this is a Vessel tailored with the ability to perform this with ease. It has been extended for use with CoreToolAnimations for candidateChanged()
events, however, it is easily extended for use in many other situations as well. One alternative to doing this in a Vessel would be to write a CoreVisitor to do this (see highlight visitor).
Visitors
The Visitor pattern is very applicable to manipulations within the Vessel paradigm. However, there are times to not use this pattern, so make sure to know how this works. If one is unaware of this ideology, investigate the examples above (the highlight Visitor is a good one). It is good for temporary data structures and complex manipulations over many collections of objects. It can also help encapsulate operations -- leading to more readability, faster implementations, increased extendability, etc.. For vessels, the Visitor gives up control over operations. This means that the Vessel is in control -- always. There is one funnel for operation (visit()
and accept()
) that makes it very easy to control, debug, and extend. In general, one should keep as much code out of accept()
as possible. This is done by putting more specificity towards a Visitor (never a bad thing). Arguments given via prop data help with this quite a bit and it is much safer to utilize the prop data when creating dynamic systems (although somewhat harder to debug). At any point, one can also dump prop data (also via Visitor) to see what props exist.
Visitors are great for complex iteration that can be redirected/extended. However, it is sometimes best to just write a for loop for operations that are extremely local to ensure that it does not get bloated or lost by an object who opted out. With all of this dynamic capability, it is also important to recognize when it is appropriate and the opportunity cost of such design decisions. See the above Vessel protocol operations for more about the basics of the Visitor.
Comments
0 comments
Please sign in to leave a comment.