ruipin / fvtt-lib-wrapper

Library for Foundry VTT which provides module developers with a simple way to modify core Foundry VTT code, while reducing the likelihood of conflict with other modules.
GNU Lesser General Public License v3.0
33 stars 13 forks source link

Allow user to bind arguments when calling libWrapper.register #58

Closed ruipin closed 2 years ago

ruipin commented 2 years ago

Sometimes users of libWrapper wish to bind the same function as a wrapper to multiple methods, and wish to distinguish which one is being called.

For example, if we have the wrapper function:

function someFunction(name, wrapped, ...args) {
  // ... code in common ...

  if(name == "foo") {
    // ... code for 'foo' ...
  }
  else {
    // ... code for 'bar' ...
  }
}

This can be achieved using Function.prototype.bind:

libWrapper.register("foo", someFunction.bind(null, "foo"));
libWrapper.register("bar", someFunction.bind(null, "bar"));

This works, and will result in calls to someFunction("foo", wrapped, ...args) and someFunction("bar", wrapped, ...args), however the this variable within those functions will be null. Due to the design of Function.prototype.bind, there is no way to use it yet have the correct value for this.

The alternative is to use an anonymous function, e.g.:

libWrapper.register("foo", function(...args) { return someFunction.call(this, "foo", ...args) });
libWrapper.register("bar", function(...args) { return someFunction.call(this, "bar", ...args) });

This will result in the correct value of this, however it involves extra code and also causes performance loss and stack trace pollution due to the extra function call.

It would probably be a good idea to improve the libWrapper API, to support this natively, e.g.:

libWrapper.register("foo", someFunction, "WRAPPER", {bind: ["foo"]});
libWrapper.register("bar", someFunction, "WRAPPER", {bind: ["bar"]});

The exact name of the parameter (bind above) is subject to change, but it is meant as a replacement to the previous two solutions, and would take an array of arguments that would be passed to the function as-is before wrapped and any other parameters in the call.

This would give the user the best of both worlds, i.e. this will be correct while the extra call (and corresponding performance loss & stack trace pollution) will be avoided.

Ideally, the shim would also be updated to support this. From a cursory glance it would be a relatively simple change.

caewok commented 2 years ago

I would use this proposed enhancement. I am wrapping all the template drawing methods in my walled templates module, so I currently have a lot of repeated code. (I was actually just looking to see if libWrapper could do something like this when I saw this issue!)

ruipin commented 2 years ago

Implemented in v1.12.0.0:

/**
 * ...
 *
 * @param {any[]} options.bind [Optional] An array of parameters that should be passed to 'fn'.
 *
 *   This allows avoiding an extra function call, for instance:
 *     libWrapper.register(PACKAGE_ID, "foo", function(wrapped, ...args) { return someFunction.call(this, wrapped, "foo", "bar", ...args) });
 *   becomes
 *     libWrapper.register(PACKAGE_ID, "foo", someFunction, "WRAPPER", {bind: ["foo", "bar"]});
 *
 *   First introduced in v1.12.0.0.
 *
 * ...
 */