[Snowman 2] Problems with async JS, I guess

Ok, I have a complicated problem. This is a very simplified version but I think the basis is the same.

Say I have a function that prints content to the screen, appending a node:

window.printSomething = function() {
	$(function () {
		$('.passage').append('<p>Something</p>')
	} )
}

However, I want this function to do nothing in certain conditions. So I define a condition:

window.printSomething = function() {
	$(function () {
		if( flag == true ) {	$('.passage').append('<p>Something</p>')	}
	} )
}

Now, in a passage, I do this:

<% flag = true ; printSomething() %>

It works, and a paragraph with the word “Something” appears. And this:

<% flag = false ; printSomething() %>

It works! It prints nothing. But if I do this instead:

<% flag = false ; printSomething() ; flag = true ; printSomething() %>

It breaks. Instead of following the order and printing the second something while ignoring the first, it prints both.

Can you tell me why it fails? I guess it’s related to the async working of JS, but I know almost nothing about that, not enough even to search for solutions succesfully.

This is slightly more similar to my actual use. I have:

  • Several passages including the printSomething() function
  • An array with the names of those passages
  • A for loop that attaches those passages to the current one, using Snowman’s story.render()
  • Within the loop, a condition that if it’s the last iteration, change the flag to true and then print the last passage

The same problem happens. Even though the flag changes to true only in the last iteration, it seems that the functions in the passages are called after that, and all the somethings are printed.

What am I actually trying to do? In my current WIP, I don’t use the standard Twine choices but a custom function. Passages are appended to the current text, instead of replacing the whole screen. I’d like to print all the previously seen passages when I restore a savegame, but that would print all the choices, and I don’t want that: only the narration. That’s when I thought of adding a flag. But, once the previous passages are printed, the flag has to change so the last passage can be printed with its choices and you can keep playing.

I attach the basic example. Does anyone have any pointer?

Snowman problem.zip (88 KB)

Untested, however, try removing the jQuery.ready() callback. Not only is it unnecessary, you don’t get to user code without the document being ready, but it likely introduces the asynchronous behavior you’re seeing.

I.e.,

window.printSomething = function () {
		if (flag === true) {
			$('.passage').append('<p>Something</p>');
		}
};
2 Likes

Apparently, the callback is needed for the function printSomething() to work inside <% %>. If I remove the callback, <% flag = true ; printSomething() %> doesn’t print anything.

However, it works if I use script tags instead of <% %>, and the async behaviour seems to disappear.

It might be a solution, but I’m afraid that if I go down that route I’ll have to replace <% %> with script tags in more parts of the code and lose the advantages they give when writing.

Thanks!

(Typing from bed on my phone… and then edited once I got up.)

That tells me that it’s probably rendering to a buffer—either a doc fragment or a new .passage element—that’s not yet attached to the DOM.

You could try something like the following:

window.printSomething = function () {
	if (flag) {
		$(window).one('sm.passage.shown', function() {
			$('.passage').append('<p>Something</p>');
		});
	}
};

NOTE: Yes, that does reintroduce asynchronicity to the process. The difference here being that it’s arranged in the proper place to allow the synchronous bits to work properly.

NOTE 2: The used event assumes Snowman 2.x. IIRC, the 1.x events have different names.


EDIT: Realized that Underscore.js’ template print() wouldn’t work from a previously defined function, as it’s injected fresh each time—thus would be out of scope. Old post contents archived below for posterity.

Old post (doesn't work)

If that’s the case, then the best solution depending on what you’re actually looking to accomplish is likely to work with the process rather than attempting to bypass it as you currently are.

Have you tried something like the following?

window.printSomething = function () {
		if (flag === true) {
			print('<p>Something</p>');
		}
};

The <%…%> syntax comes from Underscore.js’ template subsystem—Underscore.js is included with and used by Snowman—so you should be able to use its print() function.

1 Like

Thank you as always @TheMadExile! I’ll try your suggestion later in the evening.

However, one thing regarding the passage event you use. What I’m really doing looks like:

list = [ 'passage1', 'passage2', 'passage3' ]
for... 
    story.render( list[i] )

And I want a function like printsomething() in passage1 to do nothing, because choices should not appear in a transcript of the previous game.

The thing: does story.render(passage) fire passage events like sm.passage.shown? I thought it didn’t.

I don’t believe it does, no. An alternative to the event would be something like the following:

window.printSomething = function () {
	if (flag) {
		setTimeout(function () {
			$('.passage').append('<p>Something</p>');
		}, 0);
	}
};

Unappealing, but it should get the job done. Though, I can’t shake the feeling that there’s probably a better way to go about this.

@videlais might have a better idea.

Thanks again! Actually, I’ve just thought of a design fix, not technical. I’ll tell you if it works.

Fixed! But not technically. The problem is that changing the flag happened before the previous functions ended: then, simply, don’t change the flag at all. Instead, print a link that the player has to click, and then, on click, change the flat and perform the last step.

Thanks for the help! A transcript of the previously played story is something I have wanted to implement since The Master of the Land in 2017, but I could never come up with a way of displaying the passages without running all the code in them. It seems I have it now.