ruipin/fvtt-lib-wrapper

Full vs Shim compatibility

Closed this issue · 3 comments

sneat commented

Hi,

I'm the author of Scene Packer and currently include your shim but not a hard dependency.

I've been asked whether it's possible to remove the popup dialog that's bundled in the shim. I know it's possible, but I want to know what the differences currently are between the full module and the shim version. I can't see an issue or wiki entry that lists the differences and only one issue from several months ago (#3) that discusses differences.

My current thought is to use the shim that you provide, but remove the popup dialog, therefore using the full module if it's enabled but not prompting people if it isn't. What functionality would be lost with this approach though?

I probably should add a much more detailed explanation of the differences between the full library and the shim in the documentation. I'll keep this ticket open until I find time to do it, so I don't forget.

Removing the shim notification

Regarding your question about disabling the warning dialog, as you seem to be aware, yes you can strip the corresponding part of the code (second half of the shim) and no functionality is compromised. It was written knowing some people might not wish for that notification to be there. I am aware of multiple modules that do this, so you wouldn't be the only one.

I.e., you can just strip this entire section (or simply set WARN_FALLBACK to false, if you prefer):

//************** USER CUSTOMIZABLE:
// Whether to warn GM that the fallback is being used
const WARN_FALLBACK = true;
// Set up the ready hook that shows the "libWrapper not installed" warning dialog
if(WARN_FALLBACK) {
//************** USER CUSTOMIZABLE:
// Package ID & Package Title - by default attempts to auto-detect, but you might want to hardcode your package ID and title here to avoid potential auto-detect issues
const [PACKAGE_ID, PACKAGE_TITLE] = (()=>{
const match = (import.meta?.url ?? Error().stack)?.match(/\/(worlds|systems|modules)\/(.+)(?=\/)/i);
if(match?.length !== 3) return [null,null];
const dirs = match[2].split('/');
if(match[1] === 'worlds') return dirs.find(n => n && game.world.id === n) ? [game.world.id, game.world.title] : [null,null];
if(match[1] === 'systems') return dirs.find(n => n && game.system.id === n) ? [game.system.id, game.system.data.title] : [null,null];
const id = dirs.find(n => n && game.modules.has(n));
return [id, game.modules.get(id)?.data?.title];
})();
if(!PACKAGE_ID || !PACKAGE_TITLE) {
console.error("libWrapper Shim: Could not auto-detect package ID and/or title. The libWrapper fallback warning dialog will be disabled.");
return;
}
Hooks.once('ready', () => {
//************** USER CUSTOMIZABLE:
// Title and message for the dialog shown when the real libWrapper is not installed.
const FALLBACK_MESSAGE_TITLE = PACKAGE_TITLE;
const FALLBACK_MESSAGE = `
<p><b>'${PACKAGE_TITLE}' depends on the 'libWrapper' module, which is not present.</b></p>
<p>A fallback implementation will be used, which increases the chance of compatibility issues with other modules.</p>
<small><p>'libWrapper' is a library which provides package developers with a simple way to modify core Foundry VTT code, while reducing the likelihood of conflict with other packages.</p>
<p>You can install it from the "Add-on Modules" tab in the <a href="javascript:game.shutDown()">Foundry VTT Setup</a>, from the <a href="https://foundryvtt.com/packages/lib-wrapper">Foundry VTT package repository</a>, or from <a href="https://github.com/ruipin/fvtt-lib-wrapper/">libWrapper's Github page</a>.</p></small>
`;
// Settings key used for the "Don't remind me again" setting
const DONT_REMIND_AGAIN_KEY = "libwrapper-dont-remind-again";
// Dialog code
console.warn(`${PACKAGE_TITLE}: libWrapper not present, using fallback implementation.`);
game.settings.register(PACKAGE_ID, DONT_REMIND_AGAIN_KEY, { name: '', default: false, type: Boolean, scope: 'world', config: false });
if(game.user.isGM && !game.settings.get(PACKAGE_ID, DONT_REMIND_AGAIN_KEY)) {
new Dialog({
title: FALLBACK_MESSAGE_TITLE,
content: FALLBACK_MESSAGE, buttons: {
ok: { icon: '<i class="fas fa-check"></i>', label: 'Understood' },
dont_remind: { icon: '<i class="fas fa-times"></i>', label: "Don't remind me again", callback: () => game.settings.set(PACKAGE_ID, DONT_REMIND_AGAIN_KEY, true) }
}
}).render(true);
}
});
}


Difference between shim and full library

With regards to the difference in functionality, in essence the shim does standard javascript wrapping, and nothing else.

  1. Shim does not support any user-facing functionality. Think prioritization of modules, conflict detection, list of wrappers, etc. Basically, anything you can access through the libWrapper settings menu.

  2. Shim does not support libWrapper.unregister / libWrapper.unregister_all calls.

  3. Shim does not support dynamic dispatch. The next method in the wrapper chain is calculated at the time of each register call, and never changed/reordered later. This has many implications:

    1. The wrapper type metadata (WRAPPER, MIXED, OVERRIDE) is completely ignored. The wrapper call order will match the order in which they are registered. For instance, if a module registers an OVERRIDE after your module, you will never be called. Nobody guarantees MIXED wrappers come after all WRAPPER wrappers, nor that OVERRIDE wrappers come after all MIXED wrappers.

    2. Inheritance chains are static. If you have class B extends A and override B.prototype.foo before someone else overrides A.prototype.foo, calling B.prototype.foo will skip the A.prototype.foo wrapper.

    3. There is no distinction between a libWrapper (Shim) wrapper and a non-libWrapper wrapper. While normally non-libWrapper wrappers will always come after all libWrapper wrappers, when using the shim this is not the case.

  4. None of the libWrapper safeties are guaranteed when using the shim. For instance, nothing checks that modules alway call wrapped(...args) when using WRAPPER. Nothing checks that there can only be one OVERRIDE wrapper on a given method.

  5. There are no libWrapper Hooks when using the shim.

These are probably the most relevant differences. In essence, using the shim does not give you any advantage when it comes to compatibility versus not using libWrapper at all. As soon as two modules wrap the same thing, it is very likely things will break.

The intent of the shim is to remove the disadvantage from using libWrapper, i.e. of tying yourself to a dependency. It means that using libWrapper does not imply being tied to libWrapper, since if you disable libWrapper you get the exact same behaviour you would if you were doing everything by hand. You can take advantage of the benefits of libWrapper, while still working as if libWrapper didn't exist when libWrapper isn't installed.

As a bonus it brings in a few ease-of-use features such as support for getters/setters and inherited methods, but nothing that couldn't be done by hand with a couple of lines of code.


Hopefully this answers your question. Let me know if not, or if you have any more questions.

I have now updated the shim documentation with the above information.

Closing this ticket. Feel free to re-open if the above (or the documentation changes) did not answer all your questions.

sneat commented

Thanks @ruipin 👍