Determining When a Plugin is Unloaded

I created a plugin that is meant to sync scripts to the filesystem, and vice versa. This plugin exists as a single button that you can find in the “Plugins” tab. I never use Plugin:Activate. The idea is that the plugin always runs regardless of what other tools you’re using.

When I save it as a local plugin, studio refreshes all plugins, but it still seems like my old plugin is still running in the background – particularly the threads spawned with spawn. I want to kill these threads when my plugin refreshes.

Does anyone know how to detect when plugins are refreshed? Or another good way to solve this particular problem?

For those interested in reproing...

Create a script in workspace with the following source, then just right click twice on it, selecting “Save as Local Plugin”. Overwrite the plugin when you save it the second time.

local ID = game:GetService("HttpService"):GenerateGUID(false):sub(10, 13);
print("plugin " .. ID .. " starting");
spawn(function()
	while true do
		print("plugin " .. ID .. " running")
		wait(5)
	end
end)

Things I've tried...
  • plugin.Changed event
  • Button.Changed event
  • plugin.Deactivation event
  • polling plugin properties, like RobloxLocked (always false, even after unloading) or Parent (always nil).

Edit: this applies to threads spawned with spawn, and all individual scripts’ threads.

I’m not sure if you are aware, but there are quite a few of these plugins already:
Rojo: https://github.com/LPGhatguy/rojo (LPGhatGuy)
rbxmk: https://github.com/anaminus/rbxmk (Anaminus)
Studio Bridge: https://github.com/vocksel/studio-bridge (Vocksel)
RbxSync: https://github.com/evaera/RbxSync (evaera)
CodeSync: https://github.com/MemoryPenguin/CodeSync (MemoryPenguin)
rbx-exteditor: https://github.com/MemoryPenguin/rbx-exteditor (also by MemoryPenguin)

In the case of Rojo, which I’m the most familiar with, the plugin sets a unique value which the old plugin will see. Once the old plugin sees it, it stops. An example of this would be a value in shared.MyPluginName.version. It could be set to a unique number or a unique table, function, or userdata. The old plugin will compare this version to its own internal version and unload if different.

5 Likes

Ah, that works. I guess the only shortcoming is that if you clean up your plugins folder (meaning delete everything you don’t need) and refresh, it won’t necessarily fix the problem, but that’s not my use case. That’ll be someone else’s problem. :slight_smile:


Edit: and regarding the other solutions, they are pretty much all filesystem --> studio, not vice versa. I tried RbxSync (which is the only one that purports to try), but it was pretty glitchy. Not to suggest my solution will be any better, but it’ll be mine. :stuck_out_tongue:

A simple way to do this is to check the Parent property of any DockWidgetPluginGui instances created by the plugin - when the plugin is unloaded, the Parent property is equal to nil (otherwise it’s equal to game.PluginGui). This may or may not be an ugly hack, but it seems to work reliably for me

2 Likes

Built-in plugins using dock widgets listen for AncestryChanged to know when to destroy the UI and disconnect connections (specifically they use Roact and call Roact.unmount(handle)).

For other plugins, I’ve seen methods like what IdiomicLanguage said where you create something outside the plugin (such as a gui in CoreGui), but if it already exists then you Destroy() the previous instance first. The previous instance of the plugin listens for that destroy to know to cleanup.

You should file this as a feature request.

2 Likes

@HaxHelper
Aw, so my system does utilize DockWidgetPluginGuis, but not all of the time. I’m trying to keep that complicated GUI away from the user if a one-button solution will be sufficient for them.
This does look like a good solution for some use cases, and even captures the shortcoming I mentioned up above in reference to @IdiomicLanguage’s suggestion.

@dr01d3k4
I’ve gone with a Maid in _G for this. I’m frankly surprised I didn’t think of that solution before, as that’s what I did back when I tested my plugin(s) with a command line script, for which I don’t get the magic auto-cleanup that HotSwap provides.

The DockWidgetPluginGui doesn’t necessarily need its Enabled property to be true - I typically just keep all of the dock widgets parented to PluginGui at all times and toggle DockWidgetPluginGui.Enabled when I want to change visibility

this does mean that there are DockWidgetPluginGui instances just sitting there when not in use, but imo this isn’t really a big deal for a plugin interface

Does this pollute that already-very-long list of plugins? I’d like to avoid that if possible. It would feel like adding fuel to fire.

2018-11-25_22-27-39

Mine is not even that bad because I try to disable all plugins I am not actively using. Plugins are a mess to use, all around.

it does (blegh), and that’s why I have toolbar buttons on my plugins to toggle the visibility of dock widgets that they create. Plugins need some hardcore TLC

edit: however, note that only one DockWidgetPluginGui instance needs to be set up this way to treat its ancestry as a “plugin loaded” condition

1 Like