In my module I have a manager-style object in the Gateway scope to which we can add typed EventListeners. This works great in Java, but we have a use-case that would benefit from being able to add an event listener for a specific project (that is very specific to a customer, and so we'd like to not include anything related to it in the module itself).
We thought perhaps we could add an event listener with Python in the Gateway Startup Script, but quickly found that doing so was a little convoluted, to say the least. In the end, I wrote a wrapper class for the event that handles clean up and what-not, and it appears to "work", but unfortunately it doesn't seem to have access to most of the script engine tools we'd need, like the system namespace. So it's not good for much beyond throwing text in the wrapper log.
I was wondering whether there was a good method to add a Gateway-scoped event handler where a customer using the module could add in a custom script -- similar to the web dev module's handlers.
Okay, I see how we could leverage the message handler for this, but it feels a little dirty since we'd have to know the project name and message handler name for a given implementation, which means we'd have to put that in our app configuration or let it fall into the "secret magic wire up" category.
Let's say we wanted to go the less simple, more flexible route, and define gateway scoped event handlers in the designer the same way the web dev module allows for scripting of HTTP method handlers. Are those elements available in the SDK? And if so, are there resources we can review that would help us understand how to interact with the right classes in the SDK?
The common resource package; you'll want to define, at minimum, your own ResourceType in a common scope; you'll need it for both the designer and gateway.
Your actual editing workspace can be whatever Swing UI you dream up. If you want to allow easy editing of multiple resources in the same workspace, you probably want to extend TabbedResourceWorkspace
For the best script editing experience, you'll want to be compiling to an SDK version new enough to use com.inductiveautomation.ignition.designer.gui.tools.PythonTextArea or com.inductiveautomation.ignition.designer.gui.tools.ExtensionFunctionPanel, which unfortunately don't show up in our javadocs, but are still publicly accessible in the SDK.
For a practical, but not particularly fleshed out example of all this designer side scaffolding, you can take a look at this module: GitHub - paul-griffith/markdown-resources: Example of creating a custom project resource type, with editor, in Ignition
Now that you're all set on the designer and you're creating project resources of your custom type, you have to decide what to do with them on the gateway.
Probably the best approach would be to create your own ProjectLifecycleFactory, which will do the necessary work to automatically create new ProjectLifecycle instances for you as your projects change. Within the lifecycle you can react to individual resource changes.
That will probably entail reaching out to a ScriptManager to compile a function every time it changes (so that you only pay the overhead of compiling the Jython code once) and then executing it as needed. You should probably be serializing only the 'user' portion of the script from your designer scope, so you'll want to have a common-scoped com.inductiveautomation.ignition.common.script.typing.ExtensionFunctionDescriptor you can use in both the designer and gateway; in the gateway you'll call buildDef() on it to get the first line of the extension function you want to pass into compileFunction().
You'll also want to make sure you're using the script manager returned by GatewayContext.getProjectManager().getProjectScriptManager(String projectName), so that end users are able to use project libraries and access the default project database, user source, etc.
That's basically it
It's not a particularly easy process (mostly because you don't have many resources to go on) but it is quite doable; these APIs are much more ergonomic and consistent than the 7.9 and earlier project system, implausible as that might seem. There's also a lot I'm handwaving over here, including most of how to interact with the project system; you'll likely want to be guided by the resource workspace methods and go from there.
For what it's worth, we settled on an approach of creating a MessageHandlerEventSubscription class that wires the message handler in a project up to our event manager. That way we can do the wire up at runtime using the Gateway events in a project.
Then we exposed a script that handles creation of the subscription, which in turn creates a corresponding EventListener object of the specified type. Subscriptions are then tracked within our EventManager class. The script that creates the subscription also takes care of removing any previous instances of the subscription and removing its EventListener.
So far it seems to work pretty well, and the listeners appear to clean up okay whenever the project is updated and/or the gateway/module is restarted.
Of course, we may revisit the potential project resource approach down the road.
If I get some free time (haha) I might make a new SDK example that demonstrates all of the above. There's a few different concepts that all tie together that it would be good to demonstrate the ideal configuration for.