tmedwards / sugarcube-2

SugarCube is a free (gratis and libre) story format for Twine/Twee.
https://www.motoslave.net/sugarcube/2/
BSD 2-Clause "Simplified" License
177 stars 41 forks source link

Document when done macros are run in the Navigation Event section. #303

Closed BawdyInkSlinger closed 2 months ago

BawdyInkSlinger commented 4 months ago

Is your feature request related to a problem? The <<done>> macro documentation doesn't specifically mention when the <<done>> macro will execute relative to other Navigation Events. This would be useful to avoid and troubleshoot race conditions between <<done>>, special passage names (especially StoryInterface), and the navigation events.

Describe the solution you'd like. I'd like the Navigation Events section to include a list item that says when <<done>> will execute relative to the other list items.

If this is indeterminate, I think that should be explicitly stated in the <<done>> macro documentation, as that would help me decide when this macro is the right tool for the job or I should implement my code another way.

Describe alternatives you've considered. I can create a sample project that logs on every event/special passage name and include a <<done>> macro in the passage. These log messages would tell me the order in which <<done>> runs relative to the other events/passages. While this would be informative, it's a high effort solution that nobody else can learn from unless I communicate with them.

Additional context. n/a

ChapelR commented 4 months ago

The <<done>> macro uses a timer, it's not tied to any event. If it used a navigation event, it wouldn't work when used with links or dynamic DOM elements, or with other timers. There's a constant in SC's engine called Engine.minDomActionDelay that's used for stuff like this.

My version of <<done>> that was a part of my custom macro collection did use a navigation event: the way it worked was it checked if the engine was idle or rendering, if it was rendering it hooked into :passagedone, otherwise it just rendered the content right away. My version of the macro wouldn't work with dynamically rendered content at all, though. At any rate, it's possible you're thinking of my <<done>> macro, but although they share the same name and are broadly similar in what they intend to do, they aren't exactly the same in their design or effects.

As to the order it occurs in relation to other navigation events, I would recommend considering it to be unknowable. It will pretty much always occur after rendering ends, but I don't think it's wise to count on it coming in a certain order everytime in relation to say :passageend, though I imagine it would mostly just be dead last if tested. Assuming it will always come in a fixed order would probably create a race condition, though. TME might be more certain than I am though.

tmedwards commented 3 months ago

It's pretty much as described by Chapel. I can't tell you exactly when it'll run, because I don't know.

The <<done>> macro will execute after the JavaScript engine has ceded control back to the browser, in the main event loop. It gets put into the macro task queue and is only executed after its delay of, roughly, 40 ms.

BawdyInkSlinger commented 2 months ago

@ChapelR

As to the order it occurs in relation to other navigation events, I would recommend considering it to be unknowable. It will pretty much always occur after rendering ends, but I don't think it's wise to count on it coming in a certain order...

That makes sense. Thank you for confirming my suspicions.

Could this be officially documented? A more formal version of: "Don't depend on navigational event order in your \<\<done>> macro."

greyelf commented 2 months ago

Could this be officially documented? A more formal version of: "Don't depend on navigational event order in your \<\<done>> macro."

A better wording would likely be: "Don't initialise Navigational Event handlers within the body of a <<done>> macro call".

Reason being, that as stated in the <<done>> macro documentation (emphasis mine)...

Silently executes its contents when the incoming passage is done rendering and has been added to the page.

...thus in a perfect world, where any <<done>> macro is called at the end of the Passage's content, all the Navigation related Events should of already occurred (and been handled) before the body of the <<done>> macro is processed/executed. eg.

:: Library
many lines
of time consuming
passage content...

<<done>>...delay processing of this content, until hopeful after all the Navigation events have occurred<</done>>
BawdyInkSlinger commented 2 months ago

Could this be officially documented? A more formal version of: "Don't depend on navigational event order in your \<\<done>> macro."

A better wording would likely be: "Don't initialise Navigational Event handlers within the body of a <<done>> macro call".

Reason being...

Are you saying the done macro order consistently executes after a specific navigational event, so long as your done is outside of a navigational event handler?

tmedwards commented 2 months ago

The <<done>> macro will execute after the JavaScript engine has ceded control back to the browser, in the main event loop. It gets put into the macro (not SugarCube related) task queue and is only executed after its delay of, roughly, 40 ms.

As I stated, the <<done>> macro uses the task queue. There are two task queues: the (macro) task queue and the micro task queue. Macro tasks are used for timers, like setTimeout() and setInterval(). Micro tasks are used for asynchronous items, like Promises. All items in the task queues occur after the JavaScript engine has yielded control back to the browser and the task queues run.

The navigational events have their callbacks called during the JavaScript engine's run.

The <<done>> macros have their callbacks called after the JavaScript engine's run—during the task queues.

BawdyInkSlinger commented 2 months ago

@tmedwards

The navigational events have their callbacks called during the JavaScript engine's run.

The <<done>> macros have their callbacks called after the JavaScript engine's run—during the task queues.

Doesn't that mean the <<done>> macro documentation would be accurate if it said, "The <<done>> macro will always execute after the navigation events."?


By the way, when you use the term "JavaScript engine's run," I'm interpreting that as "the current task." If that's not what you mean, I wasn't able to find this phrase in the MDN documentation.

tmedwards commented 2 months ago

The main browser thread is where the JavaScript engine lives and executes. It is not, however, the only thing on the thread. The DOM, CSSOM, BOM (browser object model), the task queues, and a few other things live there too.

Thus, the JavaScript engine only gets a slice of the main threads execution time, instead of all of it. When its turn comes up in the thread it's passed control, executes, and then exits until next time its turn comes around.

So, when I say:

That's what I'm talking about. When the JavaScript engine has finished its turn.

The task queue's turn in the main thread occurs after the JavaScript engine's turn, by definition.

tmedwards commented 2 months ago

Doesn't that mean the <> macro documentation would be accurate if it said, "The <> macro will always execute after the navigation events."?

Yes, it would. Of course, it already does (emphasis mine):

Silently executes its contents when the incoming passage is done rendering and has been added to the page.

Of course, I can see how that could be confusing. Does it mean literally what it says, and <<done>> executes immediately after that in Engine.play(), or more generally, and <<done>> executes after Engine.play() has returned—you should know by now that it's the latter.

I'll see about rewording it to make that clear.