Version: 2.3.14 - Custom macro in Harlowe 3.2.2 running twice when called

Hi there. I’m writing a story that tracks has its own “time” that it tracks (essentially an integer variable between 0 and 23); the player will be able to jump around in the story’s time (i.e. what is displayed on a clock) at multiple points, but the story will also keep track of how much “time” has passed, and regular actions will add one increment to that time. To that end, I thought it would be useful to implement a custom “addTime” macro that will add to the time and change the clock at the same time.

To clarify - $rawTime is essentially military time, which is then later formatted using the $timeCalc macro to print an AM or PM, while $hoursAwake purely tracks how much “time” has elapsed since the beginning of the game (distinct from turns because I only want to add to this at certain points).

I set up a little rerunning link to check that everything is being formatted and interpreted correctly - however, every time I click the link, my addTime macro adds 2 to the $rawTime variable instead of one. I tested this in a normal link, a link with nothing else in the code hook, and simply by letting it run at the end of the passage - it always runs twice.

I also know it’s running twice because when I was troubleshooting, I attempted to fix my macro by changing the increment to 1/2 instead. This incremented by 1 appropriately, but when the rawTime “looped” (i.e. it reached 23 and was reset to 0), the next increment was 0.5 - indicating that the macro had run once, reset the time to 0, then run again and added 0.5. So yeah, it’s running twice for some reason.

Code below - excuse the horrible whitespace management - I was having trouble not getting line breaks to show up since I’m a newbie with custom macros.

(set: num-type $rawTime to 0, num-type $hoursAwake to 0)
\(set: $addTime to
	(macro: [
\	(output:)
\	[(if: $rawTime < 23)
\		[(set: $rawTime to it + 1)]
\	(else-if: $rawTime is 23)
\		  [(set: $rawTime to 0)]
\	(set: $hoursAwake to it + 1)
\	]
\(set: $timeCalc to
	(macro: [
\	(output:)
\	[(if: $rawTime is 0)
\	[(set: $formattedTime to "12 AM")]
\	(else-if: $rawTime <= 11)
\	[(set: $formattedTime to "$rawTime AM")]
\	(else-if: $rawTime is 12)
\	[(set: $formattedTime to "12 PM")]
\	(else-if: $rawTime > 12)
\	[(set: $formattedTime to (str: $rawTime % 12) + " PM")]
\	]
[The raw time is $rawTime.]<rawTime|

[The clock reads $formattedTime.]<clockTime|

[You have been awake for $hoursAwake hours.]<hoursDisplay|

{(link-rerun: "Go forward an hour.")[
(t8n:"dissolve")(rerun: ?rawTime)
(t8n:"dissolve")(rerun: ?clockTime)
(t8n:"dissolve")(rerun: ?hoursDisplay)]

Twine 2.3.14
Harlowe 3.2.2

Any help is much appreciated!

Huh. I made a $log variable to collect info and…yeah, that looks like a Harlowe bug to me: the macro hook runs only once, but the output hook runs twice. Seems like you can work around it by outputting nothing:

(set: $addTime to
	(macro: [
	(if: $rawTime < 23)
		[(set: $rawTime to it + 1)]
	(else-if: $rawTime is 23)
		  [(set: $rawTime to 0)]
	(set: $hoursAwake to it + 1)

So I’m guessing it runs the output hook twice but only collects and displays the output once. I posted an issue to the Harlowe repository.

Another work-around: if your macro takes no inputs and only has output, no actual return value, then you can just make it a passage instead: call it addTime and then do (display: "addTime"). You should just be able to wrap the whole addTime passage in {} for whitespace management.

1 Like

All this makes a ton of sense - thanks so much for the help! I also realized as I woke up this morning that I could simplify my code a ton by simply writing:

	(set: $rawTime to (it + 1) % 24)
	(set: $hoursAwake to it + 1)

So now my code is quite a bit better organized in multiple regards.


1 Like

Actually Harlowe technically displays that output twice.

Harlowe’s Passage Transition process has a two phase rendering:

  • the first phase renders the HTML element structure generated by the ‘current’ Passage wrapped within a special parent container HTML element;
  • the 2nd phase removes that parent element (and all its children) from the page’s Document Object Model, then renders only the generated HTML element structure.

I was under the impression that the HTML elements generated for the ‘current’ Passage were temporary stored internally and then used for each of the two phases, otherwise all (set:) macros directly reference within the Passage would be executed twice. But I could be wrong, or that may not include the outputs of custom macros?

I tried to isolate it to just running the custom macro and displaying output, so it happens even without a Passage Transition involved. Though it’s still displaying, which might be a two phase thing also? Here’s the code I posted to the issue report: the custom macro hook only gets executed once but the output hook runs twice.

{(set: $macroLog to "", $outputLog to "")
(set: $test to
	(macro: [
		(set: $macroLog to it + "x")
		(output:)[(set: $outputLog to it + "x")output
}Macros: $macroLog
Output: $outputLog
(link: "test")[($test:)Macros: $macroLog
Output: $outputLog]

Aaand Leon wrote code to fix this, so once the next release of Harlowe/Twine comes out…