Skip to content

Generator Architecture

samgannawayA edited this page Aug 5, 2024 · 9 revisions

Status

  • [08/05/24] Updating menu path: "File > Generate" is now "File > Automate > Generator Plugins".
  • [07/20/13] Third draft, edited by Joel Brandt after architecture review with Russell, Seetha, and T-bone. Major change is removal of any tasks related to "unsafe times" to use Generator. Also clarified the "node initialized" events.
  • [07/16/13] Second draft, edited by Joel Brandt after a few meetings with Seetha and a review by Tom McRae
  • [07/11/13] First draft, written by Dennis Kehrig and Joel Brandt, as a result of a meeting between Tim Riot, Dennis Kehrig, and Joel Brandt.

Overview

The Generator core is a Node.js library that communicates with Photoshop over KVLR and exposes an event-based API to Generator plug-ins. Photoshop will ship with a built-in Generator core and one (or more) plug-ins, as well as with the necessary Node runtime and dependencies to launch Generator.

The built-in Generator will be disabled by default. There are a number of ways that it can become enabled, detailed below.

The built-in Generator core will run in a Node process that is exec'ed by Photoshop, and that communicates over pipes (anonymous pipes on Mac, named pipes on Windows). The FDs/names for these pipes will be passed on the command line.

Multiple generators

Besides the built-in, pipe-based Generator, multiple other Generators can connect via TCP/IP at the same time without stopping other existing Generator processes from running. This is is intended to support two workflows:

  1. third-party development of new plug-ins
  2. running of plug-ins from the command line as part of "build processes" for Web designers and developers.

Allowing multiple Generators is necessary because some plug-ins may require a newer version of Generator (or the Node runtime) to run. At the same time, built-in plug-ins may be incompatible with the new version of Generator. It would be confusing to the user if built-in features went away because of an external event, and it would be frustrating to the user if they could not use both built-in and third-party features at the same time. Moreover, we cannot prevent users from creating multiple external Generator processes (because KVLR supports multiple connections). So, it is beneficial to support it explicitly.

Multiple Generators are allowed to be connected at the same time. Connecting a new Generator does not disconnect (or affect) any other Generator connections. Each Generator connection has its own section in the "File > Automate > Generator Plugins" menu (as described below).

Plug-Ins

Each Generator plug-in will be in its own folder, and that folder must be a valid node module (i.e. the folder must have a package.json file that defines a "main" javascript file). Optionally, the package.json file can define what versions of Generator it is compatible with using the "engines" property with the "generator-core" sub-property:

{
  "name" : "generator-my-awesome-plugin",
  "main" : "main.js",
  "engines" : {
    "generator-core" : ">=0.1.0"
  }
}

Plug-in locations

For Photoshop to automatically discover plug-ins, they must be placed in one of three locations. See the Home Page for the list of directories.

