Core Connection to UI
Contents
Current State
This is a WORKING DRAFT. It is woefully incomplete and lacks appropriate organization.
Major TODOs:
- Complete clarification of facade vs adapter (facade the interface owned by UI, facadeimpls are part of the adapter layer that today joins core and UI)
- Complete implications on architecture and a full description of the proposal
Scope of the project
This discusses the design of a series of classes and layers acting as an interface between the core of PCGen and the UI. Due to the responsibilities of those layers, other subsystems are subsumed into the discussion, but only in as much as they are impacted by the layers between the core and UI. (Layers is currently, and intentionally, plural. It is possible that the best design will include both an output layer and then an adapter layer on top of that output to interface with the UI)
It should be recognized that it will take time to implement the results of this discussion and this will likely be phased in over time. However, the timing of the discussion is critical to understanding at least some key points of the eventual direction, so that systems which will depend on these changes can avoid creating problems in how they would be converted to this architecture.
This is a working proposal. The intent of this document is to provoke discussion of issues that are not currently considered or captured in this description in order to develop the best possible solution for interaction of various subsystems withing PCGen.
The (somewhat aging) principle
If we look at Architecture, the core is acting as the "character data store" and "event controller". The UI is shown in the light blue layer at the top. In between is the "Character Output" subsystem. So the general principle has always been that there is an output subsystem that is responsible for preparing output. This would include things such as taking the list of Languages on a PC and sorting that list based on the currently active language. Right now, the items in this layer are a mixed responsibility. Some are still handled in the core, some are handled in the adapter layer. It is hoped that all of these can be moved to a consistent location so that they are handled appropriately and isolated from either the UI or the core.
The intent would be that the output layer and the adapter layer would make up the "Character Output" section of the diagram.
One item missing in the above structure
The architecture as shown ignore the output sheet system. It really should be along-side the UI, also attaching to the "Character Output" subsystem.
The Immediate Design Question
The core is / will be event-based. The question is really how the UI best attaches that that event-based system and how the resulting changes flow through objects to reach the UI. This will guide the design of the "Character Output" layer as well as any adapter/facade layer between the core and the UI.
Implied Questions:
- Should the output sheets (Freemarker at this point) leverage classes like DomainFacade, so that they are sharing output characteristics with the UI, or are the Facades unique to the UI?
- Is the UI best built with an event-based response to the core, polling of the core, or some hybrid?
- Is the sort of items performed before or after conversion from Domain to DomainFacade? (since the sort is intended to be language-specific)
Terminology
Subsystems and code structures:
- The "core" refers to the classes that perform the logical analysis in order to take in a command and produce a response. Generally this is done with our facets, but there are still exceptions to that design.
- The "UI" refers to the graphical elements of the interface.
- "Output Sheets" refers to the subsystem used to produce our output sheets, including the ExportHandler as well as the output tokens.
- "Output" refers to methods of getting information to a human user. From the core's perspective this includes both "output sheets" as well as "UI".
- The "adapter layer" refers to the translation from core-awareness to UI-awareness. Currently this is implemented in the *FacadeImpl classes.
- The "output layer" is a layer of code that is potentially between the core and the facades (or the core and the adapter layer). This recognizes that there are some universal items for output that can be shared. However, it is not certain that this can be or replace the current adapter layer entirely, as there are still concepts we would have to decide how they are used or not used in output sheets (the facades in particular).
For purposes of this document, it is assumed that the adapter layer and the output layer are separate entities. That is not necessarily required (more discussion is necessary). The main tasks assigned to these layers:
- Output is responsible for sorting and other general purpose behaviors of the lists/objects
- Adapter is responsible for the translation between objects and objectFacades
Related to Flow of messages:
- A "Command" is when the UI sends a message to the core. Currently this is done as a direct method call, but is likely to transition at some future point to a design using the Command design pattern (hence the name)
- A "Response" is the set of changes to the PC that are made in response to a command. Note that there can be more than one response per command, as adding an object to a PC may have side effects of adding other objects (due to object grants in the data)
- "Events" notify parts of the core to execute the command then then perform any actions that are side effects of that addition.
- A "response event" is a specific form of event sent out of the core to another subsystem. Typically this will be the output system.
- An "output channel" is an abstraction of a specific response message from the core to a consumer, such as the output system. It can be thought of as the pathway for a generic response that is not tied to the classes within the core.
Abbreviations:
- PC: PlayerCharacter
- PCF: PlayerCharacterFacade
- PCFI: PlayerCharacterFacadeImpl
- PF: PartyFacade
- PFI: PartyFacadeImpl
- UI: User Interface
Life cycle of a Player Character change using the provided terms
In order to put the terms in a useful context, the following "life cycle" is provided:
- The scenario is that the user adds a Domain to a character (we'll use "Light"). We assume for simplicity that there are no other side effects.
- The UI is responsible for sending a message to the Facade layer that says something to the effect of "add DomainFacade Light".
- The Facade then converts that to a "command" for the core (now "add Domain Light"). Currently, the only difference between the initial message and the command is that the UI message refers to a DomainFacade and the command to the core must refer to a Domain.
- When the core receives the command, it eventually moves that to a call on the DomainFacet in the core that adds the Domain to the PlayerCharacter.
- DomainFacet then throws an event, which in this case is also something displayed in the UI (a selected Domain), so that event is more broadly a "response event".
- We will generally refer to this response flowing along the "Domain output channel" back to the Facade. The Facade can then process that response having impacted the Domain output channel and thus trigger an appropriate update of the UI. Currently, the Facade directly recognizes DomainFacet, but that is not ideal, so we do not yet have a fully developed domain output channel.
Background
Over the last few revisions, the core has been adapting to be event based. If an Ability grants a Language via AUTO:LANGUAGE, for example, when the Ability is added, it is "searched" for AUTO:LANGUAGE (among other things) and those languages are added to the Language list. This event-based system replaces one that was mostly query-based (pulling from the list of objects via an exhaustive search) and ensures that the added ability is only queried once for languages for AUTO:LANGUAGE.
The UI for 6.0 was built in a way that it does not depend on (thus does not recognize) a number of the key classes in the core - instead it relies upon a series of interfaces (Facades) that provide it the ability to get the information it requires on those objects (such as the TYPE of a skill). The result is that there is a layer of Facade implementations (FacadeImpl in the class names) that provide this adapter layer between the core and the UI.
(Would it be good to have a description of the layers in the UI here? Connor mentioned there are 4 layers, I just don't know what they are...)
Key Characteristics
The following are key characteristics of the strategic solution. Primary measurement of design goals is against these characteristics.
Isolation provided by the output layer & facades
One of the principle design considerations is to insulate the core from the UI (and vice versa). Therefore, the Adapter Layer should be making the effort to do that conversion, rather than creating dependencies back in the core. As part of this work, the pcgen.core.facade interfaces will be moved out of pcgen.core (into something like pcgen.output or pcgen.adapter - TBD). This also means that the concrete classes in pcgen.core that implement those interfaces will no longer do that, and there will need to be a FacadeImpl to adapt the core classes to the expectations of the UI. This will complete the primary isolation of those two subsystems.
One challenge is in sharing infrastructure in output while not destroying isolation between the UI and output sheets.
A specific example of this isolation can be shown with Race and RaceFacade. Today, there is no RaceFacadeImpl, Race actually implements RaceFacade. The side effect is that a number of UI-specific methods (such as getRaceType()) must be implemented by the core. Since we have already discussed the necessary connection from the UI to the game mode (and that we are willing to maintain that tight connection for now) we need to be careful about the now game-mode-specific UI imposing requirements back on the core (because it can easily make the core game-mode-specific, which is counter to the design principles of the core).
This leads to one of two possible solutions:
- Break the assumption that the "generic" methods like getListFor(ListKey) on CDOMObject (including and especially the xKey objects themselves) can safely be shared with the UI
- Make a separate RaceFacadeImpl that can convert getRaceType() [UI specific call] to getListFor(ListKey.RACE_TYPE) [core specific call]. In this case, it kind of implies that RaceFacadeImpl has-a Race. In order to avoid circular dependencies, it is advised that doing conversion at the full object level (e.g. Determine the appropriate RaceFacadeImpl for a given Race) is done by a 3rd party object.
Responsibility of the Adapter (Facade) Layer
As the adapter layer was built without a complete event-based core (simply based on necessity), there are a number of functions it performs that it should not perform, as it should merely be serving as an adapter layer and translating between the core and UI. This is perhaps best characterized with some of the functions is currently does around abilities. These should be subsumed into the core, into a more capable output layer, or into the UI, as appropriate. The facade should be as transparent as possible, acting solely as in the role of an Adapter (design pattern), translating from core-speak to UI-speak and back.
Required Conversions
In implementing this communication from the core to the UI, a number of key conversions and transformations need to take place. Enumerating these and then identifying good designs and appropriate owners is a significant part of developing the full architecture for interfacing the core to the UI
Sorting
The core is generally order-independent (there are a few exceptions, but most of those will be designed out over time). This generally means "Law" and "Fire" being selected as Domains have no order to their selection. So the core generally operates with things like HashMap that ignore order (and are designed for speed). The result is that the export of lists from the core does NOT guarantee order.
When items are displayed to the end user, they must be sorted. So one transformation that needs to take place before a list is output is for it to be sorted. Note that this universally applies to output (both output sheets and UI) and thus this is a reasonable candidate to be placed in a shared output layer.
Core Object to Facade
One item to note here is that once objects only represent rules information (that is once they are no longer cloned to be added to a PC), there is no reason for these relationships to be stored as 1/character. Rather they can be stored 1/rules load
We start with the following assumption of ownership:
- Domain (core)
- DomainFacade Interface (UI)
- DomainFacadeImpl [implements DomainFacade] (facade)
Given that ownership, there are a few implied characteristics:
- Domain does not Implement DomainInterface (this ensures the concepts of the UI do not get into the core, just as the core concept does not get into the UI)
- The Facade somehow has to translate from the Domain to the DomainFacade (and vice versa).
Possible Implementations:
- OneToOneMap (two-way HashMap)
Possible Owner:
- New layer between output layer and UI
- Placed in Output Layer if the Output Sheets (Freemarker) will also leverage the Facades
CharID to PlayerCharacterFacadeImpl
Events in the core, including response events, carry with them a CharID object representing the PlayerCharacter. As a response eventually needs to trigger a UI update, we eventually need to able to convert from a CharID to a PlayerCharacterFacade.
Assumptions:
- CharID is owned by the core, and should not make it past the Facade layer
- When a response event is received, it is not guaranteed to be for the CharID for the currently displayed PC. The Output Layer/UI must still act upon receiving the response event, in order to keep information in sync with the core. Therefore an effective system to distribute events to the appropriate objects underlying the PlayerCharacterFacade is required. (This eventually will update things like ListFacades)
- Characters continue to be available from CharacterManager (1/install)
- At any given time there is a small number of PCs that will ever be open. Designing for dozens (or even 10) is overkill
Alternatives:
- A Database (presumably CharacterManager unless there is good reason for that information to be duplicated). This involves items depending on CharacterManager and calling a specific method on CharacterManager (with the CharID as a parameter and which returns a PCF or PCFI)
- Hashtable lookup: This implements a HashMap (perhaps even a OneToOneMap) between CharID and PlayerCharacterFacade. This allows immediate conversion to the appropriate PlayerCharacterFacade.
- Brute Force List lookup: This simply takes the list of characters (already available on the interface of CharacterManager) and increments across them to return the proper PlayerCharacterFacade (or PCFI?)
- Event Passing. This involves the event handler passing the entire event into a system, and then having that class determine the appropriate PC. This makes the event handler only a pass-thru, rather than querying for the appropriate PCF and then calling a method on the PCF. Obviously for any given event passing system, there can be traditional events or consumable events (which is a chain of responsibility rather than a set of listeners)
- Registered Characters: This implements a traditional listener registration (the PCF would have to be registered with some as yet undefined class. If this duplicates the list of PCs in the CharacterManager, a good reason needs to exist to duplicate that information between two locations). Each listener would be provided the event, and can choose to act (or not) based upon that event.
Note that the significant difference between these options actually comes from where the core abstraction to UI abstraction is performed. In the case of a database, the event processing system would be aware of that processing, in the case of event passing, it would not (and the recipient of the event would do that processing)
Facet to UI element
As some point, the core knowledge (it's the Domain list from DomainFacet) needs to be converted to UI knowledge (it's the ListFacade that contains DomainFacades. The list is on the Domain Tab). Note that this implies a conversion from a Domain to a DomainFacade during this transition (discussed above).
One acute challenge is in keeping the amount of code from quickly getting out of control. The core and the UI both happily leverages generics in order to have single classes that are reused in multiple instances (for different object types). The risk is losing that ability in the output layer.
Options:
- Output Layer/Facade has core knowledge and UI knowledge. In this case, the output layer knows to talk to the DomainFacet (and register as a listener), and also knows to talk to the PlayerCharacterFacadeImpl and get access to the underlying ListFacade for Domains. This implies that the output layer becomes a high level layer that has knowledge of both the core and the UI. The result will likely be a number of classes tightly tied to the existing infrastructure, and is unlikely to be flexible to game systems outside d20.
- Output Layer/Facade has no core knowledge or UI knowledge. This inverts control to remove knowledge from the output layer. Rather than knowing where to register, the output layer is instructed by the core of the channels that it has the ability to export. This is with a simple name, perhaps just a String identifier like "DOMAIN". The same is true for the UI - it can query the output layer to instruct it to connect certain output channels. In this way, while the output layer has to have enough awareness to be able to connect the events from the core to the knowledge expected by the UI, it is doing it in a generic fashion (it knows no methods or instances in the core or UI). The result is a structure that is highly reusable across game modes (because game modes would just use different channel names).
Converting Core abstractions to concrete information
In addition to the core moving to an event-based system, it is moving more toward a system that is independent of the assumptions in d20.
Therefore, what was once a set of specific methods to get information (getLegs() on Race.java) is now abstracted (now is getInteger(IntegerKey.LEGS)). The result is a significant reduction in the assumptions in the core about what the data contains. This provides flexibility as well as a way to reduce duplicate code. As a result of this design in the core, the facade layer has taken on much of the responsibility of converting the core's "xKeys" to methods, so that the UI can directly access specific information about an object. (There are a few exceptions where the facade has reached back and re-added those methods to the core, and that should be changed so the core does not have methods imposed upon it by the facades). At some point, that abstracted information (IntegerKey.LEGS) needs to be converted to a concrete piece of information. The question is where this occurs, and impacts the adapter/facade design.
In general this can be solved with a similar system to channels, discussed above. We can define "characteristics" of objects that can be referred to with a specific name. These could be local variables, these could be integers, whatever is needed to be able to do appropriate output.
Core Response Event to UI update
In general, the two starting alternatives for communicating information from one place to another are messages (events) and polling.
Reflecting upon how data for RPGs works in reality, we quickly end up with a recognition that just about any command has the possibility to produce just about any response. (There are a few exceptions, but that is simply current data limitations, not theory of what some rules have asked to do). For example, a skill being ranked up may end up in a PRExxx being met for another Ability, with a long chain of events adding many things to the PC. The net observation here is that with any event, the impacts cannot be localized though an assumption by the UI.
Full Polling
Given that reflection, we look at one "extreme" alternative: "full polling". In this case, when the UI submitted a "command" to the core, it can't make an assumption. It therefore can wait for the core to complete its work, and then trigger an update of all UI elements to include new content. However, while it is possible that every field in the UI needs to be updated after a command, the reality is that such fan-out is rare. It is likely that a command will produce a number of responses, but only a small fraction of those that are possible. So polling is a conservative example, but produces a major "reset button" in the UI seems unreasonable (at the moment anyway) and thus this alternative is not fully fleshed out for consideration.
Full Event
An alternative would be to consider the other "extreme" alternative, "full event" based. This would mean that every event passing through the core (or really every event the UI might be interested in, which is a subset of all events) is sent to the facades. The facades would then convert that event into information relevant to the UI, so that the change could be displayed. The risk with this alternative (a risk which requires proof as to whether it is a significant risk) is that of an "event storm". Specifically, there are certain types of additions to a PC that can cause items to be added to and removed from the PC repeatedly until a stable state is reached. Without some form of insulation, the UI would see each of these add/remove events and attempt to update the UI. This would cause both a CPU intensive set of operations in the UI as well as glitching visible by the end user. This alternative is also not fully fleshed out for consideration at this time.
Net Events
One of the primary risks with the full event is an event storm is the issue of since items being removed and subsequently re-added to the PC, causing a "glitch". A "net events" system would avoid that risk. The design here is to queue up the changes, with a system keeping track of "net" changes. These "net" changes can then be passed on to the UI once the core work is complete. This results in a number of code items to be developed:
- A system to determine the "net" changes. Since there are multiple event types coming from the core, there will have to be multiple classes (each likely with many instances) in order to appropriately filter and process the net changes
- A system to trigger "dumping out" the net changes. Since this should occur after the core work is completed, we need to ensure that it both occurs at a controlled time, and is guaranteed to occur. (Note that this will be sensitive to core behavior. If there are multiple ways of calling into the core, from separate threads, then we end up not being able to establish the appropriate time to send an update message. This design choice is therefore particularly sensitive to the undo/redo related considerations discussed earlier.)
Consolidated Event
If we following the path of a consolidated event, we then note that the event must only contain 2 pieces of information: The CharID of the character that has been changed, and the name of the Output Channel. Note that if the name is something more generic, such as "DOMAIN" (just a String), we no longer have to have the Facade recognizing that the core is composed of Facets. Rather, it can just link up a known display element in the UI that is looking for the "DOMAIN" output channel with the response coming from the core containing that channel name. The advantage of that is that it keeps the Output layer generic to game mode. As long as the channel is defined by the core (presumably controlled by the data) and the UI (designed to match the data), then it should be okay.
This implies a need to have an error checking mechanism that ensures that any output channel requested by the UI actually exists in the core.
Additional Considerations & Design Principles
These are considerations in the design that influence the design as the primary design question is resolved.
Defer fixing to a game mode as long as possible
Another consideration to be balanced is to defer fixing behaviors to a game mode as long as possible.
Note that the data is definitely bound to a game mode. So anything defined specifically in the data is not an issue. For purposes of this discussion, anything stored in a CDOMObject with a xKey (e.g. IntegerKey) is also considered not an issue. While not strictly true (due to ties to an LST token), the number of LST tokens will decrease over time as more generic methods of setting information are provided. That can be held outside the scope of this discussion.
Note also that the UI is definitely bound to the game mode. This was discussed on the development mailing list, and until we get more experience at what the UI for a non-d20 game mode would need to be, it is considered unnecessary to have ability in the data to define details of the interface. The step agreed to so far is for the data to be able to identify whether tabs are used or not (but all characteristics of the tab - content, layout, etc. are handled entirely in the Java code).
Given those observations, one way to keep the adapter layer generic would be to eliminate it entirely. This would expose the generic methods from CDOMObject to the UI. This, unfortunately, creates a pretty tight binding between the design of the UI and the core, so that has a number of nasty side effects and is not pursued further in this section.
At this point is it useful to have a hypothetical discussion based on the "LEGS" characteristic of Race. This is easiest since it is simply an integer (and something like RaceType, which really is a separate method on RaceFacade, adds additional complexity to the examples that is unnecessary to demonstrate the concept of separation)
The starting point we assume is that RaceFacet has a getLegs() method [this is analogous to the getRaceType() method on RaceFacade today]. In this situation, it is less than ideal because the design of the UI's Facade (remember the UI owns the Facade Interface) has been imposed upon the core. (That is also a d20 requirement being imposed on the core).
The next step is to separate Race and RaceFacade, forcing the creation of a RaceFacadeImpl. In this case, RaceFacadeImpl can simply delegate back to Race (so RaceFacadeImpl has-a Race). This allows the Facade to contain the method desired by the UI, while removing knowledge of that interface from the core. This is considered the minimum to meet the isolation characteristics of this proposal.
However, that ties two systems (both the Adapter layer which has the FacadeImpl and the UI that has the Facade Interface) to the game mode. While we must concede that the UI layer has to be tied to a game mode, we do not necessarily need to do that for the Adapter layer.
In the future, there is another step that could be pursued. (This is not recommended for current consideration due to many external dependencies and a lot of work at this time). This would be to make the Adapter layer generic, but only makes sense if the Input tokens also become generic. Consider that IntegerKey.LEGS in the previous case might be used in 2 places: In the LegsToken (an LST Token) and in the RaceFacadeImpl. So the RaceFacadeImpl in the last scenario is actually tightly tied to the LST token, not the core itself (The core could easily delete the LEGS constant and make both of the other subsystems use IntegerKey.getConstant("LEGS")... which means the core completely loses ALL knowledge of LEGS (yea!). However, with certain enhancements we could lose the LST token (think local variables as defined in the Formula Parser Equip Vars Proposal). If LEGS were a local variable, then the RaceFacadeImpl would need to be fetching VariableKey.getConstant("LEGS") and then using that to call getVariable(x) on Race. Once that occurs, it is a fair question to ask why the Facade knows anything about legs, and whether the UI should simply ask for a Local Variable "LEGS", which would tightly tie the data directly to the UI, with the input system, the core, the output layer, and the adapter layer all not really realizing they are communicating about legs.
There is a theoretical next step of making the UI generic, but as discussed earlier that requires both more learning and well as significant development that is considered unnecessary at this time.
Undo/Redo and keeping the core isolated
One of our strategic plans is to include the ability to perform an undo/redo function within PCGen. This is awkward without precise control over the core, to ensure that multiple changes are not happening at the same time. Thus, part of this architecture should be a method of using a thread-isolating queue for other systems to provide commands to the core. Another queue should then isolate the thread and provide responses back to the output subsystem. Note the use of a Queue (and more specifically a BlockingQueue) constrains the design in some ways, in that there should be one BlockingQueue for providing commands and one BlockingQueue for providing output. This should be 1/install, not 1/character (as the core itself is not written to be thread safe if multiple characters are attempting to make changes).
Resulting Architecture
Under the assumption that the Output Sheets and UI share an output layer (to perform functions like sorting), there are a certain number of things we can directly derive from that structure. The first ties to how events are handled. If the sorted list is stored in the output layer, for use by both the UI and the Output Sheets, then that list must be thread-independent from the core (because it will be read frequently by the UI. Therefore, the communication of responses from the core is to the output layer... so if it is event based, then the output layer is consuming the events from the BlockingQueue. This may actually be more convenient anyway, as it provides a single, well structured entity to capture events passing through the BlockingQueue.
Distributing Response Events
As a response event exits the core, it can be passed into a BlockingQueue. Given the nature of such a queue, there should be only one consumer of the Queue. So this is 1/install. This will then need to choose a method of distributing the response event out to the UI for the appropriate Player Character and converting the output channel name to apply it to the appropriate ListFacade CharID Hashtable database (-complexity) Brute Force List (CharacterManager) Chain of Responsibility (+performance) PC Registration (-contract) output channel message to PCFI (with name as arg) use arg to look up method name in some way (hashtable to instances of an interface) reflection
Event Management
Effectively takes on event management (because it's really the sorted lists that bear the risk of event storms)
Responsibilities of the Facade
- Develop a command to be submitted to the core
- Return responses of the command to the UI (both for undo as well as display update)
- Interface to the output layer
Possible Event Flow in resulting architecture
At Load:
- Data load request by user, actual LST data is loaded
- In the future, specifies the tabs that are to be displayed
- In the future, controls what components in core are active
- Core registers with OutputLayerChannelDatabase what responses will be available to a consumer
- UI registers demands for responses with OutputLayerChannelDatabase (This can - at most - link the tab to the channel name - a specific ListFacade doesn't exist until a PC is instantiated)
- OutputLayerChannelDatabase reports any inconsistencies in channel requests as data load issue
- Data load declared complete by RulesDataStore
At character creation:
- Need to instantiate all the ListFacade objects (NOT like it is today - right now CFI is basically too complicated to be debugged. Rather, the OLCD should be given the CFI and should load into it a series of xFacade objects with the output channel name. - this works because the channel name is known by the tab and by the core, so at that point the CFI is basically just a map of name->object and is rather easily testable!)
At runtime:
- UI receives a request from a user, such as adding a DomainFacade to a character
- UI sends a message to the adapter layer, indicating the action and the target (e.g. "grant" "DomainFacade:Light") these are not literally strings, and may be in a form like addDomain(DomainFacade df) [though they MAY BE strings - as that is much more generic and avoids d20 assumptions]
- Adapter layer converts the message to a core-compatible command (e.g. "grant" "Domain:Light")
- Adapter layer submits the command to (the core?)
- Command is transferred to the core thread via a BlockingQueue
- (the core?) consumes from the BlocakingQueue and executes the command
- Events flow through the core, triggering responses
- Responses are captured by a ResponseAggregator
- ResponseAggregator provides the complete set of changes in 2 forms: UndoableEdit and CombinedResponse.
- UndoableEdit for the undo/redo system
- CombinedResponse for the Output system
- Consider: are these the same thing?
- The CombinedResponse is placed into the BlockingQueue for the Output System
- The OutputSystem takes (consumes) the CombinedResponse
- Question: does this then need to pass this CombinedResponse to the UI processing thread? so blah.runLater?
- The OutputSystem decomposes into a series of individual actions (e.g. "CharID:33fd" "added" "Channel:DOMAIN", "Domain:Light")
- Order of operations: This decomposition order before CoR indicates the CoR will be walked many times. Should the CoR be called first and then have the PCF(I) pass off the CombinedResponse to a decomposer for the decomposer to call back into the PCF(I) ... so that we walk the CoR once yet keep decomposition out of the PCF(I)
- Individual actions provided to the PCF(I) Chain of Responsibility
- Each PCF(I) that does not match the CharID ignores the event
- When a PCF(I) recognizes the CharID, it triggers processing of the event
- PCFI does a lookup of the output channel to find the xFacade for that and then performs the appropriate action for that type of facade
- PCF(I) consumes the event, so other PCF(I)s are not notified of the event
- Output System sees the action was consumed and iterates to the next individual action, if appropriate
- Warning produced if an action is not consumed (indicates core was asked to work on a PC that the PCF(I) CoR does not recognize
- When all individual actions are complete, Output System blocks on BlockingQueue in order to find another CombinedResponse