Sugarcube initialization order and Custom Macro

Twine Version: 2.36.1

I’m just getting started with Tweego and SugarCube. I have a basic npm project set up using esbuild and typescript and am just testing out a few simple things to get my bearings. At the moment, I’m trying to define a custom macro from test.tsx:

  SugarCube.Macro.add("mymacro", {
        tags: null,
        skipArgs: false,
        handler: function () {
            this.output.append("test output")
        },
    })

Esbuild puts this in out.js, which gets picked up by tweego and shows up in the index.html story script element like this:

(() => {
  SugarCube.Macro.add("mymacro", {
    tags: null,
    skipArgs: false,
    handler: function() {
      this.output.append("test output");
    }
  })();

The problem is that the userscript runs before the Sugarcube object has been fully initialized, and Sugarcube.Macro.add is still undefined, causing an exception.

If I wrap the entire init block in $(document).on(":storyready", function(ev) { .... } then the macro registration works fine. But the active passage gets rendered before the macro has been properly registered regardless, so I see macro <<mymacro>> does not exist when I view index.html in Chrome.

Only on the initial passage though. If I navigate to some other passage that also uses the custom macro, then it works. How do I ensure my userscript waits until after SugarCube is fully initialized, but also gets to register macros before passage rendering?

1 Like

Simply doing Macro.add directly instead of Sugarcube.Macro.add, seems to do the trick. Don’t know why I got it into my head that all API interaction has to start with the Sugarcube object.

The purpose of the undocumented SugarCube debugging interface is to allow an Author to access the internals of the story format’s JavaScript based engine from a web-browser’s Console while the generated Story HTML file is being viewed.

If you’re using that SugarCube debugging interface in any other situation then that’s generally a sign that you’re doing something the wrong way. :slight_smile:

3 Likes

As you discovered, and Greyelf pointed out, you should never use use the global debugging API, SugarCube, for anything other than post-startup debugging—generally console debugging, as it’s unnecessary internally.

The issue you ran into, aside from using it in the first place, is that SugarCube is currently set up dead last in the startup sequence.


While I’m here, a few suggestions for your macro:

  • For container macros, the current idiomatic usage is to always specify an array to tags. If the macro won’t have child tags, simply leave the array empty.
  • For all macros, don’t use optional properties unnecessarily. You don’t need skipArgs if you’re just going to set it to false, as that’s its default value.

For example:

Macro.add('mymacro', {
	tags : [],

	handler() {
		this.output.append('test output');
	}
});
2 Likes

Thank you for the explanation and additional suggestions, guys. Greatly appreciated.

A minor follow-up if I may: I currently need to use a closing tag in the twee file:
<<mymacro>><</mymacro>>. Is there a way to declare an empty macro so that I don’t need the closing tag, like the <<print>> macro, or possibly use XML style <<mymacro/>>?

1 Like

SugarCube uses the same Macro API internally to define its core macros as the Author uses to define their custom macros.

So if you want to know how the <<print>> macro is defined you just need to look at the related section of SugarCube’s source code.

eg. as advised by TME “don’t use optional properties unnecessarily”, which in this specific use-case means, don’t include a tags property if a macro isn’t a container based one.

1 Like

As noted by Greyelf, the tags property makes a macro a container macro, as well as potentially defining its child tags. Thus, a self-closing macro shouldn’t include the tags property at all.

From the, as yet unreleased, v2.37.0 Macro.add() documentation, which is mostly the same as the current documentation:

tags: (optional, Array<string>) Signifies that the macro is a container macro—i.e., not self-closing. An array of child tag names or an empty array, if there are no child tags.


I may as well detail the other optional property of note, skipArgs:

skipArgs: (optional, boolean | Array<string>) Disables parsing argument strings into discrete arguments. Used by macros that only use the raw/full argument strings. Boolean true to affect all tags or an array of tag names to affect.

By default, all of a macro’s tags, both self-closing and container, have their argument strings parsed into discrete arguments. Normally, this is what you want as most macros are what’re known as argument-types—e.g., <<link>>, <<textbox>>, <<audio>>.

Some macros, however, are expression-types. Meaning that their argument strings are their complete argument, an expression—e.g., <<set>>, <<if>>. In these cases, there’s no reason to attempt to parse the argument string into discrete arguments, since they won’t be used and probably couldn’t be parsed effectively anyway.

In its basic usage, with boolean true, the skipArgs property allows you to tell expression-type macros to skip discrete argument processing for all its tags—the most common usage.

In its more complicated usage, with an array of tag names, you can have a macro whose tags vary in type—i.e., some are argument-type, others expression-type. The primary example of that is the <<switch>> macro. E.g., the relevant parts of its definition:

Macro.add('switch', {
	skipArgs : ['switch'],
	tags     : ['case', 'default'],

	handler() {
		/* … */
	}
});

As you can see, the main tag, switch, is an expression-type as it’s set to skip processing. It’s other tags, case & default, are argument-types as they are not set to skip processing.

SEE ALSO:

2 Likes

Thanks again for the detailed explanations! Needed the extra clarification to be able to see the missing piece of the puzzle while studying the print macro :sweat_smile: I had tried passing tags: null and it did not occur to me this would be interpreted differently to not passing it at all.

Thanks also for the pointer to the Sugarcube sources. Great to have as a reference for how things are done, and what’s possible.