[ARCH] CHOOSE and other Selections
- There are a whole host of issues around choices that need to be considered. First, there are different types of choices that can be made when an object is added to a PlayerCharacter. Some trigger both a choice and application when the choice is taken (e.g. ADD:FEAT), while others drive a choice that is used by other tokens (e.g. CHOOSE).
This results in a few different behaviors:
(1) Applying Selections (e.g. ADD:FEAT)
(2) Defining a selection (e.g. CHOOSE)
(3) Acting on a selection (e.g. %LIST as an argument to CSKILL)
Looking at this list, there are actually 2 behaviors:
(1) Force a selection.
(2) Apply results of the selection.
However, those behaviors occur in 2 scenarios:
(a) Selection and application are from the same token
(b) Selection is from CHOOSE and application is from a different token.
This leaves us with a few challenges, the main one being how to have an actor respond to a choice either when (a, above) the choice can be directly fed to the actor or (b, above) when the choice has to be stored in a known location separate from the actor.
If choices were limited to one per object, and each object could only be taken once, there would be at least a handful of methods of storing a choice from the CHOOSE token. However, with the ability to select more than one item (SELECT:) and take an ability more than once (MULT:YES), not to mention take the same choice more than once (STACK:YES), this second case becomes non-trivial.
Before we step into use cases, one small tangent to the graph structure. The graph contains only items granted to the PlayerCharacter. It does not contain nodes for "theoretical information" (such as available starting languages). The reason for this is one of simplicity. If theoretical information is stored, then it requries a graph traversal (expensive in time) in order to establish if a character has a certain item (such as the Dodge Feat). If the graph only contains items granted to the PC, then the query is as quick as a contains call, which is fast (O(1) for those inclined to big-O notation) for a HashMap (what underlies the current graph implementation). This is one of a few reasons, but suffice it to say there is a significant performance consideration in that design. Not holding theoretical information (in a node anyway) is important for how the result of a CHOOSE is stored.
The reason it is important is that it rules out storing the choices as the target of a theoretical edge. If we made the CHOOSE its own Node in the graph, and the selection of choices exist as an edges from the chooser to the selected choice, it's a problem. This violates the principle above that "theoretical information" is not stored as a node. The selected choice is theoretical, because a CHOOSE doesn't necessarily grant. It may be selection of a favored enemy Race, it shouldn't convert the PC to that Race!
We also get into a problem if we do too much cloning. We're trying to get away from that, but it's a consideration: Clone each CHOOSE item and store the relevant information either in that item or in some way related to that item. This is also a problem. Assume you have a MULT:YES Feat, call it Foo. It has a CHOOSE of some sort, and is NUMCHOICES=3. Assume for now SELECT:1. Meaning, you get one choice each time you take the feat, but can never have more than 3 choices. If you clone the chooser (and the underlying Feat in order to get MULT:YES), how are you enforcing the NUMCHOICES limit across multiple objects? Today, this requires a significant song & dance of the code (especially since we can get into situations with different Ability Categories being used to apply the same Ability). We need a better solution.
Since we've already asserted we are storing information in the edges of the graph, let's assume for a moment that is where the information is stored. Let's continue the assumption that the chooser is its own node in the graph. Can we store the choice on the edge leading from the object containing the choice (Feat Foo) to the Choice itself (e.g. CHOOSE:PCSTAT). Answer: No. The goal of CDOM is to be able to trace the source of a selection, and here is the counter case to this structure:
Template A grants Ability AA, Template B grants Ability AA. Assume AA is MULT:YES with CHOOSE:PCSTAT.
- Add A, select INT
Edge leading from AA to chooser contains "INT"
- Add B, select STR
Edge leading from AA to chooser contains a list of "INT" and "STR"
Some of you have probably caught on why this is a problem.
- Remove B, destroys ?? selection
Which item from the list is removed?
Note that the link from addition of "B" having selection "STR" has been lost. For anyone thinking "STR is second! select that one since B was added second: The graph has no concept if A or B was added first, and also order is fragile and hard to check given things like SELECT: (especially if SELECT is a variable that changes!)
Thus the information needs to be stored in the edge from the source object granting the MULT:YES object to the MULT:YES object.
- Add A, select INT
Edge leading from A to AA contains "INT"
- Add B, select STR
Edge leading from B to AA contains "STR"
- Remove B, destroys STR selection
Just remove the edges attached to B
This actually demonstrates that the chooser doesn't even need to be its own node:
(Note that the chooser could be its own node in order to identify to PCGen where selections need to be made in a PlayerCharacter. Having a separate node could make such identification faster, and is in consideration for the long term solution)
Anyway, we know where the result of a CHOOSE is stored (on the incoming edges to the item containing the CHOOSE). Thus from the 2 behaviors in 2 scenarios, we end up with 4 classes (give or take):
(1a) Force a selection from a token where the selection will be applied. This uses the TransitionChoice (or PersistentTransitionChoice) class. (The only difference is in how the application takes place and whether the result is stored in the PlayerCharacter (PCG) file ... persistent items are stored in today's PCG, others are not - eventually in CDOM all this information will be stored). Some more information about TransitionChoice objects can be found here: http://www.pcgen-test.org/wiki/index.php?title=User_Selection_Walkthrough
(2a) Apply results of a selection from a token where the selection took place. This uses the concept of a ChoiceActor (or PersistentChoiceActor in the case of a PersistentTransitionChoice). The ChoiceActor (typically the load token itself, in order to isolate that behavior out of the core) has the responsibility to apply the results of the choice to the PlayerCharacter. In the case of a PersistentChoiceActor, it also has the ability to save and restore the item from a persistent state (the PCG file). More information on ChoiceActors is available here: http://www.pcgen-test.org/wiki/index.php?title=CDOM_Choice_Actors_Concept_Document
(1b) Force a selection from the CHOOSE token. This uses the chooser system in pcgen.base.chooser; in the case of "new" CHOOSE tokens, it relies on the CDOMChoiceManager class. You can see the status of conversion to the new CHOOSE tokens here: http://www.pcgen-test.org/wiki/index.php?title=Architecture_Changes_5.17#CHOOSE_Rebuild This "knows" to store the information on the incoming edge to the object with the CHOOSE
(2b) Act based on a selection from a CHOOSE token. This knows to look on all incoming edges of the object with the CHOOSE for the information. The classes that pull this information and perform the necessary action are ChooseResultActors. These are close relatives to ChoiceActors, but are specific to the CHOOSE token. Note that while there can be only one ChoiceActor for a TransitionChoice, there can be many ChooseResultActors for a CHOOSE token (could have a BONUS, an SAB, and control the contents of a PRExxx token)
Eventually, it would be nice to reduce this to only one system of selections, but that requries significant work. Specifically it requires supporting more than one CHOOSE token per object (because there is no limit to the number of tokens that have a TransitionChoice, whereas there is currently a limit of only one CHOOSE).
The nice thing about this common model is that it reduces the number of unique steps requried to process a PlayerCharacter. If you go looking in the tokens, you can see that CHOOSE:LANGAUTO actually inserts itself into the ADD system, using that same infrastructure to avoid unique code in the core (while using a workaround in the token to prevent ADD:.CLEAR from clearing a CHOOSE:LANGAUTO token)
- Hi Tom,
> <snip material>So we're effectively evaluating the Choose outside of the PC Graph and apply the result to the Graph?
> If you go looking in the tokens, you can see that CHOOSE:LANGAUTOI'll go take a look at that and post back here if I have any questions.
> actually inserts itself into the ADD system, using that same
> infrastructure to avoid unique code in the core (while using a
> workaround in the token to prevent ADD:.CLEAR from clearing a
> CHOOSE:LANGAUTO token)
- --- In firstname.lastname@example.org, "Martijn Verburg" <martijnverburg@...> wrote:
> So we're effectively evaluating the Choose outside of the PC Graph and apply the result to the Graph?Assuming you mean "Choose" to be listerally CHOOSE and not include ADD (or CHOOSE:LANGAUTO), and you mean "apply the result" to be "places the choice made into the edge coming into the object with the CHOOSE", then yes. If not, we need to figure out the disconnect between what I mean and what you're seeing I wrote!
> > If you go looking in the tokens, you can see that CHOOSE:LANGAUTOJust remember CHOOSE:LANGAUTO is an exception and not a normal CHOOSE token.
> > actually inserts itself into the ADD system, using that same
> > infrastructure to avoid unique code in the core (while using a
> > workaround in the token to prevent ADD:.CLEAR from clearing a
> > CHOOSE:LANGAUTO token)
> I'll go take a look at that and post back here if I have any questions.
- Hi Tom,
> > So we're effectively evaluating the Choose outside of the PCYes we're on the same page (although I know I'll need to go over this material again in the future) :).
> > Graph and apply the result to the Graph?
> Assuming you mean "Choose" to be listerally CHOOSE and not include
> ADD (or CHOOSE:LANGAUTO), and you mean "apply the result" to be
> "places the choice made into the edge coming into the object with
> the CHOOSE", then yes. If not, we need to figure out the
> disconnect between what I mean and what you're seeing I wrote!
> > I'll go take a look at that and post back here if I have anyNoted - K
> > questions.
> Just remember CHOOSE:LANGAUTO is an exception and not a normal
> CHOOSE token.