Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

[SugarCube] Strange behavior on first opening html file in a browser

edited September 2014 in Help! with 1.x
I am having a strange cross-browser issue that shows up everywhere from the latest Chrome and Opera all the way back to Safari 5.1 and Firefox 5. I am at a loss as to where it's coming from, so I'm posting here to try to gather ideas on what could be causing it.

The first time that I open the story in a browser (I'm hosting on Dropbox), it fails to display the text of the "first" passage.  Reload the page, or navigate to the URL in a new browser tab, and everything will then load up just fine. I can return to the first run behavior by deleting the stored local storage and session storage objects.

Now, I have "first passage" in quotes there because the code of the Start passage does run (and PassageDone after it runs as well), but it seems that a state.display in that code fails to show the text of the designated passage. Here's the code in the Start passage:
<<if
SaveSystem.autosaveOK
&amp;&amp; storage.getItem("saves").autosave !== null
>>\

You have a session already in progress. Continue where you left off?
<<script>>
var resumeBtn = insertElement(
output,
"button",
"resume-button",
"ui-autostart-button",
"Resume where I left off"
);
var restartBtn = insertElement(
output,
"button",
"restart-button",
"ui-autostart-button",
"Start over"
);
$(resumeBtn).click(function() {
SaveSystem.loadAuto();
});
$(restartBtn).click(function() {
SaveSystem.deleteAuto();
state.display(state.active.variables.first);
});
<</script>>\
<<else>>\
<<script>>
alert("Testing!");
state.display(state.active.variables.first);
<</script>>\
<</if>>\
Basically, the <<if>> condition is false on first run (there are no autosaves yet present), so the script in the <<else>> should display the requested passage, yet it fails to do so. (The alert in the <<else>> does appear, so the overall logic is working correctly.) The state.display there has no effect whether I use the $first variable or name the passage directly.

So, any ideas at all?

EDIT: Neglected to mention that this is a totally silent issue: no overt error messages, no warnings in the console.

Comments

  • First issue:
    You have a grave error in your initial conditional logic.

    <<if
    SaveSystem.autosaveOK
    &amp;&amp; storage.getItem("saves").autosave !== null
    >>\
    You're using the method SaveSystem.autosaveOK() as if it were a data property, when it's a function (see: SaveSystem.autosaveOK()).  That will return the function itself, rather than calling it, which means that bit will always evaluate to true, so you're only really testing whether storage.getItem(&quot;saves&quot;).autosave !== null.

    Also, if you want to check for the existence of the autosave, simply use SaveSystem.hasAuto() (see: SaveSystem.hasAuto()).

    TL;DR: The above code is broken and not leveraging the API fully (to be fair, .hasAuto() might be more recent than your code).  Anyway, do this instead:

    <<if SaveSystem.autosaveOK() &amp;&amp; SaveSystem.hasAuto()>>\

    Second issue:
    Your big problem is how you're calling state.display() in the &lt;&lt;else&gt;&gt; clause.

    <<else>>\
    <<script>>
    alert("Testing!");
    state.display(state.active.variables.first);
    <</script>>\
    <</if>>\
    You cannot call state.display() from within a call to state.display().  The call in the "Start over" button works because it gets called long after the original call has completed, not so with the call in the &lt;&lt;else&gt;&gt; clause.

    The technical details are that the second/inner call to state.display() happens during the rendering of the passage by the first/outer call, which actually does work as you'd expect (the history is updated, output is generated and displayed, etc.), but once that call completes the first/outer call continues and overwrites the output of the second/inner call.  So, you're left in a position where the history updates of both the first/outer and second/inner calls have happened (you can check this), but the display updates of the first/outer call have won out in exclusion of those by the second/inner call.

    The fix, while a little daft, is simple, use a timeout to delay the call to state.display() until after the original call completes.

    TL;DR: The above code cannot work, so do this instead:

    <<else>>\
    <<script>>
    setTimeout(function () {
    state.display(state.active.variables.first);
    }, 10);
    <</script>>\
    <</if>>\

    Third issue: (just an annoyance really)
    Your two button are snuggling together hardcore, which (IMO) looks terrible.  I'd suggest adding a space between them.  For example:

    );
    insertText(output, " ");
    var restartBtn = insertElement(

    Putting it all together:

    <<if SaveSystem.autosaveOK() &amp;&amp; SaveSystem.hasAuto()>>\

    You have a session already in progress. Continue where you left off?
    <<script>>
    var resumeBtn = insertElement(
    output,
    "button",
    "resume-button",
    "ui-autostart-button",
    "Resume where I left off"
    );
    insertText(output, " ");
    var restartBtn = insertElement(
    output,
    "button",
    "restart-button",
    "ui-autostart-button",
    "Start over"
    );
    $(resumeBtn).click(function () {
    SaveSystem.loadAuto();
    });
    $(restartBtn).click(function () {
    SaveSystem.deleteAuto();
    state.display(state.active.variables.first);
    });
    <</script>>\
    <<else>>\
    <<script>>
    setTimeout(function () {
    state.display(state.active.variables.first);
    }, 10);
    <</script>>\
    <</if>>\
  • TheMadExile wrote:

    The technical details are that the second/inner call to state.display() happens during the rendering of the passage by the first/outer call, which actually does work as you'd expect (the history is updated, output is generated and displayed, etc.), but once that call completes the first/outer call continues and overwrites the output of the second/inner call.  So, you're left in a position where the history updates of both the first/outer and second/inner calls have happened (you can check this), but the display updates of the first/outer call have won out in exclusion of those by the second/inner call.


    To blather on a bit more about that topic.  Essentially, by calling state.display() within a passage, you're doing a kind of recursion.  Furthermore, because of the way that state.display() is structured, the history updates act as a FIFO, while the display updates act as a LIFO.

    To use pseudo-code, state.display() works like this:

    state.display():
    update history:
    render passage:
    update display:
    So, when you call state.display() within a rendering passage, it would look like this: (I used three calls here)

    state.display():
    update history:
    render passage:
    state.display():
    update history:
    render passage:
    state.display():
    update history:
    render passage:
    update display:
    update display:
    update display:
    As you can see, the history updates happen in-order of call, but the display updates happen in reverse-order.
  • Thanks as always, TME! This Start passage is probably the oldest extant code in this project, so I'm not surprised that the autosave API has improved slightly.

    Your explanation is very helpful. I have made the nested display error enough times that I should have caught that on my own, but I think that the fact that the nesting allows the history to update as normal but overwrites the text output threw me off the scent. (There's also the fact that, crazily enough, the bare state.display call did at one time "work"--in the sense that the passage displayed even on the first run in a given browser--before--though I can't see why that would be.)

    It's all working now!
Sign In or Register to comment.