Better way to create a "soft depend"?
#1
So I'm trying to make a plugin acts as a soft dependency, meaning that other plugins can utilize it if it is there, but do not require it. I tried coming up with a way that will make the system function if not all the plugins are loaded on startup, which means that HOOK_PLUGINS_LOADED won't work. My current method works, but is significantly less than ideal, because it spams the console with errors that don't matter (among other things). Here's the code outline:

[spoiler]
g_Announced = nil

function Initialize(a_Plugin)
  a_Plugin:SetName("SoftDepend")
  a_Plugin:SetVersion(.1)
	
  cRoot:Get():GetDefaultWorld():ScheduleTask(5,spreadTheWord)
  cPluginManager.AddHook(cPluginManager.HOOK_PLUGINS_LOADED,spreadTheWord)

  return true
end

function spreadTheWord()
  if not g_Announced then announceLoad() end
end

function announceLoad()
  cPluginManager:Get():ForEachPlugin(
    function(a_Plugin)
      cPluginManager:CallPlugin(a_Plugin:GetName(),"MySpecialFunction")
    end
  )
  return true
end
[/spoiler]

Options I considered but thought were meh:

An inbuilt list of plugins that this is a soft dependency for. It would then loop through the list (this would happen at the same time as "SpreadTheWord" happens in the above code) and execute for every plugin it found in the list that is loaded. However, this could require a lot of maintenance if I manage to write this well enough to be widely used (the concept is great, my execution might not be).

A config file with a list of all plugins installed that can utilize the API that the end user has to modify. I personally don't like that one much, as I hate trusting the end user. I've seen some... interesting end users, as I'm sure many of you have as well.

Every plugin that wants to use the API checking if it is loaded. If there were a plugin loaded (note the singular) hook that was called any time ANY plugin was loaded, this would be simple. Just have them check if the API is loaded on every plugin load. However, I know that hook would be awkward. I did some testing and the server doesn't tick (at least world ticks don't) while plugins are loading. Meaning a HOOK_PLUGIN_LOADED would probably either only be called once during load (at the same time as HOOK_PLUGINS_LOADED) or would be called multiple times within a single world tick, which could make for a sticky situation? Not really sure there. So the perfect solution form my standpoint is possibly terrible from a Cuberite dev's.

Every plugin that wants to use the API checking if it is loaded on a clock. So just have a function that calls itself using cWorld:ScheduleTask() after some delay so long as the API isn't found. It would initially be called using the HOOK_PLUGINS_LOADED of 5 ticks after loading method displayed above, and then call itself if the API wasn't found. I just don't like the idea of having a lot of long term loops, no matter how little energy they may or may not use.


All "disabling" signals could be handled by the API simply by recording what plugins were talking to it and looping through that list on disable (similarly other plugins could call a "remove me from the API" function on their disable if the API was active at that time).


So any methods besides the above? And which would you choose?
Reply
Thanks given by:
#2
I kinda fail to see what you're trying to do. You're trying to create a library of some sorts?
Reply
Thanks given by:
#3
Much easier way is to use the reverse dependency (and the API was designed for that usecase):
-- Library plugin
function MyLibFunction(a_Param)
  return true
end

-- Client plugin
if not(cPluginManager:Get():CallPlugin("LibPlugin", "MyLibFunction", 47) then
  -- Library not available
else
  -- Library code executed
end
This still does spam the error in the console if the library plugin is missing, but it is the intended way of doing things and the console message will be removed sometime in the future. The only limit that this has is that the library function doesn't return nil as a regular value - nil is used for detection whether the call succeeded.
Reply
Thanks given by:
#4
You can do something like hooks, by allowing other plugins to register their handlers like it's done in IRChat.
This requires the library plugin to be loaded before others.
(Is the case when not all plugins are loaded at the same time really important? It seems like a bit of an overkill to me with our current API.)
( https://github.com/36451/IRChat & https://github.com/36451/IRChatAPITest <- These might be a bit outdated, though )
Reply
Thanks given by:
#5
I like the "not all plugins are loaded" case because it gives me some future-proofing (and also the ability to bugtest using unload/load in addition to reload).

And yes, I had exactly that system in place. I created a "registerPlaceholder" function and a "callPlaceholder" function (not those exact names).

I think that, unless I find a significant problem with my method, I'll keep going with it. It's not easier, it produces more errors than IRChat's method of hooking into Core (although significantly fewer than Xoft's), but it's "safer".
Reply
Thanks given by:




Users browsing this thread: 4 Guest(s)