Plug-ins that ship with photoshop will contain a generator.jsx file that is read by PS at PS startup. This will be used to build the "Generator Plugins" menu for built-in features when Generator has not yet been enabled (and isn't running). This is described in more detail in the "menu" section below.

Generator connection, initialization, and shutdown

The built-in generator will communicate over pipes (where the FDs/names of those pipes are passed on the command line). All other generator processes will communicate over TCP sockets (so the user must enable remote connections). Thus, all Generator processes will be in 1:1 correspondence with a KVLR connection (either pipe-based or socket-based).

After a Generator process starts up, it will send a command to Photoshop to say that it is fully initialized. This command is sent after ALL initialization (including menu setup) is done, so that Photoshop knows Generator is fully ready to handle events. Decision: We will use the "nodeConnection" event for this. It is okay if PS sends some events before receiving this. (E.g. Generator will register for menu events before sending this, and it is okay if PS sends those menu events.) (Open Question: Is this "nodeConnection" or something new? Shouldn't "networkSubscribeEvent" indicate readiness to receive events? )

The "nodeConnection" command will initially be a "no-op" on the PS side, but we might use it in the future to know if/when the built-in Generator process thinks it's fully working. One future use: If PS doesn't get this in some amount of time (e.g. 5 seconds), it can then display an error message to the user saying something is broken. Open Question: Is this something we should implement for v1.0? Joel votes yes.

For the built-in Generator process, any menu events for the "dummy" menu items (described below) will be buffered until the "fully initialized" message is sent. At this time, PS will send the menu events to Generator.

When Photoshop quits, all registered Generator KVLR connections should be sent a "shutdown" event, and then the KVLR connection should be closed (pipe end or socket close). (Open Question: What is this event? And how do you sign up for it, if needed? ). This command should be sent as soon as possible once it is known that Photoshop is exiting. Finally, Photoshop should kill the built-in Generator process if it does not exit normally. (Open Question: Ideally, PS would wait for some timeout before killing the process -- I don't know how hard this is to implement.)

Enabling/disabling built-in Generator

The state of the built-in Generator after shipping Photoshop is considered "undefined" (meaning that the user has not explicitly enabled or disabled Generator). In the "undefined" state, Generator is not running. Other possible states are "disabled" and "enabled". Photoshop will only start the built-in Generator automatically in the "enabled" state. The state is persistent across Photoshop restarts.

There are three ways to enable the built-in Generator:

  1. When Generator is in the "undefined" state, if the user puts anything into the folder for third-party Generator plugins and (re)starts Photoshop, Generator will be automatically enabled.

  2. Checking one of the menu items provided by built-in Generator plugins (if Generator is in state "undefined")

  3. Checking "Enable Generator" in the Plug-Ins preferences panel and clicking OK

(Open Question: What about launching upon open of a document with generator meta data?) Decision: If the built-in Generator is disabled (or in the undefined state), the existence of Generator metadata in an opened document does not enable Generator.

When Generator gets enabled, Photoshop launches the built-in Generator. When Photoshop launches and Generator is enabled, the built-in Generator is launched as well.

When disabling the built-in Generator, the built-in Generator needs to be terminated. Photoshop should first send a "shutdown" event to Generator (the same event as mentioned in the "shutdown" section above), and wait at least (some timeout) seconds before killing the process if Generator fails to exit on its own. Finally, the "built-in" section of the "File > Automate > Generator Plugins" menu should be cleared.

Note: The enabled/disabled state of the built-in Generator has no bearing on whether external Generator processes can connect over TCP. Disabling the built-in Generator does not close TCP-based Generators. TCP-based Generator processes should be able to add menu items to (their section of) the "Generator Plugins" menu at any time. All that is required for a TCP-based Generator to work is for remote connections to be enabled.

Menus

Every Generator process that is connected to Photoshop will get its own section in the "Generator Plugins" menu (with the sections separated by a menu separator bar). In the typical case, there will only be one section (for the built-in Generator) and possibly one section for a Generator that was launched from the command line.

Menu sections will have a 1:1 mapping with KVLR connections (and Generator processes). Whenever a request to add a menu item comes over a KVLR connection, that menu item will be added to the appropriate section. Whenever a menu is picked by the user, that event will ONLY be sent to the matchking KVLR connetion. Finally, when PS observes that a KVLR connection has closed, PS should remove the menu section for that connection. (It is likely that developers will crash TCP-connected Generators during the development process. So, it is ideal if PS handles the cleanup of menus whenever it sees a connection close rather than relying on Generator to remove the menus during its normal shutdown.)

The menu section for the built-in generator will be special in a number of ways:

  1. It will always be the first menu section.

  2. When the built-in generator is in its "undefined" state (i.e. it has never been used, as described above), the "File > Automate > Generator Plugins" menu will be populated with a "dummy" menu. In this case, Photoshop will use JSX files placed in the Presets/Scripts directory (which will ONLY exist in the plugins we ship by default) to create menu entries for the built-in features. (Presets/Scripts/generate.jsx is an example.) When one of these dummy menu items is chosen, then the built-in generator will be enabled (and the checkbox checked in preferences), Generator will be started, and the built-in generator process will send the necessary commands to re-create (replace) the "dummy" menu.

  3. When a "dummy" menu item is chosen, Photoshop will cache the menu events until the built-in Generator launched, initialized, and has replaced its menu items. Generator will send an "initialized" command to PS when it is ready. At that point, the menu events will be sent to that Generator process (KVLR connection).

  4. If the user explicitly disables generator in the preferences (after it has been enabled by any means), then the dummy menus will not be created (even on PS re-launch). Instead, the "Generator Plugins" menu will be disabled. Execution of ExtendScript code that would enable generator in the "undefined" state will still enable Generator if Generator is in the "disabled" state.

Open question: How do we know when PS can stop caching menu events if the menu event subscription never happens? Can we flush them at some point? (e.g. the "generator-is-ready" command from above in the init section). Will there be a timeout?

When the built-in Generator is enabled, then Generator will handle the creation of all menus (for both built-in and third-party-installed plugins). Scanning of the generate.jsx files will not be done.

Note: Menus for third-party plugins will NOT be displayed if the user has explicitly disabled Generator in the preferences. This is by design.

Photoshop does not check or uncheck any menu items by itself. If the user selects an item in the "File > Automate > Generator Plugins" menu, an event is sent to the Generator instance that created the menu item, and the corresponding Generator plugin is responsible for checking/unchecking it. (Along similar lines, plugins may disable or enable menu items in their section at any time.)

Potentially bad interaction with other PS features

Decision: KVLR currently batches all events during "actions" inside Photoshop. So, this should be the way that "bad times to use Generator" are mitigated. We might have bugs around this, but we are not going to implement any "GeneratorSafe"/"GeneratorUnsafe" events at this time.

Since Photoshop has no way of knowing when Generator plug-ins are done handling any event or state change, they should not perform any actions during batch processes (and possibly in other situations). For this to work, Photoshop needs to send an event to running Generator instances before and after such times. For example, we could have a "GeneratorUnsafe" and "GeneratorSafe" KVLR event.

Open question: What are all of the situations where we need to send GeneratorUnsafe/GeneratorSafe events? Batch processing is one.

During such times, the Generator core will (by default) 1. stop forwarding events from the core to any plug-ins and 2. will return errors to any plug-ins that request to send commands to Photoshop. Plug-ins will be notified about the suspension, allowing them to stop ongoing operations. Afterwards, they will be told to again reset their state. This is necessary since batch operations may have modified the document in the mean time. It will be possible for plugins to explicitly request that they receive events and are able to send commands during "GeneratorUnsafe" times. (For example, this might be useful to create a generator plugin that simply logs everything that happens in Photoshop.)

Photoshop stores up events until the current PS event/command is done processing. That currently includes batch actions (actions are a single command). This is (partly) a performance issue, we don't want to be building/diffing for the event messages at every transient change to the document. We could modify this somewhat (for example, sending events after each action step), but we need to limit the time PS processes event somehow.

Joel says: I think the batching described above is fine (we don't need to change anything). However, we do need to figure out how to add the "GeneratorUnsafe/GeneratorSafe" events.

Localization

The Generator core will provide an API for accessing Photoshop's locale. It will be up to plug-in developers to decide if/how they handle localization for both menus they create and any other UI.

Any plugin we ship will have its generator.jsx checked into perforce and have localizable strings in $$$/ZString format. They are then picked up by an automated scan of the perforce source tree, and placed in a list for our translators and they build dictionary files we ship with Photoshop. From the above, third party plugins don't use generator.jsx. Their menus will be built by the plugin, and localization happens in whatever fashion the plugin chooses.

Open question: How will we insure that the localized text for the dummy menu matches the text that Generator adds at startup. The shipping plugi-ins will need access to the same localized strings that are checked in for the generator.jsx file.

Location of Node executable, convert executable and core generator code

The node executable should be renamed to Photoshop-node on Mac and Photoshop-node.exe on Windows, and placed in the same folder as the Photoshop executable. The convert executable should also be in this folder. Generator contains a KVLR command that retrieves the path of the Photoshop executable, so it will use this to find the location of the convert executable.

The generator-core code can go any reasonable place. It might make sense for it NOT to be in the Plug-ins folder. (Maybe just in the "Required" folder in the mac bundle?). It is probably a good idea to not put the built-in plugins INSIDE the generator-core folder. This will provide a cleaner separation for node dependencies.

Open Question: Where should the generator-core code live in the PS tree?