In 8.1, I'm using a ProjectResourceListener with a filter to listen for changes to my project resources.
How should I handle this in 8.3?
I see that I can register a ResourceCollectionListener with ProjectManager::addListener, but I don't understand the ResourceCollectionChangeEvent.
What is the contextObject?
First, depending on what you're doing, I'd highly recommend moving to a ResourceCollectionLifecycleFactory so we can do the boilerplate for you instead of you having to create listeners.
The Javadocs don't do a great job explaining WTF this is, but this is the common abstraction we use across all of our "create instances of something on the gateway when project resources change" pattern:
All that aside,
The contextObject added to change events is just a generic escape hatch we use to smuggle some signals for internal usage across the generic configuration system. I would just ignore it (pass null).
Also, ResourceCollectionListener is "a whole project was added/removed/updated". You probably want a listener within that project, which is a ResourceListener.
Awesome, thanks so much, this makes way more sense 
Here's what I've come up with:
class ClientResourceChangeListener(
val sessionMonitor: PerspectiveSessionMonitor,
projectManager: ProjectManager,
) : ResourceCollectionLifecycleFactory<ResourceCollectionLifecycle>(projectManager) {
val filter = ResourceFilter(ApplicationScope.ALL, listOf(ClientResource.type))
override fun getResourceFilter(): ResourceFilter {
return filter
}
override fun createLifecycle(
collection: RuntimeResourceCollection
): ResourceCollectionLifecycle {
return ClientResourceSessionNotifier(sessionMonitor, collection)
}
}
class ClientResourceSessionNotifier(
val sessionMonitor: PerspectiveSessionMonitor,
val collection: RuntimeResourceCollection,
) : ResourceCollectionLifecycle(collection) {
companion object {
const val PROTOCOL = "periscope-client-resource-notifier"
}
fun notify() {
sessionMonitor.getSessionsForProject(collection.name).forEach {
it.pages.forEach { page -> page.send(PROTOCOL, JsonObject()) }
}
}
override fun onStartup(resources: List<Resource>) {}
override fun onShutdown(resourceIds: List<ResourceId>) {}
override fun onResourcesCreated(resources: List<Resource>) = notify()
override fun onResourcesModified(resources: List<Resource>) = notify()
override fun onResourcesDeleted(resources: List<ResourceId>) = notify()
}