DynamoCore Modularization

## The Big Idea
The architecture of Dynamo was previously a tangled mess of dependencies. This PR refactors `DynamoCore` into a modular, layered architecture, where the boundaries of the various components of the application are clearly defined.

Or, in a nutshell, we are no longer accessing `DynamoModel` from remote parts of the application. This means that:
  1. In order to test parts of the application, we don't need to create an entire instance of the application.
  2. We can safely assume that different parts of the application aren't affecting other remote parts of the application unless there is a direct dependency between the two components.

## What's been changed?
* XML parsing
  - The logic for loading whole Workspaces, Nodes, Connectors, and Notes are now all located in a single place: `NodeGraph`
  - Loading .dyn and .dyf files now leverage this new API, previously we had duplicate (and unsynchronized) code for loading both.
* Node instantiation
  - `NodeFactory` has been overhauled; previously we were doing weird string parsing to determine how to create new nodes. Nothing was consistent, the API was cumbersome to use, and there was a ton of duplicate code.
  - All node instantiation is now processed through `NodeFactory`. Different types of nodes register `INodeLoader`  instances with the `NodeFactory`. For special kinds of nodes (Custom Nodes and Zero Touch Nodes), there are special `INodeLoaders` that handle the necessary extra logic for creating instances. This means that `NodeFactory` no longer needs to know about the `Function` and `DSFunction` classes.
  - All external implementations of Nodes which extend `NodeModel` are now instantiated using a pre-compiled `Expression`, as opposed to relying on an `Activator`. This means that instantiating large numbers of new nodes should be significantly faster (copy/paste, undo/redo, loading files).
* Custom Node Infrastructure
  - `CustomNodeManager` has become the primary interface for dealing with Custom Nodes.
    + It keeps track of `CustomNodeWorkspaces`, instead of just `CustomNodeDefinitions`.
  - `CustomNodeWorkspace` now emits events when the `CustomNodeDefinition` and `CustomNodeInfo` has changed.
    + This makes sense because the state of a `CustomNodeWorkspace` defines what the `CustomNodeDefinition` and `CustomNodeInfo` are.
    + These events are watched by `CustomNodeManager`, which then forwards them to `DynamoModel`, which then propagates them to the `EngineController`, Search Library, and Custom Node Instances.
  - Custom Node (`Function`) Instances are now created from `CustomNodeManager`, in order to ensure that they are kept in sync with their `CustomNodeDefinition`.
* Evaluation
  - Since `HomeWorkspaceModels` are the only kind of Workspaces that can be evaluated, `EngineController` has been moved to `HomeWorkspaceModel`
    + ~~Consequentially, all logic originally on `DynamoModel` associated with `EngineController` has now been moved to `HomeWorkspaceModel`~~ Unfortunately, `CodeBlockNodeModels` have a dependency on `EngineController` for Auto-complete. Further work can be done to decouple Auto-complete from `EngineController`, which would allow for total removal of `EngineController` from `DynamoModel`.
    + This sets the stage for eventually having multiple `HomeWorkspaceModels` active at once.
* Search
  - Search has been re-implemented to be as general as possible.
  - Search has been refactored to support the new method of instantiating Nodes.
  - The actual search method has been separated from the categorization infrastructure.
  - Categorization of Search Elements has been greatly generalized and simplified.

### Reviewers:
* [ ] @lukechurch 
* [ ] @ikeough 
* [ ] @Benglin 
* [ ] @pboyer 
This (now-removed) method exemplifies the change to `NodeFactory`. Originally, this method would take a "name" `string`, which could either be a zero-touch function name, the typename of a `NodeModel` subclass, a nickname for a `NodeModel` subclass, or a guid for a custom node instance.

Now, this has been simplified and generalized. All lookup is performed by the typename or nickname of the `NodeModel` subclass (if the typename isn't found, try searching the nicknames instead). Once the type is found, locate an `INodeLoader` for the type. The loader is then used to deserialize the node from XML.

For cases where we're not deserializing: if we know the exact kind of node we're creating, just construct it normally via `new`. For cases where we have a typename or nickname, but we're not actually deserializing a node, `NodeFactory` also registers and keeps track of `INodeFactories` for types that can be used to instantiate a new node, but not perform any deserialization.