Core Connection to UI

From PCGen Wiki
Revision as of 16:20, 25 May 2014 by Tom Parker (talk | contribs) (Core Object to Facade)
Jump to: navigation, search


Current State

This is a WORKING DRAFT. It is woefully incomplete and lacks appropriate organization.

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 responsibilites 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.

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 facade. 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.

One item missing in the above structure

The architectue 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.


Terminology

For purposes of this discussion, when the UI makes a method call to the core, we will call this a "command". The set of changes to the PC can then be thought of as the "response" to that command. The "events" notify other parts of the system (either inside or outside the core) of elements of the "response" (there can be many reponses per command since objects in PCGen can grant other objects)

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. The "facades" refers to the classes acting as an adapter between the core and the UI "Ouput 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".

An "output channel" is an abstraction of a specific response message from the core to a consumer.

Lifecycle of a Player Character change using the provided terms

In order to put the terms in a useful context, the following "lifecycle" 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 Domain Light". The Facade then converts that to a "command" for the core (also "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. That Facet then throws an event, which in this case is also something displayed in the UI, so that event is more broadly a "response event". Currently, the Facade directly recognizes DomainFacet, but that is not ideal. So as we architect this solution, we will more 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.

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...)

Considerations & Design Principles

These are considerations in the design that influence the design as the primary design question is resolved.

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 Output 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). 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.


Implied Isolation...

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. This is perhaps best characterized with some of the functions is currently does around abilities.


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. For example, having an item call getInteger(IntegerKey.LEGS) is generic to any game mode (since the key would not exist in a game mode that wasn't worried about legs). Once it becomes a method called getLegs(), then it is fixed to that game mode and the class is no longer reusable across a wide variety of game modes. While this is not a hard requirement to avoid binding, there should be a balance, and deferring the "fixing" to a game mode as long as possible would seem desirable.

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).


Required Conversions

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] (Output layer)

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 Output Layer somehow has to translate from the Domain to the DomainFacade (and vice versa)

Possible Implementations:

  • OneToOneMap (two-way HashMap)

CharID to PlayerCharacterFacadeImpl

Facet to UI element

Through Output Channel

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.

Note: Can be solved with Output Channel

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

For a consolidated




Resulting Architecture

Shared Output Layer

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.

Contents of consolidated response events

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 Ouptut 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.

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 CFI (with name as arg) use arg to look up method name in some way (hashtable to instances of an interface) reflection


Sorting

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